Merge branch 'master' into mime4j
Conflicts: OpenKeychain/build.gradle OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java OpenKeychain/src/main/res/values/strings.xml
This commit is contained in:
@@ -18,7 +18,11 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
@@ -47,7 +51,20 @@ public class BackupFragment extends Fragment {
|
||||
private int mIndex;
|
||||
|
||||
static final int REQUEST_REPEAT_PASSPHRASE = 1;
|
||||
private ExportHelper mExportHelper;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
// we won't get attached to a non-fragment activity, so the cast should be safe
|
||||
mExportHelper = new ExportHelper((FragmentActivity) activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mExportHelper = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
@@ -80,8 +97,7 @@ public class BackupFragment extends Fragment {
|
||||
}
|
||||
|
||||
if (!includeSecretKeys) {
|
||||
ExportHelper exportHelper = new ExportHelper(activity);
|
||||
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, false);
|
||||
startBackup(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,8 +152,7 @@ public class BackupFragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
ExportHelper exportHelper = new ExportHelper(activity);
|
||||
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
||||
startBackup(true);
|
||||
}
|
||||
|
||||
}.execute(activity.getContentResolver());
|
||||
@@ -167,8 +182,19 @@ public class BackupFragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
ExportHelper exportHelper = new ExportHelper(getActivity());
|
||||
exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
||||
startBackup(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void startBackup(boolean exportSecret) {
|
||||
File filename;
|
||||
String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
||||
if (exportSecret) {
|
||||
filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".asc");
|
||||
} else {
|
||||
filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".pub.asc");
|
||||
}
|
||||
mExportHelper.showExportKeysDialog(null, filename, exportSecret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
||||
|
||||
protected Uri mDataUri;
|
||||
|
||||
public static final String EXTRA_ENABLE_WORD_CONFIRM = "enable_word_confirm";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -40,6 +42,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
boolean enableWordConfirm = getIntent().getBooleanExtra(EXTRA_ENABLE_WORD_CONFIRM, false);
|
||||
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
@@ -50,7 +53,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
||||
|
||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
||||
|
||||
startFragment(savedInstanceState, mDataUri);
|
||||
startFragment(savedInstanceState, mDataUri, enableWordConfirm);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,7 +61,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
||||
setContentView(R.layout.certify_fingerprint_activity);
|
||||
}
|
||||
|
||||
private void startFragment(Bundle savedInstanceState, Uri dataUri) {
|
||||
private void startFragment(Bundle savedInstanceState, Uri dataUri, boolean enableWordConfirm) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
// we could end up with overlapping fragments.
|
||||
@@ -67,7 +70,7 @@ public class CertifyFingerprintActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri);
|
||||
CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri, enableWordConfirm);
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
@@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.ExperimentalWordConfirm;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -44,23 +46,24 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
||||
static final int REQUEST_CERTIFY = 1;
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
public static final String ARG_ENABLE_WORD_CONFIRM = "enable_word_confirm";
|
||||
|
||||
private TextView mFingerprint;
|
||||
private TextView mIntro;
|
||||
|
||||
private static final int LOADER_ID_UNIFIED = 0;
|
||||
|
||||
private Uri mDataUri;
|
||||
|
||||
private View mActionNo;
|
||||
private View mActionYes;
|
||||
private boolean mEnableWordConfirm;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static CertifyFingerprintFragment newInstance(Uri dataUri) {
|
||||
public static CertifyFingerprintFragment newInstance(Uri dataUri, boolean enableWordConfirm) {
|
||||
CertifyFingerprintFragment frag = new CertifyFingerprintFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putBoolean(ARG_ENABLE_WORD_CONFIRM, enableWordConfirm);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@@ -72,18 +75,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer());
|
||||
|
||||
mActionNo = view.findViewById(R.id.certify_fingerprint_button_no);
|
||||
mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
|
||||
View actionNo = view.findViewById(R.id.certify_fingerprint_button_no);
|
||||
View actionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
|
||||
|
||||
mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint);
|
||||
mIntro = (TextView) view.findViewById(R.id.certify_fingerprint_intro);
|
||||
|
||||
mActionNo.setOnClickListener(new View.OnClickListener() {
|
||||
actionNo.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
mActionYes.setOnClickListener(new View.OnClickListener() {
|
||||
actionYes.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
certify(mDataUri);
|
||||
@@ -103,6 +107,11 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
mEnableWordConfirm = getArguments().getBoolean(ARG_ENABLE_WORD_CONFIRM);
|
||||
|
||||
if (mEnableWordConfirm) {
|
||||
mIntro.setText(R.string.certify_fingerprint_text_words);
|
||||
}
|
||||
|
||||
loadData(dataUri);
|
||||
}
|
||||
@@ -149,10 +158,13 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_UNIFIED: {
|
||||
if (data.moveToFirst()) {
|
||||
|
||||
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
|
||||
mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
|
||||
|
||||
if (mEnableWordConfirm) {
|
||||
displayWordConfirm(fingerprintBlob);
|
||||
} else {
|
||||
displayHexConfirm(fingerprintBlob);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -162,6 +174,19 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
private void displayHexConfirm(byte[] fingerprintBlob) {
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
|
||||
mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
|
||||
}
|
||||
|
||||
private void displayWordConfirm(byte[] fingerprintBlob) {
|
||||
String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob);
|
||||
|
||||
mFingerprint.setTextSize(24);
|
||||
mFingerprint.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
|
||||
mFingerprint.setText(fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
||||
* We need to make sure we are no longer using it.
|
||||
|
||||
@@ -83,7 +83,9 @@ public class CertifyKeyFragment
|
||||
};
|
||||
private static final int INDEX_MASTER_KEY_ID = 1;
|
||||
private static final int INDEX_USER_ID = 2;
|
||||
@SuppressWarnings("unused")
|
||||
private static final int INDEX_IS_PRIMARY = 3;
|
||||
@SuppressWarnings("unused")
|
||||
private static final int INDEX_IS_REVOKED = 4;
|
||||
|
||||
private MultiUserIdsAdapter mUserIdsAdapter;
|
||||
|
||||
@@ -29,13 +29,14 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
|
||||
@@ -93,7 +94,9 @@ public class DecryptActivity extends BaseActivity {
|
||||
} else if (intent.hasExtra(Intent.EXTRA_TEXT)) {
|
||||
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
Uri uri = readToTempFile(text);
|
||||
uris.add(uri);
|
||||
if (uri != null) {
|
||||
uris.add(uri);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -105,7 +108,9 @@ public class DecryptActivity extends BaseActivity {
|
||||
} else if (intent.hasExtra(Intent.EXTRA_TEXT)) {
|
||||
for (String text : intent.getStringArrayListExtra(Intent.EXTRA_TEXT)) {
|
||||
Uri uri = readToTempFile(text);
|
||||
uris.add(uri);
|
||||
if (uri != null) {
|
||||
uris.add(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +144,9 @@ public class DecryptActivity extends BaseActivity {
|
||||
String text = clip.getItemAt(0).coerceToText(this).toString();
|
||||
uri = readToTempFile(text);
|
||||
}
|
||||
uris.add(uri);
|
||||
if (uri != null) {
|
||||
uris.add(uri);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -170,9 +177,17 @@ public class DecryptActivity extends BaseActivity {
|
||||
|
||||
}
|
||||
|
||||
public Uri readToTempFile(String text) throws IOException {
|
||||
@Nullable public Uri readToTempFile(String text) throws IOException {
|
||||
Uri tempFile = TemporaryStorageProvider.createFile(this);
|
||||
OutputStream outStream = getContentResolver().openOutputStream(tempFile);
|
||||
|
||||
// clean up ascii armored message, fixing newlines and stuff
|
||||
String cleanedText = PgpHelper.getPgpContent(text);
|
||||
if (cleanedText == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if cleanup didn't work, just try the raw data
|
||||
outStream.write(text.getBytes());
|
||||
outStream.close();
|
||||
return tempFile;
|
||||
|
||||
@@ -35,7 +35,6 @@ import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.DefaultItemAnimator;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
@@ -222,18 +221,6 @@ public class DecryptListFragment
|
||||
}
|
||||
}
|
||||
|
||||
private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
File file = new File(inputUri.getPath());
|
||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||
File targetFile = new File(parentDir, originalFilename);
|
||||
FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
|
||||
getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
|
||||
} else {
|
||||
FileHelper.saveDocument(this, mimeType, originalFilename, REQUEST_CODE_OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
@@ -388,7 +375,7 @@ public class DecryptListFragment
|
||||
onFileClick = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
displayWithViewIntent(uri);
|
||||
displayWithViewIntent(uri, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -413,7 +400,7 @@ public class DecryptListFragment
|
||||
|
||||
}
|
||||
|
||||
public void displayWithViewIntent(final Uri uri) {
|
||||
public void displayWithViewIntent(final Uri uri, boolean share) {
|
||||
Activity activity = getActivity();
|
||||
if (activity == null || mCurrentInputUri != null) {
|
||||
return;
|
||||
@@ -432,51 +419,40 @@ public class DecryptListFragment
|
||||
// OpenKeychain's internal viewer
|
||||
if ("text/plain".equals(metadata.getMimeType())) {
|
||||
|
||||
parseMime(outputUri);
|
||||
if (share) {
|
||||
try {
|
||||
String plaintext = FileHelper.readTextFromUri(activity, outputUri, result.getCharset());
|
||||
|
||||
// this is a significant i/o operation, use an asynctask
|
||||
// new AsyncTask<Void,Void,Intent>() {
|
||||
//
|
||||
// @Override
|
||||
// protected Intent doInBackground(Void... params) {
|
||||
//
|
||||
// Activity activity = getActivity();
|
||||
// if (activity == null) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
// intent.setDataAndType(outputUri, "text/plain");
|
||||
// intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
// return intent;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void onPostExecute(Intent intent) {
|
||||
// // for result so we can possibly get a snackbar error from internal viewer
|
||||
// Activity activity = getActivity();
|
||||
// if (intent == null || activity == null) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// LabeledIntent internalIntent = new LabeledIntent(
|
||||
// new Intent(intent)
|
||||
// .setClass(activity, DisplayTextActivity.class)
|
||||
// .putExtra(DisplayTextActivity.EXTRA_METADATA, result),
|
||||
// BuildConfig.APPLICATION_ID, R.string.view_internal, R.drawable.ic_launcher);
|
||||
//
|
||||
// Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
|
||||
// chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
|
||||
// new Parcelable[] { internalIntent });
|
||||
//
|
||||
// activity.startActivity(chooserIntent);
|
||||
// }
|
||||
//
|
||||
// }.execute();
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType(metadata.getMimeType());
|
||||
intent.putExtra(Intent.EXTRA_TEXT, plaintext);
|
||||
startActivity(intent);
|
||||
|
||||
} catch (IOException e) {
|
||||
Notify.create(activity, R.string.error_preparing_data, Style.ERROR).show();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(activity, DisplayTextActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(outputUri, metadata.getMimeType());
|
||||
intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result);
|
||||
activity.startActivity(intent);
|
||||
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(outputUri, metadata.getMimeType());
|
||||
|
||||
Intent intent;
|
||||
if (share) {
|
||||
intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.setType(metadata.getMimeType());
|
||||
intent.putExtra(Intent.EXTRA_STREAM, outputUri);
|
||||
} else {
|
||||
intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(outputUri, metadata.getMimeType());
|
||||
}
|
||||
|
||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show));
|
||||
@@ -486,52 +462,6 @@ public class DecryptListFragment
|
||||
|
||||
}
|
||||
|
||||
private void parseMime(final Uri inputUri) {
|
||||
|
||||
CryptoOperationHelper.Callback<MimeParsingParcel, MimeParsingResult> callback
|
||||
= new CryptoOperationHelper.Callback<MimeParsingParcel, MimeParsingResult>() {
|
||||
|
||||
@Override
|
||||
public MimeParsingParcel createOperationInput() {
|
||||
return new MimeParsingParcel(inputUri, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(MimeParsingResult result) {
|
||||
handleResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationCancelled() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(MimeParsingResult result) {
|
||||
handleResult(result);
|
||||
}
|
||||
|
||||
public void handleResult(MimeParsingResult result) {
|
||||
// TODO: merge with other log
|
||||
// saveKeyResult.getLog().add(result, 0);
|
||||
|
||||
mOutputUris = new HashMap<>(result.getTemporaryUris().size());
|
||||
for (Uri tempUri : result.getTemporaryUris()) {
|
||||
// TODO: use same inputUri for all?
|
||||
mOutputUris.put(inputUri, tempUri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
CryptoOperationHelper mimeParsingHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading);
|
||||
mimeParsingHelper.cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PgpDecryptVerifyInputParcel createOperationInput() {
|
||||
|
||||
@@ -576,13 +506,17 @@ public class DecryptListFragment
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result);
|
||||
activity.startActivity(intent);
|
||||
return true;
|
||||
case R.id.decrypt_share:
|
||||
displayWithViewIntent(model.mInputUri, true);
|
||||
return true;
|
||||
case R.id.decrypt_save:
|
||||
OpenPgpMetadata metadata = result.getDecryptionMetadata();
|
||||
if (metadata == null) {
|
||||
return true;
|
||||
}
|
||||
mCurrentInputUri = model.mInputUri;
|
||||
askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType());
|
||||
FileHelper.saveDocument(this, metadata.getFilename(), model.mInputUri, metadata.getMimeType(),
|
||||
R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT);
|
||||
return true;
|
||||
case R.id.decrypt_delete:
|
||||
deleteFile(activity, model.mInputUri);
|
||||
|
||||
@@ -48,7 +48,6 @@ import org.sufficientlysecure.keychain.service.RevokeKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
|
||||
case LOADER_ID_USER_IDS: {
|
||||
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri,
|
||||
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
case LOADER_ID_SUBKEYS: {
|
||||
|
||||
@@ -83,11 +83,7 @@ public class EncryptDecryptOverviewFragment extends Fragment {
|
||||
mDecryptFile.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
FileHelper.openDocument(EncryptDecryptOverviewFragment.this, "*/*", REQUEST_CODE_INPUT);
|
||||
} else {
|
||||
FileHelper.openFile(EncryptDecryptOverviewFragment.this, null, "*/*", REQUEST_CODE_INPUT);
|
||||
}
|
||||
FileHelper.openDocument(EncryptDecryptOverviewFragment.this, null, "*/*", false, REQUEST_CODE_INPUT);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
@@ -196,13 +195,9 @@ public class EncryptFilesFragment
|
||||
}
|
||||
|
||||
private void addInputUri() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT);
|
||||
} else {
|
||||
FileHelper.openFile(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ?
|
||||
null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri,
|
||||
"*/*", REQUEST_CODE_INPUT);
|
||||
}
|
||||
FileHelper.openDocument(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ?
|
||||
null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri,
|
||||
"*/*", true, REQUEST_CODE_INPUT);
|
||||
}
|
||||
|
||||
private void addInputUri(Uri inputUri) {
|
||||
@@ -230,19 +225,8 @@ public class EncryptFilesFragment
|
||||
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
|
||||
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||
Uri inputUri = model.inputUri;
|
||||
saveDocumentIntent(targetName, inputUri);
|
||||
}
|
||||
|
||||
private void saveDocumentIntent(String targetName, Uri inputUri) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
File file = new File(inputUri.getPath());
|
||||
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
|
||||
File targetFile = new File(parentDir, targetName);
|
||||
FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file),
|
||||
getString(R.string.specify_file_to_encrypt_to), targetFile, REQUEST_CODE_OUTPUT);
|
||||
} else {
|
||||
FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT);
|
||||
}
|
||||
FileHelper.saveDocument(this, targetName, inputUri,
|
||||
R.string.title_encrypt_to_file, R.string.specify_file_to_encrypt_to, REQUEST_CODE_OUTPUT);
|
||||
}
|
||||
|
||||
public void addFile(Intent data) {
|
||||
|
||||
@@ -64,8 +64,8 @@ public class ImportKeysFileFragment extends Fragment {
|
||||
// open .asc or .gpg files
|
||||
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
|
||||
// or gpg types!
|
||||
FileHelper.openFile(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR),
|
||||
"*/*", REQUEST_CODE_FILE);
|
||||
FileHelper.openDocument(ImportKeysFileFragment.this,
|
||||
Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
@@ -70,16 +65,19 @@ import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
|
||||
* StickyListHeaders library which does not extend upon ListView.
|
||||
@@ -536,7 +534,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
);
|
||||
|
||||
if (cursor == null) {
|
||||
Notify.create(activity, R.string.error_loading_keys, Style.ERROR);
|
||||
Notify.create(activity, R.string.error_loading_keys, Notify.Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -37,19 +38,19 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class LogDisplayFragment extends ListFragment implements OnItemClickListener {
|
||||
|
||||
@@ -60,6 +61,8 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
|
||||
public static final String EXTRA_RESULT = "log";
|
||||
protected int mTextColor;
|
||||
|
||||
private Uri mLogTempFile;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -118,170 +121,40 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_log_display_export_log:
|
||||
exportLog();
|
||||
shareLog();
|
||||
break;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void exportLog() {
|
||||
showExportLogDialog(new File(Constants.Path.APP_DIR, "export.log"));
|
||||
}
|
||||
private void shareLog() {
|
||||
|
||||
private void writeToLogFile(final OperationResult.OperationLog operationLog, final File f) {
|
||||
OperationResult.OperationLog currLog = new OperationResult.OperationLog();
|
||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG, 0);
|
||||
|
||||
boolean error = false;
|
||||
|
||||
PrintWriter pw = null;
|
||||
try {
|
||||
pw = new PrintWriter(f);
|
||||
pw.print(getPrintableOperationLog(operationLog, ""));
|
||||
if (pw.checkError()) {//IOException
|
||||
Log.e(Constants.TAG, "Log Export I/O Exception " + f.getAbsolutePath());
|
||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_WRITING, 1);
|
||||
error = true;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "File not found for exporting log " + f.getAbsolutePath());
|
||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN, 1);
|
||||
error = true;
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
if (pw != null) {
|
||||
pw.close();
|
||||
if (!error && pw.checkError()) {//check if it is only pw.close() which generated error
|
||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_WRITING, 1);
|
||||
error = true;
|
||||
|
||||
String log = mResult.getLog().getPrintableOperationLog(getResources(), 0);
|
||||
|
||||
// if there is no log temp file yet, create one
|
||||
if (mLogTempFile == null) {
|
||||
mLogTempFile = TemporaryStorageProvider.createFile(getActivity(), "openkeychain_log.txt", "text/plain");
|
||||
try {
|
||||
OutputStream outputStream = activity.getContentResolver().openOutputStream(mLogTempFile);
|
||||
outputStream.write(log.getBytes());
|
||||
} catch (IOException e) {
|
||||
Notify.create(activity, R.string.error_log_share_internal, Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_SUCCESS, 1);
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_STREAM, mLogTempFile);
|
||||
intent.setType("text/plain");
|
||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivity(intent);
|
||||
|
||||
int opResultCode = error ? OperationResult.RESULT_ERROR : OperationResult.RESULT_OK;
|
||||
OperationResult opResult = new LogExportResult(opResultCode, currLog);
|
||||
opResult.createNotify(getActivity()).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an indented String of an entire OperationLog
|
||||
*
|
||||
* @param opLog log to be converted to indented, printable format
|
||||
* @param basePadding padding to add at the start of all log entries, made for use with SubLogs
|
||||
* @return printable, indented version of passed operationLog
|
||||
*/
|
||||
private String getPrintableOperationLog(OperationResult.OperationLog opLog, String basePadding) {
|
||||
String log = "";
|
||||
for (LogEntryParcel anOpLog : opLog) {
|
||||
log += getPrintableLogEntry(anOpLog, basePadding) + "\n";
|
||||
}
|
||||
log = log.substring(0, log.length() - 1);//gets rid of extra new line
|
||||
return log;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an indented String of a LogEntryParcel including any sub-logs it may contain
|
||||
*
|
||||
* @param entryParcel log entryParcel whose String representation is to be obtained
|
||||
* @return indented version of passed log entryParcel in a readable format
|
||||
*/
|
||||
private String getPrintableLogEntry(OperationResult.LogEntryParcel entryParcel,
|
||||
String basePadding) {
|
||||
|
||||
final String indent = " ";//4 spaces = 1 Indent level
|
||||
|
||||
String padding = basePadding;
|
||||
for (int i = 0; i < entryParcel.mIndent; i++) {
|
||||
padding += indent;
|
||||
}
|
||||
String logText = padding;
|
||||
|
||||
switch (entryParcel.mType.mLevel) {
|
||||
case DEBUG:
|
||||
logText += "[DEBUG]";
|
||||
break;
|
||||
case INFO:
|
||||
logText += "[INFO]";
|
||||
break;
|
||||
case WARN:
|
||||
logText += "[WARN]";
|
||||
break;
|
||||
case ERROR:
|
||||
logText += "[ERROR]";
|
||||
break;
|
||||
case START:
|
||||
logText += "[START]";
|
||||
break;
|
||||
case OK:
|
||||
logText += "[OK]";
|
||||
break;
|
||||
case CANCELLED:
|
||||
logText += "[CANCELLED]";
|
||||
break;
|
||||
}
|
||||
|
||||
// special case: first parameter may be a quantity
|
||||
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
|
||||
&& entryParcel.mParameters[0] instanceof Integer) {
|
||||
logText += getResources().getQuantityString(entryParcel.mType.getMsgId(),
|
||||
(Integer) entryParcel.mParameters[0],
|
||||
entryParcel.mParameters);
|
||||
} else {
|
||||
logText += getResources().getString(entryParcel.mType.getMsgId(),
|
||||
entryParcel.mParameters);
|
||||
}
|
||||
|
||||
if (entryParcel instanceof SubLogEntryParcel) {
|
||||
OperationResult subResult = ((SubLogEntryParcel) entryParcel).getSubResult();
|
||||
LogEntryParcel subEntry = subResult.getLog().getLast();
|
||||
if (subEntry != null) {
|
||||
//the first line of log of subResult is same as entryParcel, so replace logText
|
||||
logText = getPrintableOperationLog(subResult.getLog(), padding);
|
||||
}
|
||||
}
|
||||
|
||||
return logText;
|
||||
}
|
||||
|
||||
private void showExportLogDialog(final File exportFile) {
|
||||
|
||||
String title = this.getString(R.string.title_export_log);
|
||||
|
||||
String message = this.getString(R.string.specify_file_to_export_log_to);
|
||||
|
||||
FileHelper.saveFile(new FileHelper.FileDialogCallback() {
|
||||
@Override
|
||||
public void onFileSelected(File file, boolean checked) {
|
||||
writeToLogFile(mResult.getLog(), file);
|
||||
}
|
||||
}, this.getActivity().getSupportFragmentManager(), title, message, exportFile, null);
|
||||
}
|
||||
|
||||
private static class LogExportResult extends OperationResult {
|
||||
|
||||
public static Creator<LogExportResult> CREATOR = new Creator<LogExportResult>() {
|
||||
public LogExportResult createFromParcel(final Parcel source) {
|
||||
return new LogExportResult(source);
|
||||
}
|
||||
|
||||
public LogExportResult[] newArray(final int size) {
|
||||
return new LogExportResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
public LogExportResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
/**
|
||||
* trivial but necessary to implement the Parcelable protocol.
|
||||
*/
|
||||
public LogExportResult(Parcel source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
@@ -50,15 +49,11 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
* NFC devices.
|
||||
* <p/>
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
* NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created
|
||||
* internally and is NOT meant to be used by signing operations before adding signature time
|
||||
*/
|
||||
public class NfcOperationActivity extends BaseNfcActivity {
|
||||
|
||||
@@ -84,8 +79,8 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
||||
@Override
|
||||
protected void initTheme() {
|
||||
mThemeChanger = new ThemeChanger(this);
|
||||
mThemeChanger.setThemes(R.style.Theme_Keychain_Light_Dialog_SecurityToken,
|
||||
R.style.Theme_Keychain_Dark_Dialog_SecurityToken);
|
||||
mThemeChanger.setThemes(R.style.Theme_Keychain_Light_Dialog,
|
||||
R.style.Theme_Keychain_Dark_Dialog);
|
||||
mThemeChanger.changeTheme();
|
||||
}
|
||||
|
||||
@@ -101,13 +96,6 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
||||
|
||||
mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
|
||||
|
||||
if (mInputParcel == null) {
|
||||
// for compatibility when used from OpenPgpService
|
||||
// (or any place other than CryptoOperationHelper)
|
||||
// NOTE: This CryptoInputParcel cannot be used for signing without adding signature time
|
||||
mInputParcel = new CryptoInputParcel();
|
||||
}
|
||||
|
||||
setTitle(R.string.nfc_text);
|
||||
|
||||
vAnimator = (ViewAnimator) findViewById(R.id.view_animator);
|
||||
@@ -163,9 +151,8 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
||||
break;
|
||||
}
|
||||
case NFC_SIGN: {
|
||||
if (mInputParcel.getSignatureTime() == null) {
|
||||
mInputParcel.addSignatureTime(new Date());
|
||||
}
|
||||
mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime);
|
||||
|
||||
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
|
||||
byte[] hash = mRequiredInput.mInputData[i];
|
||||
int algo = mRequiredInput.mSignAlgos[i];
|
||||
@@ -240,7 +227,7 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
||||
throw new IOException("Inappropriate key flags for smart card key.");
|
||||
}
|
||||
|
||||
// TODO: Is this really needed?
|
||||
// TODO: Is this really used anywhere?
|
||||
mInputParcel.addCryptoData(subkeyBytes, cardSerialNumber);
|
||||
}
|
||||
|
||||
@@ -261,15 +248,13 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
||||
protected void onNfcPostExecute() throws IOException {
|
||||
if (mServiceIntent != null) {
|
||||
// if we're triggered by OpenPgpService
|
||||
// save updated cryptoInputParcel in cache
|
||||
CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel);
|
||||
mServiceIntent.putExtra(EXTRA_CRYPTO_INPUT,
|
||||
getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT));
|
||||
setResult(RESULT_OK, mServiceIntent);
|
||||
} else {
|
||||
Intent result = new Intent();
|
||||
// send back the CryptoInputParcel we received
|
||||
result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel);
|
||||
// send back the CryptoInputParcel we receive, to conform with the pattern in
|
||||
// CryptoOperationHelper
|
||||
setResult(RESULT_OK, result);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,22 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
||||
|
||||
@@ -34,8 +44,14 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
||||
public class OrbotRequiredDialogActivity extends FragmentActivity
|
||||
implements OrbotHelper.DialogActions {
|
||||
|
||||
public static final int MESSAGE_ORBOT_STARTED = 1;
|
||||
public static final int MESSAGE_ORBOT_IGNORE = 2;
|
||||
public static final int MESSAGE_DIALOG_CANCEL = 3;
|
||||
|
||||
// if suppplied and true will start Orbot directly without showing dialog
|
||||
public static final String EXTRA_START_ORBOT = "start_orbot";
|
||||
// used for communicating results when triggered from a service
|
||||
public static final String EXTRA_MESSENGER = "messenger";
|
||||
|
||||
// to provide any previous crypto input into which proxy preference is merged
|
||||
public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input";
|
||||
@@ -43,6 +59,9 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
||||
public static final String RESULT_CRYPTO_INPUT = "result_crypto_input";
|
||||
|
||||
private CryptoInputParcel mCryptoInputParcel;
|
||||
private Messenger mMessenger;
|
||||
|
||||
private ProgressDialog mShowOrbotProgressDialog;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -54,9 +73,16 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
||||
mCryptoInputParcel = new CryptoInputParcel();
|
||||
}
|
||||
|
||||
mMessenger = getIntent().getParcelableExtra(EXTRA_MESSENGER);
|
||||
|
||||
boolean startOrbotDirect = getIntent().getBooleanExtra(EXTRA_START_ORBOT, false);
|
||||
if (startOrbotDirect) {
|
||||
OrbotHelper.bestPossibleOrbotStart(this, this);
|
||||
ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(this);
|
||||
mShowOrbotProgressDialog = new ProgressDialog(theme);
|
||||
mShowOrbotProgressDialog.setTitle(R.string.progress_starting_orbot);
|
||||
mShowOrbotProgressDialog.setCancelable(false);
|
||||
mShowOrbotProgressDialog.show();
|
||||
OrbotHelper.bestPossibleOrbotStart(this, this, false);
|
||||
} else {
|
||||
showDialog();
|
||||
}
|
||||
@@ -84,13 +110,32 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
switch (requestCode) {
|
||||
case OrbotHelper.START_TOR_RESULT: {
|
||||
onOrbotStarted(); // assumption that orbot was started, no way to tell for sure
|
||||
dismissOrbotProgressDialog();
|
||||
// unfortunately, this result is returned immediately and not when Orbot is started
|
||||
// 10s is approximately the longest time Orbot has taken to start
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onOrbotStarted(); // assumption that orbot was started
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* for when Orbot is started without showing the dialog by the EXTRA_START_ORBOT intent extra
|
||||
*/
|
||||
private void dismissOrbotProgressDialog() {
|
||||
if (mShowOrbotProgressDialog != null) {
|
||||
mShowOrbotProgressDialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrbotStarted() {
|
||||
dismissOrbotProgressDialog();
|
||||
sendMessage(MESSAGE_ORBOT_STARTED);
|
||||
Intent intent = new Intent();
|
||||
// send back unmodified CryptoInputParcel for a retry
|
||||
intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
|
||||
@@ -100,6 +145,7 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
||||
|
||||
@Override
|
||||
public void onNeutralButton() {
|
||||
sendMessage(MESSAGE_ORBOT_IGNORE);
|
||||
Intent intent = new Intent();
|
||||
mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy());
|
||||
intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel);
|
||||
@@ -109,6 +155,19 @@ public class OrbotRequiredDialogActivity extends FragmentActivity
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
sendMessage(MESSAGE_DIALOG_CANCEL);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void sendMessage(int what) {
|
||||
if (mMessenger != null) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(Constants.TAG, "Could not deliver message", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
||||
* internally and is NOT meant to be used by signing operations before adding a signature time
|
||||
*/
|
||||
public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
public static final String RESULT_CRYPTO_INPUT = "result_data";
|
||||
|
||||
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
||||
@@ -261,6 +262,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
case DIVERT_TO_CARD:
|
||||
message = getString(R.string.yubikey_pin_for, userId);
|
||||
break;
|
||||
// special case: empty passphrase just returns the empty passphrase
|
||||
case PASSPHRASE_EMPTY:
|
||||
finishCaching(new Passphrase(""));
|
||||
default:
|
||||
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
|
||||
}
|
||||
@@ -280,51 +284,42 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
mPassphraseText.setText(message);
|
||||
|
||||
if (keyType == CanonicalizedSecretKey.SecretKeyType.PATTERN) {
|
||||
// start pattern dialog and show progress circle here...
|
||||
// Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class);
|
||||
// patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123");
|
||||
// startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN);
|
||||
mInput.setVisibility(View.INVISIBLE);
|
||||
mProgress.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
// Hack to open keyboard.
|
||||
// This is the only method that I found to work across all Android versions
|
||||
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
||||
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
||||
// * opening in onActivityCreated does not work on Android 4.4
|
||||
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
mPassphraseEditText.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (getActivity() == null || mPassphraseEditText == null) {
|
||||
return;
|
||||
}
|
||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
// Hack to open keyboard.
|
||||
// This is the only method that I found to work across all Android versions
|
||||
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
||||
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
||||
// * opening in onActivityCreated does not work on Android 4.4
|
||||
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
mPassphraseEditText.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (getActivity() == null || mPassphraseEditText == null) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
mPassphraseEditText.requestFocus();
|
||||
|
||||
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
|
||||
if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin())
|
||||
|| keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
|
||||
mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
} else {
|
||||
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
mPassphraseEditText.requestFocus();
|
||||
|
||||
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
|
||||
if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin())
|
||||
|| keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
|
||||
mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
} else {
|
||||
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
}
|
||||
|
||||
mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
|
||||
AlertDialog dialog = alert.create();
|
||||
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null);
|
||||
|
||||
@@ -1,577 +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;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.nfc.FormatException;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NdefRecord;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.Ndef;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class PassphraseWizardActivity extends FragmentActivity {
|
||||
//public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener {
|
||||
//create or authenticate
|
||||
public String selectedAction;
|
||||
//for lockpattern
|
||||
public static char[] pattern;
|
||||
private static String passphrase = "";
|
||||
//nfc string
|
||||
private static byte[] output = new byte[8];
|
||||
|
||||
public static final String CREATE_METHOD = "create";
|
||||
public static final String AUTHENTICATION = "authenticate";
|
||||
|
||||
NfcAdapter adapter;
|
||||
PendingIntent pendingIntent;
|
||||
IntentFilter writeTagFilters[];
|
||||
boolean writeMode;
|
||||
Tag myTag;
|
||||
boolean writeNFC = false;
|
||||
boolean readNFC = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setTitle(R.string.unlock_method);
|
||||
}
|
||||
|
||||
selectedAction = getIntent().getAction();
|
||||
if (savedInstanceState == null) {
|
||||
SelectMethods selectMethods = new SelectMethods();
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.add(R.id.fragmentContainer, selectMethods).commit();
|
||||
}
|
||||
setContentView(R.layout.passphrase_wizard);
|
||||
|
||||
adapter = NfcAdapter.getDefaultAdapter(this);
|
||||
if (adapter != null) {
|
||||
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, PassphraseWizardActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
|
||||
IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
|
||||
tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
writeTagFilters = new IntentFilter[]{tagDetected};
|
||||
}
|
||||
}
|
||||
|
||||
public void noPassphrase(View view) {
|
||||
passphrase = "";
|
||||
Toast.makeText(this, R.string.no_passphrase_set, Toast.LENGTH_SHORT).show();
|
||||
this.finish();
|
||||
}
|
||||
|
||||
public void passphrase(View view) {
|
||||
Passphrase passphrase = new Passphrase();
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.fragmentContainer, passphrase).addToBackStack(null).commit();
|
||||
}
|
||||
|
||||
public void startLockpattern(View view) {
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setTitle(R.string.draw_lockpattern);
|
||||
}
|
||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
||||
// LockPatternFragment lpf = LockPatternFragment.newInstance("asd");
|
||||
|
||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
}
|
||||
|
||||
public void cancel(View view) {
|
||||
this.finish();
|
||||
}
|
||||
|
||||
public void savePassphrase(View view) {
|
||||
EditText passphrase = (EditText) findViewById(R.id.passphrase);
|
||||
passphrase.setError(null);
|
||||
String pw = passphrase.getText().toString();
|
||||
//check and save passphrase
|
||||
if (selectedAction.equals(CREATE_METHOD)) {
|
||||
EditText passphraseAgain = (EditText) findViewById(R.id.passphraseAgain);
|
||||
passphraseAgain.setError(null);
|
||||
String pwAgain = passphraseAgain.getText().toString();
|
||||
|
||||
if (!TextUtils.isEmpty(pw)) {
|
||||
if (!TextUtils.isEmpty(pwAgain)) {
|
||||
if (pw.equals(pwAgain)) {
|
||||
PassphraseWizardActivity.passphrase = pw;
|
||||
Toast.makeText(this, getString(R.string.passphrase_saved), Toast.LENGTH_SHORT).show();
|
||||
this.finish();
|
||||
} else {
|
||||
passphrase.setError(getString(R.string.passphrase_invalid));
|
||||
passphrase.requestFocus();
|
||||
}
|
||||
} else {
|
||||
passphraseAgain.setError(getString(R.string.missing_passphrase));
|
||||
passphraseAgain.requestFocus();
|
||||
}
|
||||
} else {
|
||||
passphrase.setError(getString(R.string.missing_passphrase));
|
||||
passphrase.requestFocus();
|
||||
}
|
||||
}
|
||||
//check for right passphrase
|
||||
if (selectedAction.equals(AUTHENTICATION)) {
|
||||
if (pw.equals(PassphraseWizardActivity.passphrase)) {
|
||||
Toast.makeText(this, getString(R.string.unlocked), Toast.LENGTH_SHORT).show();
|
||||
this.finish();
|
||||
} else {
|
||||
passphrase.setError(getString(R.string.passphrase_invalid));
|
||||
passphrase.requestFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NFC(View view) {
|
||||
if (adapter != null) {
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setTitle(R.string.nfc_title);
|
||||
}
|
||||
NFCFragment nfc = new NFCFragment();
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.fragmentContainer, nfc).addToBackStack(null).commit();
|
||||
|
||||
//if you want to create a new method or just authenticate
|
||||
if (CREATE_METHOD.equals(selectedAction)) {
|
||||
writeNFC = true;
|
||||
} else if (AUTHENTICATION.equals(selectedAction)) {
|
||||
readNFC = true;
|
||||
}
|
||||
|
||||
if (!adapter.isEnabled()) {
|
||||
showAlertDialog(getString(R.string.enable_nfc), true);
|
||||
}
|
||||
} else {
|
||||
showAlertDialog(getString(R.string.no_nfc_support), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
||||
myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||
|
||||
if (writeNFC && CREATE_METHOD.equals(selectedAction)) {
|
||||
//write new password on NFC tag
|
||||
try {
|
||||
if (myTag != null) {
|
||||
write(myTag);
|
||||
writeNFC = false; //just write once
|
||||
Toast.makeText(this, R.string.nfc_write_succesful, Toast.LENGTH_SHORT).show();
|
||||
//advance to lockpattern
|
||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
}
|
||||
} catch (IOException | FormatException e) {
|
||||
Log.e(Constants.TAG, "Failed to write on NFC tag", e);
|
||||
}
|
||||
|
||||
} else if (readNFC && AUTHENTICATION.equals(selectedAction)) {
|
||||
//read pw from NFC tag
|
||||
try {
|
||||
if (myTag != null) {
|
||||
//if tag detected, read tag
|
||||
String pwtag = read(myTag);
|
||||
if (output != null && pwtag.equals(output.toString())) {
|
||||
|
||||
//passwort matches, go to next view
|
||||
Toast.makeText(this, R.string.passphrases_match + "!", Toast.LENGTH_SHORT).show();
|
||||
|
||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
readNFC = false; //just once
|
||||
} else {
|
||||
//passwort doesnt match
|
||||
TextView nfc = (TextView) findViewById(R.id.nfcText);
|
||||
nfc.setText(R.string.nfc_wrong_tag);
|
||||
}
|
||||
}
|
||||
} catch (IOException | FormatException e) {
|
||||
Log.e(Constants.TAG, "Failed to read NFC tag", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void write(Tag tag) throws IOException, FormatException {
|
||||
//generate new random key and write them on the tag
|
||||
SecureRandom sr = new SecureRandom();
|
||||
sr.nextBytes(output);
|
||||
NdefRecord[] records = {createRecord(output.toString())};
|
||||
NdefMessage message = new NdefMessage(records);
|
||||
Ndef ndef = Ndef.get(tag);
|
||||
ndef.connect();
|
||||
ndef.writeNdefMessage(message);
|
||||
ndef.close();
|
||||
}
|
||||
|
||||
private String read(Tag tag) throws IOException, FormatException {
|
||||
//read string from tag
|
||||
String password = null;
|
||||
Ndef ndef = Ndef.get(tag);
|
||||
ndef.connect();
|
||||
NdefMessage ndefMessage = ndef.getCachedNdefMessage();
|
||||
|
||||
NdefRecord[] records = ndefMessage.getRecords();
|
||||
for (NdefRecord ndefRecord : records) {
|
||||
if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
|
||||
try {
|
||||
password = readText(ndefRecord);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(Constants.TAG, "Failed to read password from tag", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
ndef.close();
|
||||
return password;
|
||||
}
|
||||
|
||||
private String readText(NdefRecord record) throws UnsupportedEncodingException {
|
||||
//low-level method for reading nfc
|
||||
byte[] payload = record.getPayload();
|
||||
String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
|
||||
int languageCodeLength = payload[0] & 0063;
|
||||
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
|
||||
}
|
||||
|
||||
private NdefRecord createRecord(String text) throws UnsupportedEncodingException {
|
||||
//low-level method for writing nfc
|
||||
String lang = "en";
|
||||
byte[] textBytes = text.getBytes();
|
||||
byte[] langBytes = lang.getBytes("US-ASCII");
|
||||
int langLength = langBytes.length;
|
||||
int textLength = textBytes.length;
|
||||
byte[] payload = new byte[1 + langLength + textLength];
|
||||
|
||||
// set status byte (see NDEF spec for actual bits)
|
||||
payload[0] = (byte) langLength;
|
||||
// copy langbytes and textbytes into payload
|
||||
System.arraycopy(langBytes, 0, payload, 1, langLength);
|
||||
System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength);
|
||||
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload);
|
||||
}
|
||||
|
||||
public void showAlertDialog(String message, boolean nfc) {
|
||||
//This method shows an AlertDialog
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
alert.setTitle("Information").setMessage(message).setPositiveButton("Ok",
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
}
|
||||
}
|
||||
);
|
||||
if (nfc) {
|
||||
|
||||
alert.setNeutralButton(R.string.nfc_settings,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
alert.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
//pause this app and free nfc intent
|
||||
super.onPause();
|
||||
if (adapter != null) {
|
||||
WriteModeOff();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
//resume this app and get nfc intent
|
||||
super.onResume();
|
||||
if (adapter != null) {
|
||||
WriteModeOn();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteModeOn() {
|
||||
//enable nfc for this view
|
||||
writeMode = true;
|
||||
adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
|
||||
}
|
||||
|
||||
private void WriteModeOff() {
|
||||
//disable nfc for this view
|
||||
writeMode = false;
|
||||
adapter.disableForegroundDispatch(this);
|
||||
}
|
||||
|
||||
public static class SelectMethods extends Fragment {
|
||||
// private OnFragmentInteractionListener mListener;
|
||||
|
||||
/**
|
||||
* Use this factory method to create a new instance of
|
||||
* this fragment using the provided parameters.
|
||||
*/
|
||||
public SelectMethods() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (getActivity().getActionBar() != null) {
|
||||
getActivity().getActionBar().setTitle(R.string.unlock_method);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.passphrase_wizard_fragment_select_methods, container, false);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onAttach(Activity activity) {
|
||||
// super.onAttach(activity);
|
||||
// try {
|
||||
// mListener = (OnFragmentInteractionListener) activity;
|
||||
// } catch (ClassCastException e) {
|
||||
// throw new ClassCastException(activity.toString()
|
||||
// + " must implement OnFragmentInteractionListener");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onDetach() {
|
||||
// super.onDetach();
|
||||
// mListener = null;
|
||||
// }
|
||||
|
||||
/**
|
||||
* This interface must be implemented by activities that contain this
|
||||
* fragment to allow an interaction in this fragment to be communicated
|
||||
* to the activity and potentially other fragments contained in that
|
||||
* activity.
|
||||
* <p/>
|
||||
* See the Android Training lesson <a href=
|
||||
* "http://developer.android.com/training/basics/fragments/communicating.html"
|
||||
* >Communicating with Other Fragments</a> for more information.
|
||||
*/
|
||||
// public static interface OnFragmentInteractionListener {
|
||||
// public void onFragmentInteraction(Uri uri);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * A simple {@link android.support.v4.app.Fragment} subclass.
|
||||
// * Activities that contain this fragment must implement the
|
||||
// * {@link com.haibison.android.lockpattern.Passphrase.OnFragmentInteractionListener} interface
|
||||
// * to handle interaction events.
|
||||
// */
|
||||
public static class Passphrase extends Fragment {
|
||||
|
||||
// private OnFragmentInteractionListener mListener;
|
||||
|
||||
public Passphrase() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View view = inflater.inflate(R.layout.passphrase_wizard_fragment_passphrase, container, false);
|
||||
EditText passphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
|
||||
TextView passphraseText = (TextView) view.findViewById(R.id.passphraseText);
|
||||
TextView passphraseTextAgain = (TextView) view.findViewById(R.id.passphraseTextAgain);
|
||||
String selectedAction = getActivity().getIntent().getAction();
|
||||
if (selectedAction.equals(AUTHENTICATION)) {
|
||||
passphraseAgain.setVisibility(View.GONE);
|
||||
passphraseTextAgain.setVisibility(View.GONE);
|
||||
passphraseText.setText(R.string.enter_passphrase);
|
||||
// getActivity().getActionBar().setTitle(R.string.enter_passphrase);
|
||||
} else if (selectedAction.equals(CREATE_METHOD)) {
|
||||
passphraseAgain.setVisibility(View.VISIBLE);
|
||||
passphraseTextAgain.setVisibility(View.VISIBLE);
|
||||
passphraseText.setText(R.string.passphrase);
|
||||
// getActivity().getActionBar().setTitle(R.string.set_passphrase);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onAttach(Activity activity) {
|
||||
// super.onAttach(activity);
|
||||
// try {
|
||||
// mListener = (OnFragmentInteractionListener) activity;
|
||||
// } catch (ClassCastException e) {
|
||||
// throw new ClassCastException(activity.toString()
|
||||
// + " must implement OnFragmentInteractionListener");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onDetach() {
|
||||
// super.onDetach();
|
||||
// mListener = null;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * This interface must be implemented by activities that contain this
|
||||
// * fragment to allow an interaction in this fragment to be communicated
|
||||
// * to the activity and potentially other fragments contained in that
|
||||
// * activity.
|
||||
// * <p/>
|
||||
// * See the Android Training lesson <a href=
|
||||
// * "http://developer.android.com/training/basics/fragments/communicating.html"
|
||||
// * >Communicating with Other Fragments</a> for more information.
|
||||
// */
|
||||
// public interface OnFragmentInteractionListener {
|
||||
// public void onFragmentInteraction(Uri uri);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A simple {@link android.support.v4.app.Fragment} subclass.
|
||||
* Activities that contain this fragment must implement the
|
||||
* interface
|
||||
* to handle interaction events.
|
||||
* Use the method to
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
public static class NFCFragment extends Fragment {
|
||||
// TODO: Rename parameter arguments, choose names that match
|
||||
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
|
||||
private static final String ARG_PARAM1 = "param1";
|
||||
private static final String ARG_PARAM2 = "param2";
|
||||
|
||||
// TODO: Rename and change types of parameters
|
||||
private String mParam1;
|
||||
private String mParam2;
|
||||
|
||||
// private OnFragmentInteractionListener mListener;
|
||||
|
||||
/**
|
||||
* Use this factory method to create a new instance of
|
||||
* this fragment using the provided parameters.
|
||||
*
|
||||
* @param param1 Parameter 1.
|
||||
* @param param2 Parameter 2.
|
||||
* @return A new instance of fragment SelectMethods.
|
||||
*/
|
||||
// TODO: Rename and change types and number of parameters
|
||||
public static NFCFragment newInstance(String param1, String param2) {
|
||||
NFCFragment fragment = new NFCFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_PARAM1, param1);
|
||||
args.putString(ARG_PARAM2, param2);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public NFCFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getArguments() != null) {
|
||||
mParam1 = getArguments().getString(ARG_PARAM1);
|
||||
mParam2 = getArguments().getString(ARG_PARAM2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.passphrase_wizard_fragment_nfc, container, false);
|
||||
}
|
||||
|
||||
// // TODO: Rename method, update argument and hook method into UI event
|
||||
// public void onButtonPressed(Uri uri) {
|
||||
// if (mListener != null) {
|
||||
// mListener.onFragmentInteraction(uri);
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Override
|
||||
// public void onAttach(Activity activity) {
|
||||
// super.onAttach(activity);
|
||||
// try {
|
||||
// mListener = (OnFragmentInteractionListener) activity;
|
||||
// } catch (ClassCastException e) {
|
||||
// throw new ClassCastException(activity.toString()
|
||||
// + " must implement OnFragmentInteractionListener");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// @Override
|
||||
// public void onDetach() {
|
||||
// super.onDetach();
|
||||
// mListener = null;
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,11 +18,12 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
@@ -31,6 +32,8 @@ import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -74,7 +77,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
|
||||
String action = getIntent().getAction();
|
||||
|
||||
if (action != null && action.equals(ACTION_PREFS_CLOUD)) {
|
||||
if (ACTION_PREFS_CLOUD.equals(action)) {
|
||||
addPreferencesFromResource(R.xml.cloud_search_prefs);
|
||||
|
||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
||||
@@ -91,14 +94,14 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
}
|
||||
});
|
||||
initializeSearchKeyserver(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
|
||||
(SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
|
||||
);
|
||||
initializeSearchKeybase(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
|
||||
(SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
|
||||
);
|
||||
|
||||
} else if (action != null && action.equals(ACTION_PREFS_ADV)) {
|
||||
addPreferencesFromResource(R.xml.adv_preferences);
|
||||
} else if (ACTION_PREFS_ADV.equals(action)) {
|
||||
addPreferencesFromResource(R.xml.passphrase_preferences);
|
||||
|
||||
initializePassphraseCacheSubs(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
|
||||
@@ -112,7 +115,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
initializeUseNumKeypadForYubiKeyPin(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
|
||||
|
||||
} else if (action != null && action.equals(ACTION_PREFS_GUI)) {
|
||||
} else if (ACTION_PREFS_GUI.equals(action)) {
|
||||
addPreferencesFromResource(R.xml.gui_preferences);
|
||||
|
||||
initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
|
||||
@@ -192,10 +195,10 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
}
|
||||
});
|
||||
initializeSearchKeyserver(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
|
||||
(SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
|
||||
);
|
||||
initializeSearchKeybase(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
|
||||
(SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -219,14 +222,14 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
/**
|
||||
* This fragment shows the PIN/password preferences
|
||||
*/
|
||||
public static class AdvancedPrefsFragment extends PreferenceFragment {
|
||||
public static class PassphrasePrefsFragment extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.adv_preferences);
|
||||
addPreferencesFromResource(R.xml.passphrase_preferences);
|
||||
|
||||
initializePassphraseCacheSubs(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
|
||||
@@ -253,8 +256,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
}
|
||||
|
||||
public static class Initializer {
|
||||
private CheckBoxPreference mUseTor;
|
||||
private CheckBoxPreference mUseNormalProxy;
|
||||
private SwitchPreference mUseTor;
|
||||
private SwitchPreference mUseNormalProxy;
|
||||
private EditTextPreference mProxyHost;
|
||||
private EditTextPreference mProxyPort;
|
||||
private ListPreference mProxyType;
|
||||
@@ -290,8 +293,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
mActivity.addPreferencesFromResource(R.xml.proxy_prefs);
|
||||
}
|
||||
|
||||
mUseTor = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY);
|
||||
mUseNormalProxy = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY);
|
||||
mUseTor = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY);
|
||||
mUseNormalProxy = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY);
|
||||
mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST);
|
||||
mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT);
|
||||
mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE);
|
||||
@@ -467,11 +470,121 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This fragment shows the keyserver/contacts sync preferences
|
||||
*/
|
||||
public static class SyncPrefsFragment extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.sync_preferences);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// this needs to be done in onResume since the user can change sync values from Android
|
||||
// settings and we need to reflect that change when the user navigates back
|
||||
AccountManager manager = AccountManager.get(getActivity());
|
||||
final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
|
||||
// for keyserver sync
|
||||
initializeSyncCheckBox(
|
||||
(SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER),
|
||||
account,
|
||||
Constants.PROVIDER_AUTHORITY
|
||||
);
|
||||
// for contacts sync
|
||||
initializeSyncCheckBox(
|
||||
(SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS),
|
||||
account,
|
||||
ContactsContract.AUTHORITY
|
||||
);
|
||||
}
|
||||
|
||||
private void initializeSyncCheckBox(final SwitchPreference syncCheckBox,
|
||||
final Account account,
|
||||
final String authority) {
|
||||
boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
|
||||
syncCheckBox.setChecked(syncEnabled);
|
||||
setSummary(syncCheckBox, authority, syncEnabled);
|
||||
|
||||
syncCheckBox.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
boolean syncEnabled = (Boolean) newValue;
|
||||
if (syncEnabled) {
|
||||
ContentResolver.setSyncAutomatically(account, authority, true);
|
||||
} else {
|
||||
// disable syncs
|
||||
ContentResolver.setSyncAutomatically(account, authority, false);
|
||||
// cancel any ongoing/pending syncs
|
||||
ContentResolver.cancelSync(account, authority);
|
||||
}
|
||||
setSummary(syncCheckBox, authority, syncEnabled);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setSummary(SwitchPreference syncCheckBox, String authority,
|
||||
boolean checked) {
|
||||
switch (authority) {
|
||||
case Constants.PROVIDER_AUTHORITY: {
|
||||
if (checked) {
|
||||
syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_on);
|
||||
} else {
|
||||
syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_off);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ContactsContract.AUTHORITY: {
|
||||
if (checked) {
|
||||
syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_on);
|
||||
} else {
|
||||
syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_off);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This fragment shows experimental features
|
||||
*/
|
||||
public static class ExperimentalPrefsFragment extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.experimental_preferences);
|
||||
|
||||
initializeExperimentalEnableWordConfirm(
|
||||
(SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_WORD_CONFIRM));
|
||||
|
||||
initializeExperimentalEnableLinkedIdentities(
|
||||
(SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_LINKED_IDENTITIES));
|
||||
|
||||
initializeExperimentalEnableKeybase(
|
||||
(SwitchPreference) findPreference(Constants.Pref.EXPERIMENTAL_ENABLE_KEYBASE));
|
||||
|
||||
initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return AdvancedPrefsFragment.class.getName().equals(fragmentName)
|
||||
return PassphrasePrefsFragment.class.getName().equals(fragmentName)
|
||||
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)
|
||||
|| ProxyPrefsFragment.class.getName().equals(fragmentName)
|
||||
|| GuiPrefsFragment.class.getName().equals(fragmentName)
|
||||
|| SyncPrefsFragment.class.getName().equals(fragmentName)
|
||||
|| ExperimentalPrefsFragment.class.getName().equals(fragmentName)
|
||||
|| super.isValidFragment(fragmentName);
|
||||
}
|
||||
|
||||
@@ -502,11 +615,13 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
|
||||
private static void initializeTheme(final ListPreference mTheme) {
|
||||
mTheme.setValue(sPreferences.getTheme());
|
||||
mTheme.setSummary(mTheme.getEntry());
|
||||
mTheme.setSummary(mTheme.getEntry() + "\n"
|
||||
+ mTheme.getContext().getString(R.string.label_experimental_settings_theme_summary));
|
||||
mTheme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mTheme.setValue((String) newValue);
|
||||
mTheme.setSummary(mTheme.getEntry());
|
||||
mTheme.setSummary(mTheme.getEntry() + "\n"
|
||||
+ mTheme.getContext().getString(R.string.label_experimental_settings_theme_summary));
|
||||
sPreferences.setTheme((String) newValue);
|
||||
|
||||
((SettingsActivity) mTheme.getContext()).recreate();
|
||||
@@ -516,7 +631,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) {
|
||||
private static void initializeSearchKeyserver(final SwitchPreference mSearchKeyserver) {
|
||||
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
|
||||
mSearchKeyserver.setChecked(prefs.searchKeyserver);
|
||||
mSearchKeyserver.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@@ -529,7 +644,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeSearchKeybase(final CheckBoxPreference mSearchKeybase) {
|
||||
private static void initializeSearchKeybase(final SwitchPreference mSearchKeybase) {
|
||||
Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs();
|
||||
mSearchKeybase.setChecked(prefs.searchKeybase);
|
||||
mSearchKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@@ -571,4 +686,37 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeExperimentalEnableWordConfirm(final SwitchPreference mExperimentalEnableWordConfirm) {
|
||||
mExperimentalEnableWordConfirm.setChecked(sPreferences.getExperimentalEnableWordConfirm());
|
||||
mExperimentalEnableWordConfirm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mExperimentalEnableWordConfirm.setChecked((Boolean) newValue);
|
||||
sPreferences.setExperimentalEnableWordConfirm((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeExperimentalEnableLinkedIdentities(final SwitchPreference mExperimentalEnableLinkedIdentities) {
|
||||
mExperimentalEnableLinkedIdentities.setChecked(sPreferences.getExperimentalEnableLinkedIdentities());
|
||||
mExperimentalEnableLinkedIdentities.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mExperimentalEnableLinkedIdentities.setChecked((Boolean) newValue);
|
||||
sPreferences.setExperimentalEnableLinkedIdentities((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeExperimentalEnableKeybase(final SwitchPreference mExperimentalKeybase) {
|
||||
mExperimentalKeybase.setChecked(sPreferences.getExperimentalEnableKeybase());
|
||||
mExperimentalKeybase.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mExperimentalKeybase.setChecked((Boolean) newValue);
|
||||
sPreferences.setExperimentalEnableKeybase((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
@@ -34,6 +35,19 @@ public class SettingsKeyServerActivity extends BaseActivity {
|
||||
Intent intent = getIntent();
|
||||
String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
|
||||
loadFragment(savedInstanceState, servers);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -32,6 +37,9 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.CollapsingToolbarLayout;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
@@ -45,14 +53,13 @@ import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Animation.AnimationListener;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.getbase.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
@@ -65,8 +72,10 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
@@ -80,8 +89,6 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.NfcHelper;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
LoaderManager.LoaderCallbacks<Cursor>,
|
||||
@@ -97,6 +104,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
static final int REQUEST_DELETE = 4;
|
||||
|
||||
public static final String EXTRA_DISPLAY_RESULT = "display_result";
|
||||
public static final String EXTRA_LINKED_TRANSITION = "linked_transition";
|
||||
|
||||
ProviderHelper mProviderHelper;
|
||||
|
||||
@@ -107,16 +115,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
private ArrayList<ParcelableKeyRing> mKeyList;
|
||||
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper;
|
||||
|
||||
private TextView mName;
|
||||
private TextView mStatusText;
|
||||
private ImageView mStatusImage;
|
||||
private RelativeLayout mBigToolbar;
|
||||
private AppBarLayout mAppBarLayout;
|
||||
private CollapsingToolbarLayout mCollapsingToolbarLayout;
|
||||
|
||||
private ImageButton mActionEncryptFile;
|
||||
private ImageButton mActionEncryptText;
|
||||
private ImageButton mActionNfc;
|
||||
private FloatingActionButton mFab;
|
||||
private ImageView mPhoto;
|
||||
private FrameLayout mPhotoLayout;
|
||||
private ImageView mQrCode;
|
||||
private CardView mQrCodeLayout;
|
||||
|
||||
@@ -156,16 +165,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
|
||||
setTitle(null);
|
||||
|
||||
mName = (TextView) findViewById(R.id.view_key_name);
|
||||
mStatusText = (TextView) findViewById(R.id.view_key_status);
|
||||
mStatusImage = (ImageView) findViewById(R.id.view_key_status_image);
|
||||
mBigToolbar = (RelativeLayout) findViewById(R.id.toolbar_big);
|
||||
mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar_layout);
|
||||
mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
|
||||
|
||||
mActionEncryptFile = (ImageButton) findViewById(R.id.view_key_action_encrypt_files);
|
||||
mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text);
|
||||
mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc);
|
||||
mFab = (FloatingActionButton) findViewById(R.id.fab);
|
||||
mPhoto = (ImageView) findViewById(R.id.view_key_photo);
|
||||
mPhotoLayout = (FrameLayout) findViewById(R.id.view_key_photo_layout);
|
||||
mQrCode = (ImageView) findViewById(R.id.view_key_qr_code);
|
||||
mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout);
|
||||
|
||||
@@ -287,13 +297,26 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
return;
|
||||
}
|
||||
|
||||
boolean linkedTransition = getIntent().getBooleanExtra(EXTRA_LINKED_TRANSITION, false);
|
||||
if (linkedTransition && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
postponeEnterTransition();
|
||||
}
|
||||
|
||||
FragmentManager manager = getSupportFragmentManager();
|
||||
// Create an instance of the fragment
|
||||
final ViewKeyFragment frag = ViewKeyFragment.newInstance(mDataUri);
|
||||
final ViewKeyFragment frag = ViewKeyFragment.newInstance(mDataUri,
|
||||
linkedTransition ? PostponeType.LINKED : PostponeType.NONE);
|
||||
manager.beginTransaction()
|
||||
.replace(R.id.view_key_fragment, frag)
|
||||
.commit();
|
||||
|
||||
if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) {
|
||||
final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(mDataUri);
|
||||
manager.beginTransaction()
|
||||
.replace(R.id.view_key_keybase_fragment, keybaseFrag)
|
||||
.commit();
|
||||
}
|
||||
|
||||
// need to postpone loading of the yubikey fragment until after mMasterKeyId
|
||||
// is available, but we mark here that this should be done
|
||||
mShowYubikeyAfterCreation = true;
|
||||
@@ -344,12 +367,23 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_add_linked_identity: {
|
||||
Intent intent = new Intent(this, LinkedIdWizard.class);
|
||||
intent.setData(mDataUri);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_edit: {
|
||||
editKey(mDataUri);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_certify_fingerprint: {
|
||||
certifyFingeprint(mDataUri);
|
||||
certifyFingeprint(mDataUri, false);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_certify_fingerprint_word: {
|
||||
certifyFingeprint(mDataUri, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -360,10 +394,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
|
||||
editKey.setVisible(mIsSecret);
|
||||
|
||||
MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file);
|
||||
exportKey.setVisible(mIsSecret);
|
||||
|
||||
MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity);
|
||||
addLinked.setVisible(mIsSecret
|
||||
&& Preferences.getPreferences(this).getExperimentalEnableLinkedIdentities());
|
||||
|
||||
MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);
|
||||
certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked);
|
||||
MenuItem certifyFingerprintWord = menu.findItem(R.id.menu_key_view_certify_fingerprint_word);
|
||||
certifyFingerprintWord.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked
|
||||
&& Preferences.getPreferences(this).getExperimentalEnableWordConfirm());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -375,16 +418,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT);
|
||||
}
|
||||
|
||||
private void certifyFingeprint(Uri dataUri) {
|
||||
private void certifyFingeprint(Uri dataUri, boolean enableWordConfirm) {
|
||||
Intent intent = new Intent(this, CertifyFingerprintActivity.class);
|
||||
intent.setData(dataUri);
|
||||
intent.putExtra(CertifyFingerprintActivity.EXTRA_ENABLE_WORD_CONFIRM, enableWordConfirm);
|
||||
|
||||
startActivityForResult(intent, REQUEST_CERTIFY);
|
||||
}
|
||||
|
||||
private void certifyImmediate() {
|
||||
Intent intent = new Intent(this, CertifyKeyActivity.class);
|
||||
intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId});
|
||||
intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { mMasterKeyId });
|
||||
|
||||
startActivityForResult(intent, REQUEST_CERTIFY);
|
||||
}
|
||||
@@ -413,7 +457,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
|
||||
private void backupToFile() {
|
||||
new ExportHelper(this).showExportKeysDialog(
|
||||
mMasterKeyId, Constants.Path.APP_DIR_FILE, true);
|
||||
mMasterKeyId, new File(Constants.Path.APP_DIR,
|
||||
KeyFormattingUtils.convertKeyIdToHex(mMasterKeyId) + ".sec.asc"), true);
|
||||
}
|
||||
|
||||
private void deleteKey() {
|
||||
@@ -437,13 +482,13 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_QR_FINGERPRINT: {
|
||||
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is an EXTRA_RESULT, that's an error. Just show it.
|
||||
if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
@@ -465,11 +510,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
}
|
||||
|
||||
case REQUEST_BACKUP: {
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
backupToFile();
|
||||
return;
|
||||
}
|
||||
|
||||
case REQUEST_CERTIFY: {
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
@@ -478,6 +531,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
}
|
||||
|
||||
case REQUEST_DELETE: {
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
return;
|
||||
@@ -530,7 +587,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
finish();
|
||||
}
|
||||
}, R.string.snack_yubikey_view).show();
|
||||
|
||||
// and if it's not found, offer import
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
|
||||
@@ -728,9 +784,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
// get name, email, and comment from USER_ID
|
||||
KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
|
||||
if (mainUserId.name != null) {
|
||||
mName.setText(mainUserId.name);
|
||||
mCollapsingToolbarLayout.setTitle(mainUserId.name);
|
||||
} else {
|
||||
mName.setText(R.string.user_id_no_name);
|
||||
mCollapsingToolbarLayout.setTitle(getString(R.string.user_id_no_name));
|
||||
}
|
||||
|
||||
mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
@@ -767,8 +823,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap photo) {
|
||||
if (photo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPhoto.setImageBitmap(photo);
|
||||
mPhoto.setVisibility(View.VISIBLE);
|
||||
mPhotoLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -781,9 +841,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
State.REVOKED, R.color.icons, true);
|
||||
color = getResources().getColor(R.color.key_flag_red);
|
||||
|
||||
mActionEncryptFile.setVisibility(View.GONE);
|
||||
mActionEncryptText.setVisibility(View.GONE);
|
||||
mActionNfc.setVisibility(View.GONE);
|
||||
mActionEncryptFile.setVisibility(View.INVISIBLE);
|
||||
mActionEncryptText.setVisibility(View.INVISIBLE);
|
||||
mActionNfc.setVisibility(View.INVISIBLE);
|
||||
mFab.setVisibility(View.GONE);
|
||||
mQrCodeLayout.setVisibility(View.GONE);
|
||||
} else if (mIsExpired) {
|
||||
@@ -797,9 +857,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
State.EXPIRED, R.color.icons, true);
|
||||
color = getResources().getColor(R.color.key_flag_red);
|
||||
|
||||
mActionEncryptFile.setVisibility(View.GONE);
|
||||
mActionEncryptText.setVisibility(View.GONE);
|
||||
mActionNfc.setVisibility(View.GONE);
|
||||
mActionEncryptFile.setVisibility(View.INVISIBLE);
|
||||
mActionEncryptText.setVisibility(View.INVISIBLE);
|
||||
mActionNfc.setVisibility(View.INVISIBLE);
|
||||
mFab.setVisibility(View.GONE);
|
||||
mQrCodeLayout.setVisibility(View.GONE);
|
||||
} else if (mIsSecret) {
|
||||
@@ -814,15 +874,15 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
mQrCodeLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
// and place leftOf qr code
|
||||
RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
|
||||
mName.getLayoutParams();
|
||||
// remove right margin
|
||||
nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
nameParams.setMarginEnd(0);
|
||||
}
|
||||
nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
|
||||
mName.setLayoutParams(nameParams);
|
||||
// RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
|
||||
// mName.getLayoutParams();
|
||||
// // remove right margin
|
||||
// nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
|
||||
// if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
// nameParams.setMarginEnd(0);
|
||||
// }
|
||||
// nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
|
||||
// mName.setLayoutParams(nameParams);
|
||||
|
||||
RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams)
|
||||
mStatusText.getLayoutParams();
|
||||
@@ -844,7 +904,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
}
|
||||
mFab.setVisibility(View.VISIBLE);
|
||||
// noinspection deprecation (no getDrawable with theme at current minApi level 15!)
|
||||
mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
|
||||
mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
|
||||
} else {
|
||||
mActionEncryptFile.setVisibility(View.VISIBLE);
|
||||
mActionEncryptText.setVisibility(View.VISIBLE);
|
||||
@@ -872,22 +932,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
}
|
||||
|
||||
if (mPreviousColor == 0 || mPreviousColor == color) {
|
||||
mStatusBar.setBackgroundColor(getStatusBarBackgroundColor(color));
|
||||
mBigToolbar.setBackgroundColor(color);
|
||||
mAppBarLayout.setBackgroundColor(color);
|
||||
mCollapsingToolbarLayout.setContentScrimColor(color);
|
||||
mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color));
|
||||
mPreviousColor = color;
|
||||
} else {
|
||||
ObjectAnimator colorFade1 =
|
||||
ObjectAnimator.ofObject(mStatusBar, "backgroundColor",
|
||||
new ArgbEvaluator(), mPreviousColor,
|
||||
getStatusBarBackgroundColor(color));
|
||||
ObjectAnimator colorFade2 =
|
||||
ObjectAnimator.ofObject(mBigToolbar, "backgroundColor",
|
||||
ObjectAnimator colorFade =
|
||||
ObjectAnimator.ofObject(mAppBarLayout, "backgroundColor",
|
||||
new ArgbEvaluator(), mPreviousColor, color);
|
||||
mCollapsingToolbarLayout.setContentScrimColor(color);
|
||||
mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color));
|
||||
|
||||
colorFade1.setDuration(1200);
|
||||
colorFade2.setDuration(1200);
|
||||
colorFade1.start();
|
||||
colorFade2.start();
|
||||
colorFade.setDuration(1200);
|
||||
colorFade.start();
|
||||
mPreviousColor = color;
|
||||
}
|
||||
|
||||
@@ -963,4 +1020,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements
|
||||
public static final int TAB_IDENTITIES = 1;
|
||||
public static final int TAB_SUBKEYS = 2;
|
||||
public static final int TAB_CERTS = 3;
|
||||
public static final int TAB_KEYBASE = 4;
|
||||
|
||||
// view
|
||||
private ViewPager mViewPager;
|
||||
@@ -140,11 +139,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements
|
||||
adapter.addTab(ViewKeyAdvCertsFragment.class,
|
||||
certsBundle, getString(R.string.key_view_tab_certs));
|
||||
|
||||
Bundle trustBundle = new Bundle();
|
||||
trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri);
|
||||
adapter.addTab(ViewKeyTrustFragment.class,
|
||||
trustBundle, getString(R.string.key_view_tab_keybase));
|
||||
|
||||
// update layout after operations
|
||||
mSlidingTabLayout.setViewPager(mViewPager);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements
|
||||
case LOADER_ID_USER_IDS: {
|
||||
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri,
|
||||
UserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -18,52 +18,73 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.transition.Fade;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionInflater;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.*;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ViewTreeObserver.OnPreDrawListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
public class ViewKeyFragment extends LoaderFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
public static final String ARG_POSTPONE_TYPE = "postpone_type";
|
||||
|
||||
private ListView mUserIds;
|
||||
//private ListView mLinkedSystemContact;
|
||||
|
||||
boolean mIsSecret = false;
|
||||
enum PostponeType {
|
||||
NONE, LINKED;
|
||||
}
|
||||
|
||||
CardView mSystemContactCard;
|
||||
LinearLayout mSystemContactLayout;
|
||||
ImageView mSystemContactPicture;
|
||||
TextView mSystemContactName;
|
||||
boolean mIsSecret = false;
|
||||
|
||||
private static final int LOADER_ID_UNIFIED = 0;
|
||||
private static final int LOADER_ID_USER_IDS = 1;
|
||||
private static final int LOADER_ID_LINKED_CONTACT = 2;
|
||||
private static final int LOADER_ID_LINKED_IDS = 3;
|
||||
|
||||
private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID
|
||||
= "loader_linked_contact_master_key_id";
|
||||
@@ -71,16 +92,29 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
= "loader_linked_contact_is_secret";
|
||||
|
||||
private UserIdsAdapter mUserIdsAdapter;
|
||||
private LinkedIdsAdapter mLinkedIdsAdapter;
|
||||
|
||||
private Uri mDataUri;
|
||||
private PostponeType mPostponeType;
|
||||
|
||||
private CardView mSystemContactCard;
|
||||
private LinearLayout mSystemContactLayout;
|
||||
private ImageView mSystemContactPicture;
|
||||
private TextView mSystemContactName;
|
||||
|
||||
private ListView mLinkedIds;
|
||||
private CardView mLinkedIdsCard;
|
||||
private byte[] mFingerprint;
|
||||
private TextView mLinkedIdsExpander;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ViewKeyFragment newInstance(Uri dataUri) {
|
||||
public static ViewKeyFragment newInstance(Uri dataUri, PostponeType postponeType) {
|
||||
ViewKeyFragment frag = new ViewKeyFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putInt(ARG_POSTPONE_TYPE, postponeType.ordinal());
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@@ -93,6 +127,11 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
View view = inflater.inflate(R.layout.view_key_fragment, getContainer());
|
||||
|
||||
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
|
||||
mLinkedIdsCard = (CardView) view.findViewById(R.id.card_linked_ids);
|
||||
|
||||
mLinkedIds = (ListView) view.findViewById(R.id.view_key_linked_ids);
|
||||
|
||||
mLinkedIdsExpander = (TextView) view.findViewById(R.id.view_key_linked_ids_expander);
|
||||
|
||||
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
@@ -100,6 +139,12 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
showUserIdInfo(position);
|
||||
}
|
||||
});
|
||||
mLinkedIds.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
showLinkedId(position);
|
||||
}
|
||||
});
|
||||
|
||||
mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card);
|
||||
mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout);
|
||||
@@ -109,6 +154,47 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
return root;
|
||||
}
|
||||
|
||||
private void showLinkedId(final int position) {
|
||||
final LinkedIdViewFragment frag;
|
||||
try {
|
||||
frag = mLinkedIdsAdapter.getLinkedIdFragment(mDataUri, position, mFingerprint);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Transition trans = TransitionInflater.from(getActivity())
|
||||
.inflateTransition(R.transition.linked_id_card_trans);
|
||||
// setSharedElementReturnTransition(trans);
|
||||
setExitTransition(new Fade());
|
||||
frag.setSharedElementEnterTransition(trans);
|
||||
}
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.add(R.id.view_key_fragment, frag)
|
||||
.hide(frag)
|
||||
.commit();
|
||||
|
||||
frag.setOnIdentityLoadedListener(new OnIdentityLoadedListener() {
|
||||
@Override
|
||||
public void onIdentityLoaded() {
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getFragmentManager().beginTransaction()
|
||||
.show(frag)
|
||||
.addSharedElement(mLinkedIdsCard, "card_linked_ids")
|
||||
.remove(ViewKeyFragment.this)
|
||||
.addToBackStack("linked_id")
|
||||
.commit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void showUserIdInfo(final int position) {
|
||||
if (!mIsSecret) {
|
||||
final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position);
|
||||
@@ -129,8 +215,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
* Hides card if no linked system contact exists. Sets name, picture
|
||||
* and onClickListener for the linked system contact's layout.
|
||||
* In the case of a secret key, "me" (own profile) contact details are loaded.
|
||||
*
|
||||
* @param contactId
|
||||
*/
|
||||
private void loadLinkedSystemContact(final long contactId) {
|
||||
// contact doesn't exist, stop
|
||||
@@ -188,7 +272,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
* ContactsContract.Contact table)
|
||||
*
|
||||
* @param contactId _ID for row in ContactsContract.Contacts table
|
||||
* @param context
|
||||
*/
|
||||
private void launchContactActivity(final long contactId, Context context) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
@@ -202,6 +285,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
mPostponeType = PostponeType.values()[getArguments().getInt(ARG_POSTPONE_TYPE, 0)];
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
getActivity().finish();
|
||||
@@ -225,12 +309,17 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
};
|
||||
|
||||
static final int INDEX_MASTER_KEY_ID = 1;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_USER_ID = 2;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_IS_REVOKED = 3;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_IS_EXPIRED = 4;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_VERIFIED = 5;
|
||||
static final int INDEX_HAS_ANY_SECRET = 6;
|
||||
static final int INDEX_FINGERPRINT = 7;
|
||||
@SuppressWarnings("unused")
|
||||
static final int INDEX_HAS_ENCRYPT = 8;
|
||||
|
||||
private static final String[] RAWCONTACT_PROJECTION = {
|
||||
@@ -246,21 +335,26 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
// TODO Is this loader the same as the one in the activity?
|
||||
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
setContentShown(false);
|
||||
|
||||
switch (id) {
|
||||
case LOADER_ID_UNIFIED: {
|
||||
setContentShown(false, false);
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
||||
}
|
||||
case LOADER_ID_USER_IDS:
|
||||
|
||||
case LOADER_ID_USER_IDS: {
|
||||
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
return LinkedIdsAdapter.createLoader(getActivity(), mDataUri);
|
||||
}
|
||||
|
||||
//we need a separate loader for linked contact to ensure refreshing on verification
|
||||
case LOADER_ID_LINKED_CONTACT: {
|
||||
@@ -310,19 +404,21 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
if (data.moveToFirst()) {
|
||||
|
||||
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
mFingerprint = data.getBlob(INDEX_FINGERPRINT);
|
||||
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
|
||||
// load user ids after we know if it's a secret key
|
||||
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
|
||||
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
// we need to load linked contact here to prevent lag introduced by loader
|
||||
// for the linked contact
|
||||
long contactId = ContactHelper.findContactId(
|
||||
getActivity().getContentResolver(),
|
||||
masterKeyId);
|
||||
loadLinkedSystemContact(contactId);
|
||||
if (Preferences.getPreferences(getActivity()).getExperimentalEnableLinkedIdentities()) {
|
||||
mLinkedIdsAdapter =
|
||||
new LinkedIdsAdapter(getActivity(), null, 0, mIsSecret, mLinkedIdsExpander);
|
||||
mLinkedIds.setAdapter(mLinkedIdsAdapter);
|
||||
getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
|
||||
}
|
||||
|
||||
|
||||
Bundle linkedContactData = new Bundle();
|
||||
linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId);
|
||||
@@ -336,10 +432,28 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
case LOADER_ID_USER_IDS: {
|
||||
setContentShown(true, false);
|
||||
mUserIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
mLinkedIdsAdapter.swapCursor(data);
|
||||
mLinkedIdsCard.setVisibility(mLinkedIdsAdapter.getCount() > 0 ? View.VISIBLE : View.GONE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mPostponeType == PostponeType.LINKED) {
|
||||
mLinkedIdsCard.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
|
||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
mLinkedIdsCard.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
getActivity().startPostponedEnterTransition();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LOADER_ID_LINKED_CONTACT: {
|
||||
if (data.moveToFirst()) {// if we have a linked contact
|
||||
long contactId = data.getLong(INDEX_CONTACT_ID);
|
||||
@@ -349,7 +463,6 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
}
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,6 +476,11 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
mUserIdsAdapter.swapCursor(null);
|
||||
break;
|
||||
}
|
||||
case LOADER_ID_LINKED_IDS: {
|
||||
mLinkedIdsCard.setVisibility(View.GONE);
|
||||
mLinkedIdsAdapter.swapCursor(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,14 +59,12 @@ import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
|
||||
public class ViewKeyTrustFragment extends LoaderFragment implements
|
||||
public class ViewKeyKeybaseFragment extends LoaderFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor>,
|
||||
CryptoOperationHelper.Callback<KeybaseVerificationParcel, KeybaseVerificationResult> {
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
|
||||
private View mStartSearch;
|
||||
private TextView mTrustReadout;
|
||||
private TextView mReportHeader;
|
||||
private TableLayout mProofListing;
|
||||
private LayoutInflater mInflater;
|
||||
@@ -86,15 +84,25 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
||||
private CryptoOperationHelper<KeybaseVerificationParcel, KeybaseVerificationResult>
|
||||
mKeybaseOpHelper;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ViewKeyKeybaseFragment newInstance(Uri dataUri) {
|
||||
ViewKeyKeybaseFragment frag = new ViewKeyKeybaseFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer());
|
||||
mInflater = inflater;
|
||||
|
||||
mTrustReadout = (TextView) view.findViewById(R.id.view_key_trust_readout);
|
||||
mStartSearch = view.findViewById(R.id.view_key_trust_search_cloud);
|
||||
mStartSearch.setEnabled(false);
|
||||
mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative);
|
||||
mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list);
|
||||
mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header);
|
||||
@@ -157,85 +165,47 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
||||
}
|
||||
|
||||
boolean nothingSpecial = true;
|
||||
StringBuilder message = new StringBuilder();
|
||||
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
if (data.moveToFirst()) {
|
||||
|
||||
if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) {
|
||||
message.append(getString(R.string.key_trust_it_is_yours)).append("\n");
|
||||
nothingSpecial = false;
|
||||
} else if (data.getInt(INDEX_VERIFIED) != 0) {
|
||||
message.append(getString(R.string.key_trust_already_verified)).append("\n");
|
||||
nothingSpecial = false;
|
||||
}
|
||||
|
||||
// If this key is revoked, don’t trust it!
|
||||
if (data.getInt(INDEX_TRUST_IS_REVOKED) != 0) {
|
||||
message.append(getString(R.string.key_trust_revoked)).
|
||||
append(getString(R.string.key_trust_old_keys));
|
||||
|
||||
nothingSpecial = false;
|
||||
} else {
|
||||
if (data.getInt(INDEX_TRUST_IS_EXPIRED) != 0) {
|
||||
|
||||
// if expired, don’t trust it!
|
||||
message.append(getString(R.string.key_trust_expired)).
|
||||
append(getString(R.string.key_trust_old_keys));
|
||||
|
||||
nothingSpecial = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (nothingSpecial) {
|
||||
message.append(getString(R.string.key_trust_maybe));
|
||||
}
|
||||
|
||||
final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT);
|
||||
final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp);
|
||||
if (fingerprint != null) {
|
||||
|
||||
mStartSearch.setEnabled(true);
|
||||
mStartSearch.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
final Preferences.ProxyPrefs proxyPrefs =
|
||||
Preferences.getPreferences(getActivity()).getProxyPrefs();
|
||||
|
||||
OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
|
||||
@Override
|
||||
public void onOrbotStarted() {
|
||||
mStartSearch.setEnabled(false);
|
||||
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNeutralButton() {
|
||||
mStartSearch.setEnabled(false);
|
||||
new DescribeKey(ParcelableProxy.getForNoProxy())
|
||||
.execute(fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) {
|
||||
mStartSearch.setEnabled(false);
|
||||
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
startSearch(fingerprint);
|
||||
}
|
||||
|
||||
mTrustReadout.setText(message);
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
private void startSearch(final String fingerprint) {
|
||||
final Preferences.ProxyPrefs proxyPrefs =
|
||||
Preferences.getPreferences(getActivity()).getProxyPrefs();
|
||||
|
||||
OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
|
||||
@Override
|
||||
public void onOrbotStarted() {
|
||||
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNeutralButton() {
|
||||
new DescribeKey(ParcelableProxy.getForNoProxy())
|
||||
.execute(fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) {
|
||||
new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
||||
* We need to make sure we are no longer using it.
|
||||
@@ -299,17 +269,16 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
||||
return new ResultPage(getString(R.string.key_trust_results_prefix), proofList);
|
||||
}
|
||||
|
||||
private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks,String proofType){
|
||||
private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks, String proofType) {
|
||||
//Formatting SpannableStringBuilder with String.format() causes the links to stop working.
|
||||
//This method is to insert the links while reserving the links
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
||||
ssb.append(proofType);
|
||||
if(proofType.contains("%s")){
|
||||
if (proofType.contains("%s")) {
|
||||
int i = proofType.indexOf("%s");
|
||||
ssb.replace(i,i+2,proofLinks);
|
||||
}
|
||||
else ssb.append(proofLinks);
|
||||
ssb.replace(i, i + 2, proofLinks);
|
||||
} else ssb.append(proofLinks);
|
||||
|
||||
return ssb;
|
||||
}
|
||||
@@ -343,7 +312,6 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
||||
result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence);
|
||||
}
|
||||
|
||||
mStartSearch.setVisibility(View.GONE);
|
||||
mReportHeader.setVisibility(View.VISIBLE);
|
||||
mProofListing.setVisibility(View.VISIBLE);
|
||||
mReportHeader.setText(result.mHeader);
|
||||
@@ -358,22 +326,35 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
||||
text.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
mProofListing.addView(row);
|
||||
}
|
||||
|
||||
// mSearchReport.loadDataWithBaseURL("file:///android_res/drawable/", s, "text/html", "UTF-8", null);
|
||||
}
|
||||
}
|
||||
|
||||
private String getProofNarrative(int proofType) {
|
||||
int stringIndex;
|
||||
switch (proofType) {
|
||||
case Proof.PROOF_TYPE_TWITTER: stringIndex = R.string.keybase_narrative_twitter; break;
|
||||
case Proof.PROOF_TYPE_GITHUB: stringIndex = R.string.keybase_narrative_github; break;
|
||||
case Proof.PROOF_TYPE_DNS: stringIndex = R.string.keybase_narrative_dns; break;
|
||||
case Proof.PROOF_TYPE_WEB_SITE: stringIndex = R.string.keybase_narrative_web_site; break;
|
||||
case Proof.PROOF_TYPE_HACKERNEWS: stringIndex = R.string.keybase_narrative_hackernews; break;
|
||||
case Proof.PROOF_TYPE_COINBASE: stringIndex = R.string.keybase_narrative_coinbase; break;
|
||||
case Proof.PROOF_TYPE_REDDIT: stringIndex = R.string.keybase_narrative_reddit; break;
|
||||
default: stringIndex = R.string.keybase_narrative_unknown;
|
||||
case Proof.PROOF_TYPE_TWITTER:
|
||||
stringIndex = R.string.keybase_narrative_twitter;
|
||||
break;
|
||||
case Proof.PROOF_TYPE_GITHUB:
|
||||
stringIndex = R.string.keybase_narrative_github;
|
||||
break;
|
||||
case Proof.PROOF_TYPE_DNS:
|
||||
stringIndex = R.string.keybase_narrative_dns;
|
||||
break;
|
||||
case Proof.PROOF_TYPE_WEB_SITE:
|
||||
stringIndex = R.string.keybase_narrative_web_site;
|
||||
break;
|
||||
case Proof.PROOF_TYPE_HACKERNEWS:
|
||||
stringIndex = R.string.keybase_narrative_hackernews;
|
||||
break;
|
||||
case Proof.PROOF_TYPE_COINBASE:
|
||||
stringIndex = R.string.keybase_narrative_coinbase;
|
||||
break;
|
||||
case Proof.PROOF_TYPE_REDDIT:
|
||||
stringIndex = R.string.keybase_narrative_reddit;
|
||||
break;
|
||||
default:
|
||||
stringIndex = R.string.keybase_narrative_unknown;
|
||||
}
|
||||
return getActivity().getString(stringIndex);
|
||||
}
|
||||
@@ -390,14 +371,22 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
||||
// which proofs do we have working verifiers for?
|
||||
private boolean haveProofFor(int proofType) {
|
||||
switch (proofType) {
|
||||
case Proof.PROOF_TYPE_TWITTER: return true;
|
||||
case Proof.PROOF_TYPE_GITHUB: return true;
|
||||
case Proof.PROOF_TYPE_DNS: return true;
|
||||
case Proof.PROOF_TYPE_WEB_SITE: return true;
|
||||
case Proof.PROOF_TYPE_HACKERNEWS: return true;
|
||||
case Proof.PROOF_TYPE_COINBASE: return true;
|
||||
case Proof.PROOF_TYPE_REDDIT: return true;
|
||||
default: return false;
|
||||
case Proof.PROOF_TYPE_TWITTER:
|
||||
return true;
|
||||
case Proof.PROOF_TYPE_GITHUB:
|
||||
return true;
|
||||
case Proof.PROOF_TYPE_DNS:
|
||||
return true;
|
||||
case Proof.PROOF_TYPE_WEB_SITE:
|
||||
return true;
|
||||
case Proof.PROOF_TYPE_HACKERNEWS:
|
||||
return true;
|
||||
case Proof.PROOF_TYPE_COINBASE:
|
||||
return true;
|
||||
case Proof.PROOF_TYPE_REDDIT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
@@ -193,9 +194,9 @@ public class KeyAdapter extends CursorAdapter {
|
||||
String dateTime = DateUtils.formatDateTime(context,
|
||||
item.mCreation.getTime(),
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
| DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_SHOW_YEAR
|
||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||
|
||||
mCreationDate.setText(context.getString(R.string.label_key_created,
|
||||
dateTime));
|
||||
mCreationDate.setTextColor(textColor);
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* 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.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||
import org.sufficientlysecure.keychain.util.FilterCursorWrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public class LinkedIdsAdapter extends UserAttributesAdapter {
|
||||
private final boolean mIsSecret;
|
||||
protected LayoutInflater mInflater;
|
||||
WeakHashMap<Integer,UriAttribute> mLinkedIdentityCache = new WeakHashMap<>();
|
||||
|
||||
private Cursor mUnfilteredCursor;
|
||||
|
||||
private TextView mExpander;
|
||||
|
||||
public LinkedIdsAdapter(Context context, Cursor c, int flags,
|
||||
boolean isSecret, TextView expander) {
|
||||
super(context, c, flags);
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mIsSecret = isSecret;
|
||||
|
||||
if (expander != null) {
|
||||
expander.setVisibility(View.GONE);
|
||||
/* don't show an expander (maybe in some sort of advanced view?)
|
||||
mExpander = expander;
|
||||
mExpander.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
showUnfiltered();
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor cursor) {
|
||||
if (cursor == null) {
|
||||
mUnfilteredCursor = null;
|
||||
return super.swapCursor(null);
|
||||
}
|
||||
mUnfilteredCursor = cursor;
|
||||
FilterCursorWrapper filteredCursor = new FilterCursorWrapper(cursor) {
|
||||
@Override
|
||||
public boolean isVisible(Cursor cursor) {
|
||||
UriAttribute id = getItemAtPosition(cursor);
|
||||
return id instanceof LinkedAttribute;
|
||||
}
|
||||
};
|
||||
|
||||
if (mExpander != null) {
|
||||
int hidden = filteredCursor.getHiddenCount();
|
||||
if (hidden == 0) {
|
||||
mExpander.setVisibility(View.GONE);
|
||||
} else {
|
||||
mExpander.setVisibility(View.VISIBLE);
|
||||
mExpander.setText(mContext.getResources().getQuantityString(
|
||||
R.plurals.linked_id_expand, hidden));
|
||||
}
|
||||
}
|
||||
|
||||
return super.swapCursor(filteredCursor);
|
||||
}
|
||||
|
||||
private void showUnfiltered() {
|
||||
mExpander.setVisibility(View.GONE);
|
||||
super.swapCursor(mUnfilteredCursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
|
||||
ViewHolder holder = (ViewHolder) view.getTag();
|
||||
|
||||
if (!mIsSecret) {
|
||||
int isVerified = cursor.getInt(INDEX_VERIFIED);
|
||||
switch (isVerified) {
|
||||
case Certs.VERIFIED_SECRET:
|
||||
KeyFormattingUtils.setStatusImage(mContext, holder.vVerified,
|
||||
null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
case Certs.VERIFIED_SELF:
|
||||
KeyFormattingUtils.setStatusImage(mContext, holder.vVerified,
|
||||
null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
default:
|
||||
KeyFormattingUtils.setStatusImage(mContext, holder.vVerified,
|
||||
null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UriAttribute id = getItemAtPosition(cursor);
|
||||
holder.setData(mContext, id);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
view.setTransitionName(id.mUri.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public UriAttribute getItemAtPosition(Cursor cursor) {
|
||||
int rank = cursor.getInt(INDEX_RANK);
|
||||
Log.d(Constants.TAG, "requested rank: " + rank);
|
||||
|
||||
UriAttribute ret = mLinkedIdentityCache.get(rank);
|
||||
if (ret != null) {
|
||||
Log.d(Constants.TAG, "cached!");
|
||||
return ret;
|
||||
}
|
||||
Log.d(Constants.TAG, "not cached!");
|
||||
|
||||
try {
|
||||
byte[] data = cursor.getBlob(INDEX_ATTRIBUTE_DATA);
|
||||
ret = LinkedAttribute.fromAttributeData(data);
|
||||
mLinkedIdentityCache.put(rank, ret);
|
||||
return ret;
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "could not read linked identity subpacket data", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriAttribute getItem(int position) {
|
||||
Cursor cursor = getCursor();
|
||||
cursor.moveToPosition(position);
|
||||
return getItemAtPosition(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View v = mInflater.inflate(R.layout.linked_id_item, null);
|
||||
ViewHolder holder = new ViewHolder(v);
|
||||
v.setTag(holder);
|
||||
return v;
|
||||
}
|
||||
|
||||
// don't show revoked user ids, irrelevant for average users
|
||||
public static final String LINKED_IDS_WHERE = UserPackets.IS_REVOKED + " = 0";
|
||||
|
||||
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
||||
Uri baseUri = UserPackets.buildLinkedIdsUri(dataUri);
|
||||
return new CursorLoader(activity, baseUri,
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, LINKED_IDS_WHERE, null, null);
|
||||
}
|
||||
|
||||
public LinkedIdViewFragment getLinkedIdFragment(Uri baseUri,
|
||||
int position, byte[] fingerprint) throws IOException {
|
||||
Cursor c = getCursor();
|
||||
c.moveToPosition(position);
|
||||
int rank = c.getInt(UserIdsAdapter.INDEX_RANK);
|
||||
|
||||
Uri dataUri = UserPackets.buildLinkedIdsUri(baseUri);
|
||||
return LinkedIdViewFragment.newInstance(dataUri, rank, mIsSecret, fingerprint);
|
||||
}
|
||||
|
||||
public static class ViewHolder {
|
||||
final public ImageView vVerified;
|
||||
final public ImageView vIcon;
|
||||
final public TextView vTitle;
|
||||
final public TextView vComment;
|
||||
|
||||
public ViewHolder(View view) {
|
||||
vVerified = (ImageView) view.findViewById(R.id.linked_id_certified_icon);
|
||||
vIcon = (ImageView) view.findViewById(R.id.linked_id_type_icon);
|
||||
vTitle = (TextView) view.findViewById(R.id.linked_id_title);
|
||||
vComment = (TextView) view.findViewById(R.id.linked_id_comment);
|
||||
}
|
||||
|
||||
public void setData(Context context, UriAttribute id) {
|
||||
|
||||
vTitle.setText(id.getDisplayTitle(context));
|
||||
|
||||
String comment = id.getDisplayComment(context);
|
||||
if (comment != null) {
|
||||
vComment.setVisibility(View.VISIBLE);
|
||||
vComment.setText(comment);
|
||||
} else {
|
||||
vComment.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
vIcon.setImageResource(id.getDisplayIcon());
|
||||
|
||||
}
|
||||
|
||||
public void seekAttention() {
|
||||
ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
|
||||
anim.setStartDelay(200);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
|
||||
|
||||
public class LinkedIdsCertAdapter extends CursorAdapter {
|
||||
|
||||
public static final String[] USER_CERTS_PROJECTION = new String[]{
|
||||
UserPackets._ID,
|
||||
UserPackets.TYPE,
|
||||
UserPackets.USER_ID,
|
||||
UserPackets.ATTRIBUTE_DATA,
|
||||
UserPackets.RANK,
|
||||
UserPackets.VERIFIED,
|
||||
UserPackets.IS_PRIMARY,
|
||||
UserPackets.IS_REVOKED
|
||||
};
|
||||
protected static final int INDEX_ID = 0;
|
||||
protected static final int INDEX_TYPE = 1;
|
||||
protected static final int INDEX_USER_ID = 2;
|
||||
protected static final int INDEX_ATTRIBUTE_DATA = 3;
|
||||
protected static final int INDEX_RANK = 4;
|
||||
protected static final int INDEX_VERIFIED = 5;
|
||||
protected static final int INDEX_IS_PRIMARY = 6;
|
||||
protected static final int INDEX_IS_REVOKED = 7;
|
||||
|
||||
public LinkedIdsCertAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
||||
Uri baseUri = UserPackets.buildLinkedIdsUri(dataUri);
|
||||
return new CursorLoader(activity, baseUri,
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -171,7 +171,7 @@ public class MultiUserIdsAdapter extends CursorAdapter {
|
||||
|
||||
CertifyAction action = actions.get(keyId);
|
||||
if (actions.get(keyId) == null) {
|
||||
actions.put(keyId, new CertifyAction(keyId, uids));
|
||||
actions.put(keyId, new CertifyAction(keyId, uids, null));
|
||||
} else {
|
||||
action.mUserIds.addAll(uids);
|
||||
}
|
||||
|
||||
@@ -137,9 +137,9 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter {
|
||||
String dateTime = DateUtils.formatDateTime(context,
|
||||
cursor.getLong(mIndexCreation) * 1000,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
| DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_SHOW_YEAR
|
||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||
|
||||
h.creation.setText(context.getString(R.string.label_key_created, dateTime));
|
||||
h.creation.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
|
||||
@@ -8,22 +8,24 @@ import android.view.View;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
|
||||
public abstract class UserAttributesAdapter extends CursorAdapter {
|
||||
public static final String[] USER_IDS_PROJECTION = new String[]{
|
||||
public static final String[] USER_PACKETS_PROJECTION = new String[]{
|
||||
UserPackets._ID,
|
||||
UserPackets.TYPE,
|
||||
UserPackets.USER_ID,
|
||||
UserPackets.ATTRIBUTE_DATA,
|
||||
UserPackets.RANK,
|
||||
UserPackets.VERIFIED,
|
||||
UserPackets.IS_PRIMARY,
|
||||
UserPackets.IS_REVOKED
|
||||
};
|
||||
protected static final int INDEX_ID = 0;
|
||||
protected static final int INDEX_TYPE = 1;
|
||||
protected static final int INDEX_USER_ID = 2;
|
||||
protected static final int INDEX_RANK = 3;
|
||||
protected static final int INDEX_VERIFIED = 4;
|
||||
protected static final int INDEX_IS_PRIMARY = 5;
|
||||
protected static final int INDEX_IS_REVOKED = 6;
|
||||
public static final int INDEX_ID = 0;
|
||||
public static final int INDEX_TYPE = 1;
|
||||
public static final int INDEX_USER_ID = 2;
|
||||
public static final int INDEX_ATTRIBUTE_DATA = 3;
|
||||
public static final int INDEX_RANK = 4;
|
||||
public static final int INDEX_VERIFIED = 5;
|
||||
public static final int INDEX_IS_PRIMARY = 6;
|
||||
public static final int INDEX_IS_REVOKED = 7;
|
||||
|
||||
public UserAttributesAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
@@ -46,4 +48,5 @@ public abstract class UserAttributesAdapter extends CursorAdapter {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getInt(INDEX_VERIFIED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ public class UserIdsAdapter extends UserAttributesAdapter {
|
||||
public static CursorLoader createLoader(Activity activity, Uri dataUri) {
|
||||
Uri baseUri = UserPackets.buildUserIdsUri(dataUri);
|
||||
return new CursorLoader(activity, baseUri,
|
||||
UserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_WHERE, null, null);
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION, USER_IDS_WHERE, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,7 @@ public abstract class BaseActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
KeyserverSyncAdapterService.cancelUpdates(this);
|
||||
|
||||
if (mThemeChanger.changeTheme()) {
|
||||
Intent intent = getIntent();
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
@@ -50,17 +50,21 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
* @see KeychainService
|
||||
*
|
||||
*/
|
||||
abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
|
||||
public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult>
|
||||
extends Fragment implements CryptoOperationHelper.Callback<T, S> {
|
||||
|
||||
final private CryptoOperationHelper<T, S> mOperationHelper;
|
||||
|
||||
public CryptoOperationFragment() {
|
||||
mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing);
|
||||
}
|
||||
|
||||
public CryptoOperationFragment(Integer initialProgressMsg) {
|
||||
mOperationHelper = new CryptoOperationHelper<>(1, this, this, initialProgressMsg);
|
||||
}
|
||||
|
||||
public CryptoOperationFragment() {
|
||||
mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing);
|
||||
public CryptoOperationFragment(int id, Integer initialProgressMsg) {
|
||||
mOperationHelper = new CryptoOperationHelper<>(id, this, this, initialProgressMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
@@ -70,9 +72,11 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
// particular helper. a request code looks as follows:
|
||||
// (id << 9) + (1<<8) + REQUEST_CODE_X
|
||||
// that is, starting from LSB, there are 8 bits request code, 1
|
||||
// fixed bit set, then 7 bit operator-id code. the first two
|
||||
// summands are stored in the mId for easy operation.
|
||||
private final int mId;
|
||||
// fixed bit set, then 7 bit helper-id code. the first two
|
||||
// summands are stored in the mHelperId for easy operation.
|
||||
private final int mHelperId;
|
||||
// bitmask for helperId is everything except the least 8 bits
|
||||
public static final int HELPER_ID_BITMASK = ~0xff;
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 1;
|
||||
public static final int REQUEST_CODE_NFC = 2;
|
||||
@@ -92,7 +96,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
*/
|
||||
public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback,
|
||||
Integer progressMessageString) {
|
||||
mId = (id << 9) + (1<<8);
|
||||
mHelperId = (id << 9) + (1<<8);
|
||||
mActivity = activity;
|
||||
mUseFragment = false;
|
||||
mCallback = callback;
|
||||
@@ -103,7 +107,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
* if OperationHelper is being integrated into a fragment
|
||||
*/
|
||||
public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) {
|
||||
mId = (id << 9) + (1<<8);
|
||||
mHelperId = (id << 9) + (1<<8);
|
||||
mFragment = fragment;
|
||||
mUseFragment = true;
|
||||
mProgressMessageResource = progressMessageString;
|
||||
@@ -162,9 +166,9 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
|
||||
protected void startActivityForResult(Intent intent, int requestCode) {
|
||||
if (mUseFragment) {
|
||||
mFragment.startActivityForResult(intent, mId + requestCode);
|
||||
mFragment.startActivityForResult(intent, mHelperId + requestCode);
|
||||
} else {
|
||||
mActivity.startActivityForResult(intent, mId + requestCode);
|
||||
mActivity.startActivityForResult(intent, mHelperId + requestCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,13 +180,13 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
Log.d(Constants.TAG, "received activity result in OperationHelper");
|
||||
|
||||
if ((requestCode & mId) != mId) {
|
||||
if ((requestCode & HELPER_ID_BITMASK) != mHelperId) {
|
||||
// this wasn't meant for us to handle
|
||||
return false;
|
||||
}
|
||||
Log.d(Constants.TAG, "handling activity result in OperationHelper");
|
||||
// filter out mId from requestCode
|
||||
requestCode ^= mId;
|
||||
// filter out mHelperId from requestCode
|
||||
requestCode ^= mHelperId;
|
||||
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
mCallback.onCryptoOperationCancelled();
|
||||
@@ -313,7 +317,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
||||
}
|
||||
|
||||
public void cryptoOperation() {
|
||||
cryptoOperation(new CryptoInputParcel());
|
||||
cryptoOperation(new CryptoInputParcel(new Date()));
|
||||
}
|
||||
|
||||
public void onHandleResult(OperationResult result) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
@@ -119,14 +120,19 @@ public class FileDialogFragment extends DialogFragment {
|
||||
mFilename = (EditText) view.findViewById(R.id.input);
|
||||
mFilename.setText(mFile.getName());
|
||||
mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// only .asc or .gpg files
|
||||
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
|
||||
// or gpg types!
|
||||
FileHelper.openFile(FileDialogFragment.this, Uri.fromFile(mFile), "*/*", REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
mBrowse.setVisibility(View.GONE);
|
||||
} else {
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// only .asc or .gpg files
|
||||
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
|
||||
// or gpg types!
|
||||
FileHelper.saveDocumentKitKat(
|
||||
FileDialogFragment.this, "*/*", mFile.getName(), REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
|
||||
if (checkboxText == null) {
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
package org.sufficientlysecure.keychain.ui.linked;
|
||||
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
|
||||
public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragment {
|
||||
|
||||
protected LinkedIdWizard mLinkedIdWizard;
|
||||
|
||||
private ImageView mVerifyImage;
|
||||
private TextView mVerifyStatus;
|
||||
private ViewAnimator mVerifyAnimator;
|
||||
|
||||
// This is a resource, set AFTER it has been verified
|
||||
LinkedTokenResource mVerifiedResource = null;
|
||||
private ViewAnimator mVerifyButtonAnimator;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mLinkedIdWizard = (LinkedIdWizard) getActivity();
|
||||
}
|
||||
|
||||
protected abstract View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
|
||||
|
||||
@Override @NonNull
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = newView(inflater, container, savedInstanceState);
|
||||
|
||||
View nextButton = view.findViewById(R.id.next_button);
|
||||
if (nextButton != null) {
|
||||
nextButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cryptoOperation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||
}
|
||||
});
|
||||
|
||||
mVerifyAnimator = (ViewAnimator) view.findViewById(R.id.verify_progress);
|
||||
mVerifyImage = (ImageView) view.findViewById(R.id.verify_image);
|
||||
mVerifyStatus = (TextView) view.findViewById(R.id.verify_status);
|
||||
mVerifyButtonAnimator = (ViewAnimator) view.findViewById(R.id.verify_buttons);
|
||||
|
||||
view.findViewById(R.id.button_verify).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
proofVerify();
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.button_retry).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
proofVerify();
|
||||
}
|
||||
});
|
||||
|
||||
setVerifyProgress(false, null);
|
||||
mVerifyStatus.setText(R.string.linked_verify_pending);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
abstract LinkedTokenResource getResource(OperationLog log);
|
||||
|
||||
private void setVerifyProgress(boolean on, Boolean success) {
|
||||
if (success == null) {
|
||||
mVerifyStatus.setText(R.string.linked_verifying);
|
||||
displayButton(on ? 2 : 0);
|
||||
} else if (success) {
|
||||
mVerifyStatus.setText(R.string.linked_verify_success);
|
||||
mVerifyImage.setImageResource(R.drawable.status_signature_verified_cutout_24dp);
|
||||
mVerifyImage.setColorFilter(getResources().getColor(R.color.android_green_dark),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
displayButton(2);
|
||||
} else {
|
||||
mVerifyStatus.setText(R.string.linked_verify_error);
|
||||
mVerifyImage.setImageResource(R.drawable.status_signature_unknown_cutout_24dp);
|
||||
mVerifyImage.setColorFilter(getResources().getColor(R.color.android_red_dark),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
displayButton(1);
|
||||
}
|
||||
mVerifyAnimator.setDisplayedChild(on ? 1 : 0);
|
||||
}
|
||||
|
||||
public void displayButton(int button) {
|
||||
if (mVerifyButtonAnimator.getDisplayedChild() == button) {
|
||||
return;
|
||||
}
|
||||
mVerifyButtonAnimator.setDisplayedChild(button);
|
||||
}
|
||||
|
||||
protected void proofVerify() {
|
||||
setVerifyProgress(true, null);
|
||||
|
||||
new AsyncTask<Void,Void,LinkedVerifyResult>() {
|
||||
|
||||
@Override
|
||||
protected LinkedVerifyResult doInBackground(Void... params) {
|
||||
long timer = System.currentTimeMillis();
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
LinkedTokenResource resource = getResource(log);
|
||||
if (resource == null) {
|
||||
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
LinkedVerifyResult result = resource.verify(getActivity(), mLinkedIdWizard.mFingerprint);
|
||||
|
||||
// ux flow: this operation should take at last a second
|
||||
timer = System.currentTimeMillis() -timer;
|
||||
if (timer < 1000) try {
|
||||
Thread.sleep(1000 -timer);
|
||||
} catch (InterruptedException e) {
|
||||
// never mind
|
||||
}
|
||||
|
||||
if (result.success()) {
|
||||
mVerifiedResource = resource;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(LinkedVerifyResult result) {
|
||||
super.onPostExecute(result);
|
||||
if (result.success()) {
|
||||
setVerifyProgress(false, true);
|
||||
} else {
|
||||
setVerifyProgress(false, false);
|
||||
// on error, show error message
|
||||
result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation() {
|
||||
if (mVerifiedResource == null) {
|
||||
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
|
||||
.show(LinkedIdCreateFinalFragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
super.cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
if (mVerifiedResource == null) {
|
||||
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
|
||||
.show(LinkedIdCreateFinalFragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
super.cryptoOperation(cryptoInput);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Parcelable createOperationInput() {
|
||||
SaveKeyringParcel skp =
|
||||
new SaveKeyringParcel(mLinkedIdWizard.mMasterKeyId, mLinkedIdWizard.mFingerprint);
|
||||
|
||||
WrappedUserAttribute ua =
|
||||
LinkedAttribute.fromResource(mVerifiedResource).toUserAttribute();
|
||||
|
||||
skp.mAddUserAttribute.add(ua);
|
||||
|
||||
return skp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(OperationResult result) {
|
||||
// if bad -> display here!
|
||||
if (!result.success()) {
|
||||
result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(OperationResult result) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
* 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.linked;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Random;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Base64;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.BuildConfig;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.resources.GithubResource;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator;
|
||||
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator.Status;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
public class LinkedIdCreateGithubFragment extends CryptoOperationFragment<SaveKeyringParcel,EditKeyResult> {
|
||||
|
||||
public static final String ARG_GITHUB_COOKIE = "github_cookie";
|
||||
private Button mRetryButton;
|
||||
|
||||
enum State {
|
||||
IDLE, AUTH_PROCESS, AUTH_ERROR, POST_PROCESS, POST_ERROR, LID_PROCESS, LID_ERROR, DONE
|
||||
}
|
||||
|
||||
ViewAnimator mButtonContainer;
|
||||
|
||||
StatusIndicator mStatus1, mStatus2, mStatus3;
|
||||
|
||||
byte[] mFingerprint;
|
||||
long mMasterKeyId;
|
||||
private SaveKeyringParcel mSaveKeyringParcel;
|
||||
private TextView mLinkedIdTitle, mLinkedIdComment;
|
||||
private boolean mFinishOnStop;
|
||||
|
||||
public static LinkedIdCreateGithubFragment newInstance() {
|
||||
return new LinkedIdCreateGithubFragment();
|
||||
}
|
||||
|
||||
public LinkedIdCreateGithubFragment() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.linked_create_github_fragment, container, false);
|
||||
|
||||
mButtonContainer = (ViewAnimator) view.findViewById(R.id.button_container);
|
||||
|
||||
mStatus1 = (StatusIndicator) view.findViewById(R.id.linked_status_step1);
|
||||
mStatus2 = (StatusIndicator) view.findViewById(R.id.linked_status_step2);
|
||||
mStatus3 = (StatusIndicator) view.findViewById(R.id.linked_status_step3);
|
||||
|
||||
mRetryButton = (Button) view.findViewById(R.id.button_retry);
|
||||
|
||||
((ImageView) view.findViewById(R.id.linked_id_type_icon)).setImageResource(R.drawable.linked_github);
|
||||
((ImageView) view.findViewById(R.id.linked_id_certified_icon)).setImageResource(R.drawable.octo_link_24dp);
|
||||
mLinkedIdTitle = (TextView) view.findViewById(R.id.linked_id_title);
|
||||
mLinkedIdComment = (TextView) view.findViewById(R.id.linked_id_comment);
|
||||
|
||||
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LinkedIdWizard activity = (LinkedIdWizard) getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
activity.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
step1GetOAuthCode();
|
||||
// for animation testing
|
||||
// onCryptoOperationSuccess(null);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
LinkedIdWizard wizard = (LinkedIdWizard) getActivity();
|
||||
mFingerprint = wizard.mFingerprint;
|
||||
mMasterKeyId = wizard.mMasterKeyId;
|
||||
}
|
||||
|
||||
private void step1GetOAuthCode() {
|
||||
|
||||
setState(State.AUTH_PROCESS);
|
||||
|
||||
mButtonContainer.setDisplayedChild(1);
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
oAuthRequest("github.com/login/oauth/authorize", BuildConfig.GITHUB_CLIENT_ID, "gist");
|
||||
}
|
||||
}, 300);
|
||||
|
||||
}
|
||||
|
||||
private void showRetryForOAuth() {
|
||||
|
||||
mRetryButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
v.setOnClickListener(null);
|
||||
step1GetOAuthCode();
|
||||
}
|
||||
});
|
||||
mButtonContainer.setDisplayedChild(3);
|
||||
|
||||
}
|
||||
|
||||
private void step1GetOAuthToken() {
|
||||
|
||||
if (mOAuthCode == null) {
|
||||
setState(State.AUTH_ERROR);
|
||||
showRetryForOAuth();
|
||||
return;
|
||||
}
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String gistText = GithubResource.generate(activity, mFingerprint);
|
||||
|
||||
new AsyncTask<Void,Void,JSONObject>() {
|
||||
|
||||
Exception mException;
|
||||
|
||||
@Override
|
||||
protected JSONObject doInBackground(Void... dummy) {
|
||||
try {
|
||||
|
||||
JSONObject params = new JSONObject();
|
||||
params.put("client_id", BuildConfig.GITHUB_CLIENT_ID);
|
||||
params.put("client_secret", BuildConfig.GITHUB_CLIENT_SECRET);
|
||||
params.put("code", mOAuthCode);
|
||||
params.put("state", mOAuthState);
|
||||
|
||||
return jsonHttpRequest("https://github.com/login/oauth/access_token", params, null);
|
||||
|
||||
} catch (IOException | HttpResultException e) {
|
||||
mException = e;
|
||||
} catch (JSONException e) {
|
||||
throw new AssertionError("json error, this is a bug!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JSONObject result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
// we couldn't show an error anyways
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "response: " + result);
|
||||
|
||||
if (result == null || result.optString("access_token", null) == null) {
|
||||
setState(State.AUTH_ERROR);
|
||||
showRetryForOAuth();
|
||||
|
||||
if (result != null) {
|
||||
Notify.create(activity, R.string.linked_error_auth_failed, Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mException instanceof SocketTimeoutException) {
|
||||
Notify.create(activity, R.string.linked_error_timeout, Style.ERROR).show();
|
||||
} else if (mException instanceof HttpResultException) {
|
||||
Notify.create(activity, activity.getString(R.string.linked_error_http,
|
||||
((HttpResultException) mException).mResponse),
|
||||
Style.ERROR).show();
|
||||
} else if (mException instanceof IOException) {
|
||||
Notify.create(activity, R.string.linked_error_network, Style.ERROR).show();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
step2PostGist(result.optString("access_token"), gistText);
|
||||
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void step2PostGist(final String accessToken, final String gistText) {
|
||||
|
||||
setState(State.POST_PROCESS);
|
||||
|
||||
new AsyncTask<Void,Void,JSONObject>() {
|
||||
|
||||
Exception mException;
|
||||
|
||||
@Override
|
||||
protected JSONObject doInBackground(Void... dummy) {
|
||||
try {
|
||||
|
||||
long timer = System.currentTimeMillis();
|
||||
|
||||
JSONObject file = new JSONObject();
|
||||
file.put("content", gistText);
|
||||
|
||||
JSONObject files = new JSONObject();
|
||||
files.put("openpgp.txt", file);
|
||||
|
||||
JSONObject params = new JSONObject();
|
||||
params.put("public", true);
|
||||
params.put("description", getString(R.string.linked_gist_description));
|
||||
params.put("files", files);
|
||||
|
||||
JSONObject result = jsonHttpRequest("https://api.github.com/gists", params, accessToken);
|
||||
|
||||
// ux flow: this operation should take at last a second
|
||||
timer = System.currentTimeMillis() -timer;
|
||||
if (timer < 1000) try {
|
||||
Thread.sleep(1000 -timer);
|
||||
} catch (InterruptedException e) {
|
||||
// never mind
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (IOException | HttpResultException e) {
|
||||
mException = e;
|
||||
} catch (JSONException e) {
|
||||
throw new AssertionError("json error, this is a bug!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JSONObject result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
Log.d(Constants.TAG, "response: " + result);
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
// we couldn't show an error anyways
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
setState(State.POST_ERROR);
|
||||
showRetryForOAuth();
|
||||
|
||||
if (mException instanceof SocketTimeoutException) {
|
||||
Notify.create(activity, R.string.linked_error_timeout, Style.ERROR).show();
|
||||
} else if (mException instanceof HttpResultException) {
|
||||
Notify.create(activity, activity.getString(R.string.linked_error_http,
|
||||
((HttpResultException) mException).mResponse),
|
||||
Style.ERROR).show();
|
||||
} else if (mException instanceof IOException) {
|
||||
Notify.create(activity, R.string.linked_error_network, Style.ERROR).show();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
GithubResource resource;
|
||||
|
||||
try {
|
||||
String gistId = result.getString("id");
|
||||
JSONObject owner = result.getJSONObject("owner");
|
||||
String gistLogin = owner.getString("login");
|
||||
|
||||
URI uri = URI.create("https://gist.github.com/" + gistLogin + "/" + gistId);
|
||||
resource = GithubResource.create(uri);
|
||||
} catch (JSONException e) {
|
||||
setState(State.POST_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
View linkedItem = mButtonContainer.getChildAt(2);
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||
linkedItem.setTransitionName(resource.toUri().toString());
|
||||
}
|
||||
|
||||
// we only need authorization for this one operation, drop it afterwards
|
||||
revokeToken(accessToken);
|
||||
|
||||
step3EditKey(resource);
|
||||
}
|
||||
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void revokeToken(final String token) {
|
||||
|
||||
new AsyncTask<Void,Void,Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... dummy) {
|
||||
try {
|
||||
HttpsURLConnection nection = (HttpsURLConnection) new URL(
|
||||
"https://api.github.com/applications/" + BuildConfig.GITHUB_CLIENT_ID + "/tokens/" + token)
|
||||
.openConnection();
|
||||
nection.setRequestMethod("DELETE");
|
||||
String encoded = Base64.encodeToString(
|
||||
(BuildConfig.GITHUB_CLIENT_ID + ":" + BuildConfig.GITHUB_CLIENT_SECRET).getBytes(), Base64.DEFAULT);
|
||||
nection.setRequestProperty("Authorization", "Basic " + encoded);
|
||||
nection.connect();
|
||||
} catch (IOException e) {
|
||||
// nvm
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void step3EditKey(final GithubResource resource) {
|
||||
|
||||
// set item data while we're there
|
||||
{
|
||||
Context context = getActivity();
|
||||
mLinkedIdTitle.setText(resource.getDisplayTitle(context));
|
||||
mLinkedIdComment.setText(resource.getDisplayComment(context));
|
||||
}
|
||||
|
||||
setState(State.LID_PROCESS);
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
WrappedUserAttribute ua = LinkedAttribute.fromResource(resource).toUserAttribute();
|
||||
mSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint);
|
||||
mSaveKeyringParcel.mAddUserAttribute.add(ua);
|
||||
|
||||
cryptoOperation();
|
||||
|
||||
}
|
||||
}, 250);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SaveKeyringParcel createOperationInput() {
|
||||
// if this is null, the cryptoOperation silently aborts - which is what we want in that case
|
||||
return mSaveKeyringParcel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(EditKeyResult result) {
|
||||
|
||||
setState(State.DONE);
|
||||
|
||||
mButtonContainer.getInAnimation().setDuration(750);
|
||||
mButtonContainer.setDisplayedChild(2);
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FragmentActivity activity = getActivity();
|
||||
Intent intent = new Intent(activity, ViewKeyActivity.class);
|
||||
intent.setData(KeyRings.buildGenericKeyRingUri(mMasterKeyId));
|
||||
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_LINKED_TRANSITION, true);
|
||||
View linkedItem = mButtonContainer.getChildAt(2);
|
||||
|
||||
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
activity, linkedItem, linkedItem.getTransitionName()).toBundle();
|
||||
activity.startActivity(intent, options);
|
||||
mFinishOnStop = true;
|
||||
} else {
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
// cookies are automatically saved, we don't want that
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
String cookie = cookieManager.getCookie("https://github.com/");
|
||||
outState.putString(ARG_GITHUB_COOKIE, cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
String cookie = savedInstanceState.getString(ARG_GITHUB_COOKIE);
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
cookieManager.setCookie("https://github.com/", cookie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
try {
|
||||
// cookies are automatically saved, we don't want that
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
// noinspection deprecation (replacement is api lvl 21)
|
||||
cookieManager.removeAllCookie();
|
||||
} catch (Exception e) {
|
||||
// no biggie if this fails
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
if (mFinishOnStop) {
|
||||
Activity activity = getActivity();
|
||||
activity.setResult(Activity.RESULT_OK);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(EditKeyResult result) {
|
||||
result.createNotify(getActivity()).show(this);
|
||||
setState(State.LID_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationCancelled() {
|
||||
mRetryButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
v.setOnClickListener(null);
|
||||
mButtonContainer.setDisplayedChild(1);
|
||||
setState(State.LID_PROCESS);
|
||||
cryptoOperation();
|
||||
}
|
||||
});
|
||||
mButtonContainer.setDisplayedChild(3);
|
||||
setState(State.LID_ERROR);
|
||||
}
|
||||
|
||||
private String mOAuthCode, mOAuthState;
|
||||
|
||||
public void oAuthRequest(String hostAndPath, String clientId, String scope) {
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] buf = new byte[16];
|
||||
new Random().nextBytes(buf);
|
||||
mOAuthState = new String(Hex.encode(buf));
|
||||
mOAuthCode = null;
|
||||
|
||||
final Dialog auth_dialog = new Dialog(activity);
|
||||
auth_dialog.setContentView(R.layout.oauth_webview);
|
||||
WebView web = (WebView) auth_dialog.findViewById(R.id.web_view);
|
||||
web.getSettings().setSaveFormData(false);
|
||||
web.getSettings().setUserAgentString("OpenKeychain " + BuildConfig.VERSION_NAME);
|
||||
web.setWebViewClient(new WebViewClient() {
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
Uri uri = Uri.parse(url);
|
||||
if ("oauth-openkeychain".equals(uri.getScheme())) {
|
||||
|
||||
if (mOAuthCode != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uri.getQueryParameter("error") != null) {
|
||||
Log.i(Constants.TAG, "got oauth error: " + uri.getQueryParameter("error"));
|
||||
auth_dialog.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if mOAuthState == queryParam[state]
|
||||
mOAuthCode = uri.getQueryParameter("code");
|
||||
|
||||
auth_dialog.dismiss();
|
||||
return true;
|
||||
}
|
||||
// don't surf away from github!
|
||||
if (!"github.com".equals(uri.getHost())) {
|
||||
auth_dialog.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
auth_dialog.setTitle(R.string.linked_webview_title_github);
|
||||
auth_dialog.setCancelable(true);
|
||||
auth_dialog.setOnDismissListener(new OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
step1GetOAuthToken();
|
||||
}
|
||||
});
|
||||
auth_dialog.show();
|
||||
|
||||
web.loadUrl("https://" + hostAndPath +
|
||||
"?client_id=" + clientId +
|
||||
"&scope=" + scope +
|
||||
"&redirect_uri=oauth-openkeychain://linked/" +
|
||||
"&state=" + mOAuthState);
|
||||
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
mStatus1.setDisplayedChild(Status.IDLE);
|
||||
mStatus2.setDisplayedChild(Status.IDLE);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case AUTH_PROCESS:
|
||||
mStatus1.setDisplayedChild(Status.PROGRESS);
|
||||
mStatus2.setDisplayedChild(Status.IDLE);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case AUTH_ERROR:
|
||||
mStatus1.setDisplayedChild(Status.ERROR);
|
||||
mStatus2.setDisplayedChild(Status.IDLE);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case POST_PROCESS:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.PROGRESS);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case POST_ERROR:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.ERROR);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case LID_PROCESS:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.OK);
|
||||
mStatus3.setDisplayedChild(Status.PROGRESS);
|
||||
break;
|
||||
case LID_ERROR:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.OK);
|
||||
mStatus3.setDisplayedChild(Status.ERROR);
|
||||
break;
|
||||
case DONE:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.OK);
|
||||
mStatus3.setDisplayedChild(Status.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONObject jsonHttpRequest(String url, JSONObject params, String accessToken)
|
||||
throws IOException, HttpResultException {
|
||||
|
||||
HttpsURLConnection nection = (HttpsURLConnection) new URL(url).openConnection();
|
||||
nection.setDoInput(true);
|
||||
nection.setDoOutput(true);
|
||||
nection.setConnectTimeout(2000);
|
||||
nection.setReadTimeout(1000);
|
||||
nection.setRequestProperty("Content-Type", "application/json");
|
||||
nection.setRequestProperty("Accept", "application/json");
|
||||
nection.setRequestProperty("User-Agent", "OpenKeychain " + BuildConfig.VERSION_NAME);
|
||||
if (accessToken != null) {
|
||||
nection.setRequestProperty("Authorization", "token " + accessToken);
|
||||
}
|
||||
|
||||
OutputStream os = nection.getOutputStream();
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
|
||||
writer.write(params.toString());
|
||||
writer.flush();
|
||||
writer.close();
|
||||
os.close();
|
||||
|
||||
try {
|
||||
|
||||
nection.connect();
|
||||
|
||||
int code = nection.getResponseCode();
|
||||
if (code != HttpsURLConnection.HTTP_CREATED && code != HttpsURLConnection.HTTP_OK) {
|
||||
throw new HttpResultException(nection.getResponseCode(), nection.getResponseMessage());
|
||||
}
|
||||
|
||||
InputStream in = new BufferedInputStream(nection.getInputStream());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
StringBuilder response = new StringBuilder();
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
response.append(line);
|
||||
}
|
||||
|
||||
try {
|
||||
return new JSONObject(response.toString());
|
||||
} catch (JSONException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
} finally {
|
||||
nection.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class HttpResultException extends Exception {
|
||||
final int mCode;
|
||||
final String mResponse;
|
||||
|
||||
HttpResultException(int code, String response) {
|
||||
mCode = code;
|
||||
mResponse = response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.linked;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
||||
|
||||
public class LinkedIdCreateHttpsStep1Fragment extends Fragment {
|
||||
|
||||
LinkedIdWizard mLinkedIdWizard;
|
||||
|
||||
EditText mEditUri;
|
||||
|
||||
public static LinkedIdCreateHttpsStep1Fragment newInstance() {
|
||||
LinkedIdCreateHttpsStep1Fragment frag = new LinkedIdCreateHttpsStep1Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mLinkedIdWizard = (LinkedIdWizard) getActivity();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.linked_create_https_fragment_step1, container, false);
|
||||
|
||||
view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
String uri = "https://" + mEditUri.getText();
|
||||
|
||||
if (!checkUri(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String proofText = GenericHttpsResource.generateText(getActivity(),
|
||||
mLinkedIdWizard.mFingerprint);
|
||||
|
||||
LinkedIdCreateHttpsStep2Fragment frag =
|
||||
LinkedIdCreateHttpsStep2Fragment.newInstance(uri, proofText);
|
||||
|
||||
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||
}
|
||||
});
|
||||
|
||||
mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri);
|
||||
|
||||
mEditUri.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
String uri = "https://" + editable;
|
||||
if (uri.length() > 0) {
|
||||
if (checkUri(uri)) {
|
||||
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.ic_stat_retyped_ok, 0);
|
||||
} else {
|
||||
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.ic_stat_retyped_bad, 0);
|
||||
}
|
||||
} else {
|
||||
// remove drawable if email is empty
|
||||
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// mEditUri.setText("mugenguild.com/pgpkey.txt");
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private static boolean checkUri(String uri) {
|
||||
return Patterns.WEB_URL.matcher(uri).matches();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.linked;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragment {
|
||||
|
||||
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
|
||||
|
||||
public static final String ARG_URI = "uri", ARG_TEXT = "text";
|
||||
|
||||
EditText mEditUri;
|
||||
|
||||
URI mResourceUri;
|
||||
String mResourceString;
|
||||
|
||||
public static LinkedIdCreateHttpsStep2Fragment newInstance
|
||||
(String uri, String proofText) {
|
||||
|
||||
LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_URI, uri);
|
||||
args.putString(ARG_TEXT, proofText);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
GenericHttpsResource getResource(OperationLog log) {
|
||||
return GenericHttpsResource.createNew(mResourceUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
mResourceUri = new URI(getArguments().getString(ARG_URI));
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
mResourceString = getArguments().getString(ARG_TEXT);
|
||||
|
||||
}
|
||||
|
||||
protected View newView(LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
if (view != null) {
|
||||
|
||||
view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
proofSend();
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.button_save).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
proofSave();
|
||||
}
|
||||
});
|
||||
|
||||
mEditUri = (EditText) view.findViewById(R.id.linked_create_https_uri);
|
||||
mEditUri.setText(mResourceUri.toString());
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void proofSend () {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString);
|
||||
sendIntent.setType("text/plain");
|
||||
startActivity(sendIntent);
|
||||
}
|
||||
|
||||
private void proofSave () {
|
||||
String state = Environment.getExternalStorageState();
|
||||
if (!Environment.MEDIA_MOUNTED.equals(state)) {
|
||||
Notify.create(getActivity(), "External storage not available!", Style.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
String targetName = "pgpkey.txt";
|
||||
|
||||
FileHelper.saveDocument(this,
|
||||
targetName, Uri.fromFile(new File(Constants.Path.APP_DIR, targetName)),
|
||||
"text/plain", R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to,
|
||||
REQUEST_CODE_OUTPUT);
|
||||
}
|
||||
|
||||
private void saveFile(Uri uri) {
|
||||
try {
|
||||
PrintWriter out =
|
||||
new PrintWriter(getActivity().getContentResolver().openOutputStream(uri));
|
||||
out.print(mResourceString);
|
||||
if (out.checkError()) {
|
||||
Notify.create(getActivity(), "Error writing file!", Style.ERROR).show();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Notify.create(getActivity(), "File could not be opened for writing!", Style.ERROR).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
// For saving a file
|
||||
case REQUEST_CODE_OUTPUT:
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
Uri uri = data.getData();
|
||||
saveFile(uri);
|
||||
break;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.linked;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
public class LinkedIdCreateTwitterStep1Fragment extends Fragment {
|
||||
|
||||
LinkedIdWizard mLinkedIdWizard;
|
||||
|
||||
EditText mEditHandle;
|
||||
|
||||
public static LinkedIdCreateTwitterStep1Fragment newInstance() {
|
||||
LinkedIdCreateTwitterStep1Fragment frag = new LinkedIdCreateTwitterStep1Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mLinkedIdWizard = (LinkedIdWizard) getActivity();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step1, container, false);
|
||||
|
||||
view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
final String handle = mEditHandle.getText().toString();
|
||||
|
||||
if ("".equals(handle)) {
|
||||
mEditHandle.setError("Please input a Twitter handle!");
|
||||
return;
|
||||
}
|
||||
|
||||
new AsyncTask<Void,Void,Boolean>() {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
return true;
|
||||
// return checkHandle(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (result == null) {
|
||||
Notify.create(getActivity(),
|
||||
"Connection error while checking username!",
|
||||
Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
Notify.create(getActivity(),
|
||||
"This handle does not exist on Twitter!",
|
||||
Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedIdCreateTwitterStep2Fragment frag =
|
||||
LinkedIdCreateTwitterStep2Fragment.newInstance(handle);
|
||||
|
||||
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mLinkedIdWizard.loadFragment(null, null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||
}
|
||||
});
|
||||
|
||||
mEditHandle = (EditText) view.findViewById(R.id.linked_create_twitter_handle);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/* not used at this point, too many problems
|
||||
private static Boolean checkHandle(String handle) {
|
||||
try {
|
||||
HttpURLConnection nection =
|
||||
(HttpURLConnection) new URL("https://twitter.com/" + handle).openConnection();
|
||||
nection.setRequestMethod("HEAD");
|
||||
nection.setRequestProperty("User-Agent", "OpenKeychain");
|
||||
return nection.getResponseCode() == 200;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.linked;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
|
||||
|
||||
public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragment {
|
||||
|
||||
public static final String ARG_HANDLE = "handle";
|
||||
|
||||
String mResourceHandle;
|
||||
String mResourceString;
|
||||
|
||||
public static LinkedIdCreateTwitterStep2Fragment newInstance
|
||||
(String handle) {
|
||||
|
||||
LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_HANDLE, handle);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mResourceString =
|
||||
TwitterResource.generate(mLinkedIdWizard.mFingerprint);
|
||||
|
||||
mResourceHandle = getArguments().getString(ARG_HANDLE);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
if (view != null) {
|
||||
view.findViewById(R.id.button_send).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
proofSend();
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.button_share).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
proofShare();
|
||||
}
|
||||
});
|
||||
|
||||
((TextView) view.findViewById(R.id.linked_tweet_published)).setText(
|
||||
Html.fromHtml(getString(R.string.linked_create_twitter_2_3, mResourceHandle))
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
LinkedTokenResource getResource(OperationLog log) {
|
||||
return TwitterResource.searchInTwitterStream(getActivity(),
|
||||
mResourceHandle, mResourceString, log);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.linked_create_twitter_fragment_step2, container, false);
|
||||
}
|
||||
|
||||
private void proofShare() {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, mResourceString);
|
||||
sendIntent.setType("text/plain");
|
||||
startActivity(sendIntent);
|
||||
}
|
||||
|
||||
private void proofSend() {
|
||||
|
||||
Uri.Builder builder = Uri.parse("https://twitter.com/intent/tweet").buildUpon();
|
||||
builder.appendQueryParameter("text", mResourceString);
|
||||
Uri uri = builder.build();
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
getActivity().startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.linked;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class LinkedIdSelectFragment extends Fragment {
|
||||
|
||||
LinkedIdWizard mLinkedIdWizard;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static LinkedIdSelectFragment newInstance() {
|
||||
LinkedIdSelectFragment frag = new LinkedIdSelectFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.linked_select_fragment, container, false);
|
||||
|
||||
view.findViewById(R.id.linked_create_https_button)
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LinkedIdCreateHttpsStep1Fragment frag =
|
||||
LinkedIdCreateHttpsStep1Fragment.newInstance();
|
||||
|
||||
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
view.findViewById(R.id.linked_create_dns_button)
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LinkedIdCreateDnsStep1Fragment frag =
|
||||
LinkedIdCreateDnsStep1Fragment.newInstance();
|
||||
|
||||
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
view.findViewById(R.id.linked_create_twitter_button)
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LinkedIdCreateTwitterStep1Fragment frag =
|
||||
LinkedIdCreateTwitterStep1Fragment.newInstance();
|
||||
|
||||
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.linked_create_github_button)
|
||||
.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LinkedIdCreateGithubFragment frag =
|
||||
LinkedIdCreateGithubFragment.newInstance();
|
||||
|
||||
mLinkedIdWizard.loadFragment(null, frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mLinkedIdWizard = (LinkedIdWizard) getActivity();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
package org.sufficientlysecure.keychain.ui.linked;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextSwitcher;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Constants.key;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedResource;
|
||||
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.ViewHolder.VerifyState;
|
||||
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.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertListWidget;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class LinkedIdViewFragment extends CryptoOperationFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor>, OnBackStackChangedListener {
|
||||
|
||||
private static final String ARG_DATA_URI = "data_uri";
|
||||
private static final String ARG_LID_RANK = "rank";
|
||||
private static final String ARG_IS_SECRET = "verified";
|
||||
private static final String ARG_FINGERPRINT = "fingerprint";
|
||||
private static final int LOADER_ID_LINKED_ID = 1;
|
||||
|
||||
private UriAttribute mLinkedId;
|
||||
private LinkedTokenResource mLinkedResource;
|
||||
private boolean mIsSecret;
|
||||
|
||||
private Context mContext;
|
||||
private byte[] mFingerprint;
|
||||
|
||||
private AsyncTask mInProgress;
|
||||
|
||||
private Uri mDataUri;
|
||||
private ViewHolder mViewHolder;
|
||||
private int mLidRank;
|
||||
private OnIdentityLoadedListener mIdLoadedListener;
|
||||
private long mCertifyKeyId;
|
||||
|
||||
public static LinkedIdViewFragment newInstance(Uri dataUri, int rank,
|
||||
boolean isSecret, byte[] fingerprint) throws IOException {
|
||||
LinkedIdViewFragment frag = new LinkedIdViewFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putInt(ARG_LID_RANK, rank);
|
||||
args.putBoolean(ARG_IS_SECRET, isSecret);
|
||||
args.putByteArray(ARG_FINGERPRINT, fingerprint);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
public LinkedIdViewFragment() {
|
||||
// IMPORTANT: the id must be unique in the ViewKeyActivity CryptoOperationHelper id namespace!
|
||||
// no initial progress message -> we handle progress ourselves!
|
||||
super(5, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
mDataUri = args.getParcelable(ARG_DATA_URI);
|
||||
mLidRank = args.getInt(ARG_LID_RANK);
|
||||
|
||||
mIsSecret = args.getBoolean(ARG_IS_SECRET);
|
||||
mFingerprint = args.getByteArray(ARG_FINGERPRINT);
|
||||
|
||||
mContext = getActivity();
|
||||
|
||||
getLoaderManager().initLoader(LOADER_ID_LINKED_ID, null, this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
switch (id) {
|
||||
case LOADER_ID_LINKED_ID:
|
||||
return new CursorLoader(getActivity(), mDataUri,
|
||||
UserIdsAdapter.USER_PACKETS_PROJECTION,
|
||||
Tables.USER_PACKETS + "." + UserPackets.RANK
|
||||
+ " = " + Integer.toString(mLidRank), null, null);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_LINKED_ID:
|
||||
|
||||
// Nothing to load means break if we are *expected* to load
|
||||
if (!cursor.moveToFirst()) {
|
||||
if (mIdLoadedListener != null) {
|
||||
Notify.create(getActivity(), "Error loading identity!",
|
||||
Notify.LENGTH_LONG, Style.ERROR).show();
|
||||
finishFragment();
|
||||
}
|
||||
// Or just ignore, this is probably some intermediate state during certify
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
int certStatus = cursor.getInt(UserIdsAdapter.INDEX_VERIFIED);
|
||||
|
||||
byte[] data = cursor.getBlob(UserIdsAdapter.INDEX_ATTRIBUTE_DATA);
|
||||
UriAttribute linkedId = LinkedAttribute.fromAttributeData(data);
|
||||
|
||||
loadIdentity(linkedId, certStatus);
|
||||
|
||||
if (mIdLoadedListener != null) {
|
||||
mIdLoadedListener.onIdentityLoaded();
|
||||
mIdLoadedListener = null;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "error parsing identity", e);
|
||||
Notify.create(getActivity(), "Error parsing identity!",
|
||||
Notify.LENGTH_LONG, Style.ERROR).show();
|
||||
finishFragment();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void finishFragment() {
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FragmentManager manager = getFragmentManager();
|
||||
manager.removeOnBackStackChangedListener(LinkedIdViewFragment.this);
|
||||
manager.popBackStack("linked_id", FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface OnIdentityLoadedListener {
|
||||
void onIdentityLoaded();
|
||||
}
|
||||
|
||||
public void setOnIdentityLoadedListener(OnIdentityLoadedListener listener) {
|
||||
mIdLoadedListener = listener;
|
||||
}
|
||||
|
||||
private void loadIdentity(UriAttribute linkedId, int certStatus) {
|
||||
mLinkedId = linkedId;
|
||||
|
||||
if (mLinkedId instanceof LinkedAttribute) {
|
||||
LinkedResource res = ((LinkedAttribute) mLinkedId).mResource;
|
||||
mLinkedResource = (LinkedTokenResource) res;
|
||||
}
|
||||
|
||||
if (!mIsSecret) {
|
||||
switch (certStatus) {
|
||||
case Certs.VERIFIED_SECRET:
|
||||
KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
|
||||
null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
case Certs.VERIFIED_SELF:
|
||||
KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
|
||||
null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
default:
|
||||
KeyFormattingUtils.setStatusImage(mContext, mViewHolder.mLinkedIdHolder.vVerified,
|
||||
null, State.INVALID, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
mViewHolder.mLinkedIdHolder.vVerified.setImageResource(R.drawable.octo_link_24dp);
|
||||
}
|
||||
|
||||
mViewHolder.mLinkedIdHolder.setData(mContext, mLinkedId);
|
||||
|
||||
setShowVerifying(false);
|
||||
|
||||
// no resource, nothing further we can do…
|
||||
if (mLinkedResource == null) {
|
||||
mViewHolder.vButtonView.setVisibility(View.GONE);
|
||||
mViewHolder.vButtonVerify.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLinkedResource.isViewable()) {
|
||||
mViewHolder.vButtonView.setVisibility(View.VISIBLE);
|
||||
mViewHolder.vButtonView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = mLinkedResource.getViewIntent();
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
getActivity().startActivity(intent);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mViewHolder.vButtonView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
|
||||
}
|
||||
|
||||
static class ViewHolder {
|
||||
private final View vButtonView;
|
||||
private final ViewAnimator vVerifyingContainer;
|
||||
private final ViewAnimator vItemCertified;
|
||||
private final View vKeySpinnerContainer;
|
||||
LinkedIdsAdapter.ViewHolder mLinkedIdHolder;
|
||||
|
||||
private ViewAnimator vButtonSwitcher;
|
||||
private CertListWidget vLinkedCerts;
|
||||
private CertifyKeySpinner vKeySpinner;
|
||||
private final View vButtonVerify;
|
||||
private final View vButtonRetry;
|
||||
private final View vButtonConfirm;
|
||||
|
||||
private final ViewAnimator vProgress;
|
||||
private final TextSwitcher vText;
|
||||
|
||||
ViewHolder(View root) {
|
||||
vLinkedCerts = (CertListWidget) root.findViewById(R.id.linked_id_certs);
|
||||
vKeySpinner = (CertifyKeySpinner) root.findViewById(R.id.cert_key_spinner);
|
||||
vKeySpinnerContainer = root.findViewById(R.id.cert_key_spincontainer);
|
||||
vButtonSwitcher = (ViewAnimator) root.findViewById(R.id.button_animator);
|
||||
|
||||
mLinkedIdHolder = new LinkedIdsAdapter.ViewHolder(root);
|
||||
|
||||
vButtonVerify = root.findViewById(R.id.button_verify);
|
||||
vButtonRetry = root.findViewById(R.id.button_retry);
|
||||
vButtonConfirm = root.findViewById(R.id.button_confirm);
|
||||
vButtonView = root.findViewById(R.id.button_view);
|
||||
|
||||
vVerifyingContainer = (ViewAnimator) root.findViewById(R.id.linked_verify_container);
|
||||
vItemCertified = (ViewAnimator) root.findViewById(R.id.linked_id_certified);
|
||||
|
||||
vProgress = (ViewAnimator) root.findViewById(R.id.linked_cert_progress);
|
||||
vText = (TextSwitcher) root.findViewById(R.id.linked_cert_text);
|
||||
}
|
||||
|
||||
enum VerifyState {
|
||||
VERIFYING, VERIFY_OK, VERIFY_ERROR, CERTIFYING
|
||||
}
|
||||
|
||||
void setVerifyingState(Context context, VerifyState state, boolean isSecret) {
|
||||
switch (state) {
|
||||
case VERIFYING:
|
||||
vProgress.setDisplayedChild(0);
|
||||
vText.setText(context.getString(R.string.linked_text_verifying));
|
||||
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
break;
|
||||
|
||||
case VERIFY_OK:
|
||||
vProgress.setDisplayedChild(1);
|
||||
if (!isSecret) {
|
||||
showButton(2);
|
||||
if (!vKeySpinner.isSingleEntry()) {
|
||||
vKeySpinnerContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
showButton(1);
|
||||
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
}
|
||||
break;
|
||||
|
||||
case VERIFY_ERROR:
|
||||
showButton(1);
|
||||
vProgress.setDisplayedChild(2);
|
||||
vText.setText(context.getString(R.string.linked_text_error));
|
||||
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
break;
|
||||
|
||||
case CERTIFYING:
|
||||
vProgress.setDisplayedChild(0);
|
||||
vText.setText(context.getString(R.string.linked_text_confirming));
|
||||
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void showVerifyingContainer(Context context, boolean show, boolean isSecret) {
|
||||
if (vVerifyingContainer.getDisplayedChild() == (show ? 1 : 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
vVerifyingContainer.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down);
|
||||
vVerifyingContainer.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down);
|
||||
vVerifyingContainer.setDisplayedChild(show ? 1 : 0);
|
||||
|
||||
vItemCertified.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down);
|
||||
vItemCertified.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down);
|
||||
vItemCertified.setDisplayedChild(show || isSecret ? 1 : 0);
|
||||
}
|
||||
|
||||
void showButton(int which) {
|
||||
if (vButtonSwitcher.getDisplayedChild() == which) {
|
||||
return;
|
||||
}
|
||||
vButtonSwitcher.setDisplayedChild(which);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean mVerificationState = false;
|
||||
/** Switches between the 'verifying' ui bit and certificate status. This method
|
||||
* must behave correctly in all states, showing or hiding the appropriate views
|
||||
* and cancelling pending operations where necessary.
|
||||
*
|
||||
* This method also handles back button functionality in combination with
|
||||
* onBackStateChanged.
|
||||
*/
|
||||
void setShowVerifying(boolean show) {
|
||||
if (!show) {
|
||||
if (mInProgress != null) {
|
||||
mInProgress.cancel(false);
|
||||
mInProgress = null;
|
||||
}
|
||||
getFragmentManager().removeOnBackStackChangedListener(this);
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getFragmentManager().popBackStack("verification",
|
||||
FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
}
|
||||
});
|
||||
|
||||
if (!mVerificationState) {
|
||||
return;
|
||||
}
|
||||
mVerificationState = false;
|
||||
|
||||
mViewHolder.showButton(0);
|
||||
mViewHolder.vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
mViewHolder.showVerifyingContainer(mContext, false, mIsSecret);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mVerificationState) {
|
||||
return;
|
||||
}
|
||||
mVerificationState = true;
|
||||
|
||||
FragmentManager manager = getFragmentManager();
|
||||
manager.beginTransaction().addToBackStack("verification").commit();
|
||||
manager.executePendingTransactions();
|
||||
manager.addOnBackStackChangedListener(this);
|
||||
mViewHolder.showVerifyingContainer(mContext, true, mIsSecret);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
setShowVerifying(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.linked_id_view_fragment, null);
|
||||
|
||||
mViewHolder = new ViewHolder(root);
|
||||
root.setTag(mViewHolder);
|
||||
|
||||
((ImageView) root.findViewById(R.id.status_icon_verified))
|
||||
.setColorFilter(mContext.getResources().getColor(R.color.android_green_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
((ImageView) root.findViewById(R.id.status_icon_invalid))
|
||||
.setColorFilter(mContext.getResources().getColor(R.color.android_red_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
|
||||
mViewHolder.vButtonVerify.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
verifyResource();
|
||||
}
|
||||
});
|
||||
mViewHolder.vButtonRetry.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
verifyResource();
|
||||
}
|
||||
});
|
||||
mViewHolder.vButtonConfirm.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
initiateCertifying();
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(CertListWidget.ARG_URI, Certs.buildLinkedIdCertsUri(mDataUri, mLidRank));
|
||||
args.putBoolean(CertListWidget.ARG_IS_SECRET, mIsSecret);
|
||||
getLoaderManager().initLoader(CertListWidget.LOADER_ID_LINKED_CERTS,
|
||||
args, mViewHolder.vLinkedCerts);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void verifyResource() {
|
||||
|
||||
// only one at a time (no sync needed, mInProgress is only touched in ui thread)
|
||||
if (mInProgress != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowVerifying(true);
|
||||
|
||||
mViewHolder.vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
mViewHolder.setVerifyingState(mContext, VerifyState.VERIFYING, mIsSecret);
|
||||
|
||||
mInProgress = new AsyncTask<Void,Void,LinkedVerifyResult>() {
|
||||
@Override
|
||||
protected LinkedVerifyResult doInBackground(Void... params) {
|
||||
long timer = System.currentTimeMillis();
|
||||
LinkedVerifyResult result = mLinkedResource.verify(getActivity(), mFingerprint);
|
||||
|
||||
// ux flow: this operation should take at last a second
|
||||
timer = System.currentTimeMillis() -timer;
|
||||
if (timer < 1000) try {
|
||||
Thread.sleep(1000 -timer);
|
||||
} catch (InterruptedException e) {
|
||||
// never mind
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(LinkedVerifyResult result) {
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
if (result.success()) {
|
||||
mViewHolder.vText.setText(getString(mLinkedResource.getVerifiedText(mIsSecret)));
|
||||
// hack to preserve bold text
|
||||
((TextView) mViewHolder.vText.getCurrentView()).setText(
|
||||
mLinkedResource.getVerifiedText(mIsSecret));
|
||||
mViewHolder.setVerifyingState(mContext, VerifyState.VERIFY_OK, mIsSecret);
|
||||
mViewHolder.mLinkedIdHolder.seekAttention();
|
||||
} else {
|
||||
mViewHolder.setVerifyingState(mContext, VerifyState.VERIFY_ERROR, mIsSecret);
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
mInProgress = null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void initiateCertifying() {
|
||||
|
||||
if (mIsSecret) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the user's passphrase for this key (if required)
|
||||
mCertifyKeyId = mViewHolder.vKeySpinner.getSelectedKeyId();
|
||||
if (mCertifyKeyId == key.none || mCertifyKeyId == key.symmetric) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
SubtleAttentionSeeker.tintBackground(mViewHolder.vKeySpinnerContainer, 600).start();
|
||||
} else {
|
||||
Notify.create(getActivity(), R.string.select_key_to_certify, Style.ERROR).show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mViewHolder.setVerifyingState(mContext, VerifyState.CERTIFYING, false);
|
||||
cryptoOperation();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationCancelled() {
|
||||
super.onCryptoOperationCancelled();
|
||||
|
||||
// go back to 'verified ok'
|
||||
setShowVerifying(false);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Parcelable createOperationInput() {
|
||||
long masterKeyId = KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint);
|
||||
CertifyAction action = new CertifyAction(masterKeyId, null,
|
||||
Collections.singletonList(mLinkedId.toUserAttribute()));
|
||||
|
||||
// fill values for this action
|
||||
CertifyActionsParcel parcel = new CertifyActionsParcel(mCertifyKeyId);
|
||||
parcel.mCertifyActions.addAll(Collections.singletonList(action));
|
||||
|
||||
return parcel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(OperationResult result) {
|
||||
result.createNotify(getActivity()).show();
|
||||
// no need to do anything else, we will get a loader refresh!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(OperationResult result) {
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.linked;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class LinkedIdWizard extends BaseActivity {
|
||||
|
||||
public static final int FRAG_ACTION_START = 0;
|
||||
public static final int FRAG_ACTION_TO_RIGHT = 1;
|
||||
public static final int FRAG_ACTION_TO_LEFT = 2;
|
||||
|
||||
long mMasterKeyId;
|
||||
byte[] mFingerprint;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTitle(getString(R.string.title_linked_id_create));
|
||||
|
||||
try {
|
||||
Uri uri = getIntent().getData();
|
||||
uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(uri);
|
||||
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(uri);
|
||||
if (!ring.hasAnySecret()) {
|
||||
Log.e(Constants.TAG, "Linked Identities can only be added to secret keys!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mMasterKeyId = ring.extractOrGetMasterKeyId();
|
||||
mFingerprint = ring.getFingerprint();
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Invalid uri given, key does not exist!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// pass extras into fragment
|
||||
LinkedIdSelectFragment frag = LinkedIdSelectFragment.newInstance();
|
||||
loadFragment(null, frag, FRAG_ACTION_START);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.create_key_activity);
|
||||
}
|
||||
|
||||
public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
// we could end up with overlapping fragments.
|
||||
if (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideKeyboard();
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
|
||||
switch (action) {
|
||||
case FRAG_ACTION_START:
|
||||
transaction.setCustomAnimations(0, 0);
|
||||
transaction.replace(R.id.create_key_fragment_container, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
break;
|
||||
case FRAG_ACTION_TO_LEFT:
|
||||
getSupportFragmentManager().popBackStackImmediate();
|
||||
break;
|
||||
case FRAG_ACTION_TO_RIGHT:
|
||||
transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left,
|
||||
R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right);
|
||||
transaction.addToBackStack(null);
|
||||
transaction.replace(R.id.create_key_fragment_container, fragment)
|
||||
.commitAllowingStateLoss();
|
||||
break;
|
||||
|
||||
}
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager)
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
// check if no view has focus
|
||||
View v = getCurrentFocus();
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!getFragmentManager().popBackStackImmediate()) {
|
||||
navigateBack();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
// Respond to the action bar's Up/Home button
|
||||
case android.R.id.home:
|
||||
navigateBack();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
Intent upIntent = NavUtils.getParentActivityIntent(this);
|
||||
upIntent.setData(KeyRings.buildGenericKeyRingUri(mMasterKeyId));
|
||||
// This activity is NOT part of this app's task, so create a new task
|
||||
// when navigating up, with a synthesized back stack.
|
||||
TaskStackBuilder.create(this)
|
||||
// Add all of this activity's parents to the back stack
|
||||
.addNextIntentWithParentStack(upIntent)
|
||||
// Navigate up to the closest parent
|
||||
.startActivities();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.spongycastle.util.Arrays;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
|
||||
public class ExperimentalWordConfirm {
|
||||
|
||||
public static String getWords(Context context, byte[] fingerprintBlob) {
|
||||
ArrayList<String> words = new ArrayList<>();
|
||||
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
context.getAssets().open("word_confirm_list.txt"),
|
||||
"UTF-8"
|
||||
));
|
||||
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
words.add(line);
|
||||
|
||||
line = reader.readLine();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IOException", e);
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String fingerprint = "";
|
||||
|
||||
// NOTE: 160 bit SHA-1 truncated to 156 bit
|
||||
byte[] fingerprintBlobTruncated = Arrays.copyOfRange(fingerprintBlob, 0, 156 / 8);
|
||||
|
||||
// TODO: implement key stretching to minimize fp length?
|
||||
|
||||
// BitSet bits = BitSet.valueOf(fingerprintBlob); // min API 19 and little endian!
|
||||
BitSet bits = bitSetToByteArray(fingerprintBlobTruncated);
|
||||
Log.d(Constants.TAG, "bits: " + bits.toString());
|
||||
|
||||
final int CHUNK_SIZE = 13;
|
||||
final int LAST_CHUNK_INDEX = fingerprintBlobTruncated.length * 8 / CHUNK_SIZE; // 12
|
||||
Log.d(Constants.TAG, "LAST_CHUNK_INDEX: " + LAST_CHUNK_INDEX);
|
||||
|
||||
int from = 0;
|
||||
int to = CHUNK_SIZE;
|
||||
for (int i = 0; i < (LAST_CHUNK_INDEX + 1); i++) {
|
||||
Log.d(Constants.TAG, "from: " + from + " to: " + to);
|
||||
|
||||
BitSet setIndex = bits.get(from, to);
|
||||
int wordIndex = (int) bitSetToLong(setIndex);
|
||||
// int wordIndex = (int) setIndex.toLongArray()[0]; // min API 19
|
||||
|
||||
fingerprint += words.get(wordIndex);
|
||||
|
||||
if (i != LAST_CHUNK_INDEX) {
|
||||
// line break every 3 words
|
||||
if (to % (CHUNK_SIZE * 3) == 0) {
|
||||
fingerprint += "\n";
|
||||
} else {
|
||||
fingerprint += " ";
|
||||
}
|
||||
}
|
||||
|
||||
from = to;
|
||||
to += CHUNK_SIZE;
|
||||
}
|
||||
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BitSet containing the values in bytes.
|
||||
* BIG ENDIAN!
|
||||
*/
|
||||
private static BitSet bitSetToByteArray(byte[] bytes) {
|
||||
int arrayLength = bytes.length * 8;
|
||||
BitSet bits = new BitSet();
|
||||
|
||||
for (int i = 0; i < arrayLength; i++) {
|
||||
if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
|
||||
bits.set(i);
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
private static long bitSetToLong(BitSet bits) {
|
||||
long value = 0L;
|
||||
for (int i = 0; i < bits.length(); ++i) {
|
||||
value += bits.get(i) ? (1L << i) : 0L;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -269,6 +269,10 @@ public class KeyFormattingUtils {
|
||||
return hexString;
|
||||
}
|
||||
|
||||
public static long convertFingerprintToKeyId(byte[] fingerprint) {
|
||||
return ByteBuffer.wrap(fingerprint, 12, 8).getLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a human-readable version of a key ID, which is usually 64 bits: lower-case, no
|
||||
* leading 0x, space-separated quartets (for keys whose length in hex is divisible by 4)
|
||||
|
||||
@@ -17,13 +17,19 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.util;
|
||||
|
||||
import android.animation.AnimatorInflater;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.Keyframe;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
|
||||
@TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
/** Simple animation helper for subtle attention seeker stuff.
|
||||
*
|
||||
@@ -36,6 +42,10 @@ public class SubtleAttentionSeeker {
|
||||
}
|
||||
|
||||
public static ObjectAnimator tada(View view, float shakeFactor) {
|
||||
return tada(view, shakeFactor, 1400);
|
||||
}
|
||||
|
||||
public static ObjectAnimator tada(View view, float shakeFactor, int duration) {
|
||||
|
||||
PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofKeyframe(View.SCALE_X,
|
||||
Keyframe.ofFloat(0f, 1f),
|
||||
@@ -80,7 +90,19 @@ public class SubtleAttentionSeeker {
|
||||
);
|
||||
|
||||
return ObjectAnimator.ofPropertyValuesHolder(view, pvhScaleX, pvhScaleY, pvhRotate).
|
||||
setDuration(1400);
|
||||
setDuration(duration);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||
public static ObjectAnimator tintBackground(View view, int duration) {
|
||||
return ObjectAnimator.ofArgb(view, "backgroundColor",
|
||||
0x00FF0000, 0x33FF0000, 0x00FF0000).setDuration(duration);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.LOLLIPOP)
|
||||
public static ObjectAnimator tintText(View view, int duration) {
|
||||
return ObjectAnimator.ofArgb(view, "backgroundColor",
|
||||
0x00FF7F00, 0x33FF7F00, 0x00FF7F00).setDuration(duration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ public class ThemeChanger {
|
||||
// hack to get holo design (which is not automatically applied due to activity's
|
||||
// Theme.NoDisplay)
|
||||
if (Constants.Pref.Theme.DARK.equals(preferences.getTheme())) {
|
||||
return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Dialog);
|
||||
return new ContextThemeWrapper(context, R.style.Theme_Keychain_Dark);
|
||||
} else {
|
||||
return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Light_Dialog);
|
||||
return new ContextThemeWrapper(context, R.style.Theme_Keychain_Light);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import org.ocpsoft.prettytime.PrettyTime;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
|
||||
public class CertListWidget extends ViewAnimator
|
||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
public static final int LOADER_ID_LINKED_CERTS = 38572;
|
||||
|
||||
public static final String ARG_URI = "uri";
|
||||
public static final String ARG_IS_SECRET = "is_secret";
|
||||
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] CERTS_PROJECTION = new String[]{
|
||||
KeychainContract.Certs._ID,
|
||||
KeychainContract.Certs.MASTER_KEY_ID,
|
||||
KeychainContract.Certs.VERIFIED,
|
||||
KeychainContract.Certs.TYPE,
|
||||
KeychainContract.Certs.RANK,
|
||||
KeychainContract.Certs.KEY_ID_CERTIFIER,
|
||||
KeychainContract.Certs.USER_ID,
|
||||
KeychainContract.Certs.SIGNER_UID,
|
||||
KeychainContract.Certs.CREATION
|
||||
};
|
||||
public static final int INDEX_MASTER_KEY_ID = 1;
|
||||
public static final int INDEX_VERIFIED = 2;
|
||||
public static final int INDEX_TYPE = 3;
|
||||
public static final int INDEX_RANK = 4;
|
||||
public static final int INDEX_KEY_ID_CERTIFIER = 5;
|
||||
public static final int INDEX_USER_ID = 6;
|
||||
public static final int INDEX_SIGNER_UID = 7;
|
||||
public static final int INDEX_CREATION = 8;
|
||||
|
||||
private TextView vCollapsed;
|
||||
private ListView vExpanded;
|
||||
private View vExpandButton;
|
||||
private boolean mIsSecret;
|
||||
|
||||
public CertListWidget(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
View root = getRootView();
|
||||
vCollapsed = (TextView) root.findViewById(R.id.cert_collapsed_list);
|
||||
vExpanded = (ListView) root.findViewById(R.id.cert_expanded_list);
|
||||
vExpandButton = root.findViewById(R.id.cert_expand_button);
|
||||
|
||||
// for now
|
||||
vExpandButton.setVisibility(View.GONE);
|
||||
vExpandButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
toggleExpanded();
|
||||
}
|
||||
});
|
||||
|
||||
// vExpanded.setAdapter(null);
|
||||
|
||||
}
|
||||
|
||||
void toggleExpanded() {
|
||||
setDisplayedChild(getDisplayedChild() == 1 ? 0 : 1);
|
||||
}
|
||||
|
||||
void setExpanded(boolean expanded) {
|
||||
setDisplayedChild(expanded ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
Uri uri = args.getParcelable(ARG_URI);
|
||||
mIsSecret = args.getBoolean(ARG_IS_SECRET, false);
|
||||
return new CursorLoader(getContext(), uri,
|
||||
CERTS_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
|
||||
if (data == null || !data.moveToFirst()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO support external certificates
|
||||
Date userCert = null;
|
||||
while (!data.isAfterLast()) {
|
||||
|
||||
int verified = data.getInt(INDEX_VERIFIED);
|
||||
Date creation = new Date(data.getLong(INDEX_CREATION) * 1000);
|
||||
|
||||
if (verified == Certs.VERIFIED_SECRET) {
|
||||
if (userCert == null || userCert.after(creation)) {
|
||||
userCert = creation;
|
||||
}
|
||||
}
|
||||
|
||||
data.moveToNext();
|
||||
}
|
||||
|
||||
if (userCert != null) {
|
||||
PrettyTime format = new PrettyTime();
|
||||
if (mIsSecret) {
|
||||
vCollapsed.setText("You created this identity "
|
||||
+ format.format(userCert) + ".");
|
||||
} else {
|
||||
vCollapsed.setText("You verified and confirmed this identity "
|
||||
+ format.format(userCert) + ".");
|
||||
}
|
||||
} else {
|
||||
vCollapsed.setText("This identity is not yet verified or confirmed.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,25 +17,24 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
|
||||
public class CertifyKeySpinner extends KeySpinner {
|
||||
private long mHiddenMasterKeyId = Constants.key.none;
|
||||
private boolean mIsSingle;
|
||||
|
||||
public CertifyKeySpinner(Context context) {
|
||||
super(context);
|
||||
@@ -94,9 +93,11 @@ public class CertifyKeySpinner extends KeySpinner {
|
||||
if (!data.isNull(mIndexHasCertify)) {
|
||||
if (selection == -1) {
|
||||
selection = data.getPosition() + 1;
|
||||
mIsSingle = true;
|
||||
} else {
|
||||
// if selection is already set, we have more than one certify key!
|
||||
// get back to "none"!
|
||||
mIsSingle = false;
|
||||
selection = 0;
|
||||
}
|
||||
}
|
||||
@@ -106,6 +107,9 @@ public class CertifyKeySpinner extends KeySpinner {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSingleEntry() {
|
||||
return mIsSingle && getSelectedItemPosition() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isItemEnabled(Cursor cursor) {
|
||||
@@ -128,4 +132,10 @@ public class CertifyKeySpinner extends KeySpinner {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @StringRes int getNoneString() {
|
||||
return R.string.choice_select_cert;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
@@ -34,6 +35,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@@ -117,8 +119,7 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
||||
if (getContext() instanceof FragmentActivity) {
|
||||
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
|
||||
} else {
|
||||
throw new AssertionError("KeySpinner must be attached to FragmentActivity, this is "
|
||||
+ getContext().getClass());
|
||||
// ignore, this happens during preview! we use fragmentactivities everywhere either way
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,9 +227,11 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
||||
return inner.getView(position -1, convertView, parent);
|
||||
}
|
||||
|
||||
return convertView != null ? convertView :
|
||||
View view = convertView != null ? convertView :
|
||||
LayoutInflater.from(getContext()).inflate(
|
||||
R.layout.keyspinner_item_none, parent, false);
|
||||
((TextView) view.findViewById(R.id.keyspinner_key_name)).setText(getNoneString());
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -259,4 +262,9 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
||||
bundle.putLong(ARG_KEY_ID, getSelectedKeyId());
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public @StringRes int getNoneString() {
|
||||
return R.string.cert_none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.*;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class PrefixedEditText extends EditText {
|
||||
|
||||
private String mPrefix;
|
||||
private Rect mPrefixRect = new Rect();
|
||||
|
||||
public PrefixedEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
TypedArray style = context.getTheme().obtainStyledAttributes(
|
||||
attrs, R.styleable.PrefixedEditText, 0, 0);
|
||||
mPrefix = style.getString(R.styleable.PrefixedEditText_prefix);
|
||||
if (mPrefix == null) {
|
||||
mPrefix = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
getPaint().getTextBounds(mPrefix, 0, mPrefix.length(), mPrefixRect);
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawText(mPrefix, super.getCompoundPaddingLeft(), getBaseline(), getPaint());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompoundPaddingLeft() {
|
||||
return super.getCompoundPaddingLeft() + mPrefixRect.width();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
|
||||
public class StatusIndicator extends ToolableViewAnimator {
|
||||
|
||||
public enum Status {
|
||||
IDLE, PROGRESS, OK, ERROR
|
||||
}
|
||||
|
||||
public StatusIndicator(Context context) {
|
||||
super(context);
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.status_indicator, this, true);
|
||||
setInAnimation(AnimationUtils.loadAnimation(context, R.anim.fade_in));
|
||||
setOutAnimation(AnimationUtils.loadAnimation(context, R.anim.fade_out));
|
||||
}
|
||||
|
||||
public StatusIndicator(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.status_indicator, this, true);
|
||||
setInAnimation(AnimationUtils.loadAnimation(context, R.anim.fade_in));
|
||||
setOutAnimation(AnimationUtils.loadAnimation(context, R.anim.fade_out));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayedChild(int whichChild) {
|
||||
if (whichChild != getDisplayedChild()) {
|
||||
super.setDisplayedChild(whichChild);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDisplayedChild(Status status) {
|
||||
setDisplayedChild(status.ordinal());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user