New Gradle project structure
This commit is contained in:
@@ -0,0 +1,846 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.LookupUnknownKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
/* Intents */
|
||||
// without permission
|
||||
public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT";
|
||||
|
||||
/* EXTRA keys for input */
|
||||
public static final String EXTRA_TEXT = "text";
|
||||
|
||||
private long mSignatureKeyId = 0;
|
||||
|
||||
private boolean mReturnResult = false;
|
||||
private boolean mSignedOnly = false;
|
||||
private boolean mAssumeSymmetricEncryption = false;
|
||||
|
||||
private EditText mMessage = null;
|
||||
private LinearLayout mSignatureLayout = null;
|
||||
private ImageView mSignatureStatusImage = null;
|
||||
private TextView mUserId = null;
|
||||
private TextView mUserIdRest = null;
|
||||
|
||||
private ViewFlipper mSource = null;
|
||||
private TextView mSourceLabel = null;
|
||||
private ImageView mSourcePrevious = null;
|
||||
private ImageView mSourceNext = null;
|
||||
|
||||
private boolean mDecryptEnabled = true;
|
||||
private String mDecryptString = "";
|
||||
private boolean mReplyEnabled = true;
|
||||
private String mReplyString = "";
|
||||
|
||||
private int mDecryptTarget;
|
||||
|
||||
private EditText mFilename = null;
|
||||
private CheckBox mDeleteAfter = null;
|
||||
private BootstrapButton mBrowse = null;
|
||||
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
|
||||
private Uri mContentUri = null;
|
||||
private boolean mReturnBinary = false;
|
||||
|
||||
private long mUnknownSignatureKeyId = 0;
|
||||
|
||||
private long mSecretKeyId = Id.key.none;
|
||||
|
||||
private FileDialogFragment mFileDialog;
|
||||
|
||||
private boolean mLookupUnknownKey = true;
|
||||
|
||||
private boolean mDecryptImmediately = false;
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
|
||||
if (mDecryptEnabled) {
|
||||
menu.add(1, Id.menu.option.decrypt, 0, mDecryptString).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
}
|
||||
if (mReplyEnabled) {
|
||||
menu.add(1, Id.menu.option.reply, 1, mReplyString).setShowAsAction(
|
||||
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case Id.menu.option.decrypt: {
|
||||
decryptClicked();
|
||||
|
||||
return true;
|
||||
}
|
||||
case Id.menu.option.reply: {
|
||||
replyClicked();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
mSource = (ViewFlipper) findViewById(R.id.source);
|
||||
mSourceLabel = (TextView) findViewById(R.id.sourceLabel);
|
||||
mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious);
|
||||
mSourceNext = (ImageView) findViewById(R.id.sourceNext);
|
||||
|
||||
mSourcePrevious.setClickable(true);
|
||||
mSourcePrevious.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_right_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_right_out));
|
||||
mSource.showPrevious();
|
||||
updateSource();
|
||||
}
|
||||
});
|
||||
|
||||
mSourceNext.setClickable(true);
|
||||
OnClickListener nextSourceClickListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_left_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_left_out));
|
||||
mSource.showNext();
|
||||
updateSource();
|
||||
}
|
||||
};
|
||||
mSourceNext.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mSourceLabel.setClickable(true);
|
||||
mSourceLabel.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mMessage = (EditText) findViewById(R.id.message);
|
||||
mSignatureLayout = (LinearLayout) findViewById(R.id.signature);
|
||||
mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status);
|
||||
mUserId = (TextView) findViewById(R.id.mainUserId);
|
||||
mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
|
||||
|
||||
// measure the height of the source_file view and set the message view's min height to that,
|
||||
// so it fills mSource fully... bit of a hack.
|
||||
View tmp = findViewById(R.id.sourceFile);
|
||||
tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
int height = tmp.getMeasuredHeight();
|
||||
mMessage.setMinimumHeight(height);
|
||||
|
||||
mFilename = (EditText) findViewById(R.id.filename);
|
||||
mBrowse = (BootstrapButton) findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*",
|
||||
Id.request.filename);
|
||||
}
|
||||
});
|
||||
|
||||
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption);
|
||||
|
||||
// default: message source
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
|
||||
mSource.showNext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.decrypt_activity);
|
||||
|
||||
// set actionbar without home button if called from another app
|
||||
ActionBarHelper.setBackButton(this);
|
||||
|
||||
initView();
|
||||
|
||||
setupDrawerNavigation(savedInstanceState);
|
||||
|
||||
// Handle intent actions
|
||||
handleActions(getIntent());
|
||||
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceMessage
|
||||
&& mMessage.getText().length() == 0) {
|
||||
|
||||
CharSequence clipboardText = ClipboardReflection.getClipboardText(this);
|
||||
|
||||
String data = "";
|
||||
if (clipboardText != null) {
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
|
||||
if (!matcher.matches()) {
|
||||
matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(clipboardText);
|
||||
}
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
mMessage.setText(data);
|
||||
Toast.makeText(this, R.string.using_clipboard_content, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mSignatureLayout.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (mSignatureKeyId == 0) {
|
||||
return;
|
||||
}
|
||||
PGPPublicKeyRing key = ProviderHelper.getPGPPublicKeyRingByKeyId(
|
||||
DecryptActivity.this, mSignatureKeyId);
|
||||
if (key != null) {
|
||||
Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class);
|
||||
intent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID);
|
||||
intent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, mSignatureKeyId);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mReplyEnabled = false;
|
||||
|
||||
// build new actionbar
|
||||
invalidateOptionsMenu();
|
||||
|
||||
if (mReturnResult) {
|
||||
mSourcePrevious.setClickable(false);
|
||||
mSourcePrevious.setEnabled(false);
|
||||
mSourcePrevious.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceNext.setClickable(false);
|
||||
mSourceNext.setEnabled(false);
|
||||
mSourceNext.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceLabel.setClickable(false);
|
||||
mSourceLabel.setEnabled(false);
|
||||
}
|
||||
|
||||
updateSource();
|
||||
|
||||
if (mDecryptImmediately
|
||||
|| (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText()
|
||||
.length() > 0 || mContentUri != null))) {
|
||||
decryptClicked();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActions(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
String type = intent.getType();
|
||||
Uri uri = intent.getData();
|
||||
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
/*
|
||||
* Android's Action
|
||||
*/
|
||||
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
||||
// When sending to Keychain Encrypt via share menu
|
||||
if ("text/plain".equals(type)) {
|
||||
// Plain text
|
||||
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (sharedText != null) {
|
||||
// handle like normal text decryption, override action and extras to later
|
||||
// execute ACTION_DECRYPT in main actions
|
||||
extras.putString(EXTRA_TEXT, sharedText);
|
||||
action = ACTION_DECRYPT;
|
||||
}
|
||||
} else {
|
||||
// Binary via content provider (could also be files)
|
||||
// override uri to get stream from send
|
||||
uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
action = ACTION_DECRYPT;
|
||||
}
|
||||
} else if (Intent.ACTION_VIEW.equals(action)) {
|
||||
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
|
||||
|
||||
// override action
|
||||
action = ACTION_DECRYPT;
|
||||
}
|
||||
|
||||
String textData = extras.getString(EXTRA_TEXT);
|
||||
|
||||
/**
|
||||
* Main Actions
|
||||
*/
|
||||
if (ACTION_DECRYPT.equals(action) && textData != null) {
|
||||
Log.d(Constants.TAG, "textData null, matching text ...");
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(textData);
|
||||
if (matcher.matches()) {
|
||||
Log.d(Constants.TAG, "PGP_MESSAGE matched");
|
||||
textData = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
textData = textData.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(textData);
|
||||
} else {
|
||||
matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(textData);
|
||||
if (matcher.matches()) {
|
||||
Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
|
||||
textData = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
textData = textData.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(textData);
|
||||
|
||||
mDecryptString = getString(R.string.btn_verify);
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Nothing matched!");
|
||||
}
|
||||
}
|
||||
} else if (ACTION_DECRYPT.equals(action) && uri != null) {
|
||||
// get file path from uri
|
||||
String path = FileHelper.getPath(this, uri);
|
||||
|
||||
if (path != null) {
|
||||
mInputFilename = path;
|
||||
mFilename.setText(mInputFilename);
|
||||
guessOutputFilename();
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceFile) {
|
||||
mSource.showNext();
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG,
|
||||
"Direct binary data without actual file in filesystem is not supported. Please use the Remote Service API!");
|
||||
Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
// end activity
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG,
|
||||
"Include the extra 'text' or an Uri with setData() in your Intent!");
|
||||
}
|
||||
}
|
||||
|
||||
private void guessOutputFilename() {
|
||||
mInputFilename = mFilename.getText().toString();
|
||||
File file = new File(mInputFilename);
|
||||
String filename = file.getName();
|
||||
if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
|
||||
filename = filename.substring(0, filename.length() - 4);
|
||||
}
|
||||
mOutputFilename = Constants.path.APP_DIR + "/" + filename;
|
||||
}
|
||||
|
||||
private void updateSource() {
|
||||
switch (mSource.getCurrentView().getId()) {
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
mDecryptString = getString(R.string.btn_decrypt);
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
mDecryptString = getString(R.string.btn_decrypt);
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptClicked() {
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceFile) {
|
||||
mDecryptTarget = Id.target.file;
|
||||
} else {
|
||||
mDecryptTarget = Id.target.message;
|
||||
}
|
||||
initiateDecryption();
|
||||
}
|
||||
|
||||
private void initiateDecryption() {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
String currentFilename = mFilename.getText().toString();
|
||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
||||
guessOutputFilename();
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
Toast.makeText(this, R.string.no_file_selected, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInputFilename.startsWith("file")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mDecryptTarget == Id.target.message) {
|
||||
String messageData = mMessage.getText().toString();
|
||||
Matcher matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(messageData);
|
||||
if (matcher.matches()) {
|
||||
mSignedOnly = true;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// else treat it as an decrypted message/file
|
||||
mSignedOnly = false;
|
||||
|
||||
getDecryptionKeyFromInputStream();
|
||||
|
||||
// if we need a symmetric passphrase or a passphrase to use a secret key ask for it
|
||||
if (mSecretKeyId == Id.key.symmetric
|
||||
|| PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
|
||||
showPassphraseDialog();
|
||||
} else {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
private void showPassphraseDialog() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
|
||||
messenger, mSecretKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Rework function, remove global variables
|
||||
*/
|
||||
private void getDecryptionKeyFromInputStream() {
|
||||
InputStream inStream = null;
|
||||
if (mContentUri != null) {
|
||||
try {
|
||||
inStream = getContentResolver().openInputStream(mContentUri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "File not found!", e);
|
||||
Toast.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(mInputFilename)) {
|
||||
Toast.makeText(this, getString(R.string.error_external_storage_not_ready),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
inStream = new BufferedInputStream(new FileInputStream(mInputFilename));
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "File not found!", e);
|
||||
Toast.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes());
|
||||
}
|
||||
|
||||
// get decryption key for this inStream
|
||||
try {
|
||||
try {
|
||||
if (inStream.markSupported()) {
|
||||
inStream.mark(200); // should probably set this to the max size of two pgpF
|
||||
// objects, if it even needs to be anything other than 0.
|
||||
}
|
||||
mSecretKeyId = PgpHelper.getDecryptionKeyId(this, inStream);
|
||||
if (mSecretKeyId == Id.key.none) {
|
||||
throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
mAssumeSymmetricEncryption = false;
|
||||
} catch (NoAsymmetricEncryptionException e) {
|
||||
if (inStream.markSupported()) {
|
||||
inStream.reset();
|
||||
}
|
||||
mSecretKeyId = Id.key.symmetric;
|
||||
if (!PgpOperation.hasSymmetricEncryption(this, inStream)) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_no_known_encryption_found));
|
||||
}
|
||||
mAssumeSymmetricEncryption = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void replyClicked() {
|
||||
Intent intent = new Intent(this, EncryptActivity.class);
|
||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||
String data = mMessage.getText().toString();
|
||||
data = data.replaceAll("(?m)^", "> ");
|
||||
data = "\n\n" + data;
|
||||
intent.putExtra(EncryptActivity.EXTRA_TEXT, data);
|
||||
intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId });
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void askForOutputFilename() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger,
|
||||
getString(R.string.title_decrypt_to_file),
|
||||
getString(R.string.specify_file_to_decrypt_to), mOutputFilename, null);
|
||||
|
||||
mFileDialog.show(getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
|
||||
private void lookupUnknownKey(long unknownKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_OKAY) {
|
||||
// the result is handled by onActivityResult() as LookupUnknownKeyDialogFragment
|
||||
// starts a new Intent which then returns data
|
||||
} else if (message.what == LookupUnknownKeyDialogFragment.MESSAGE_CANCEL) {
|
||||
// decrypt again, but don't lookup unknown keys!
|
||||
mLookupUnknownKey = false;
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
LookupUnknownKeyDialogFragment lookupKeyDialog = LookupUnknownKeyDialogFragment
|
||||
.newInstance(messenger, unknownKeyId);
|
||||
|
||||
lookupKeyDialog.show(getSupportFragmentManager(), "unknownKeyDialog");
|
||||
}
|
||||
|
||||
private void decryptStart() {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
// choose action based on input: decrypt stream, file or bytes
|
||||
if (mContentUri != null) {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_STREAM);
|
||||
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_PROVIDER_URI, mContentUri);
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_FILE);
|
||||
|
||||
Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
|
||||
+ mOutputFilename);
|
||||
|
||||
data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
|
||||
data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
|
||||
} else {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
|
||||
|
||||
String message = mMessage.getText().toString();
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, message.getBytes());
|
||||
}
|
||||
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyId);
|
||||
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_SIGNED_ONLY, mSignedOnly);
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_LOOKUP_UNKNOWN_KEY, mLookupUnknownKey);
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_RETURN_BYTES, mReturnBinary);
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
R.string.progress_decrypting, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
// if key is unknown show lookup dialog
|
||||
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_LOOKUP_KEY)
|
||||
&& mLookupUnknownKey) {
|
||||
mUnknownSignatureKeyId = returnData
|
||||
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
|
||||
lookupUnknownKey(mUnknownSignatureKeyId);
|
||||
return;
|
||||
}
|
||||
|
||||
mSignatureKeyId = 0;
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mReplyEnabled = false;
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
|
||||
Toast.makeText(DecryptActivity.this, R.string.decryption_successful,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
if (mReturnResult) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtras(returnData);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mDecryptTarget) {
|
||||
case Id.target.message:
|
||||
String decryptedMessage = returnData
|
||||
.getString(KeychainIntentService.RESULT_DECRYPTED_STRING);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
mReplyEnabled = false;
|
||||
|
||||
// build new action bar
|
||||
invalidateOptionsMenu();
|
||||
break;
|
||||
|
||||
case Id.target.file:
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// shouldn't happen
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE)) {
|
||||
String userId = returnData
|
||||
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
|
||||
mSignatureKeyId = returnData
|
||||
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
|
||||
mUserIdRest.setText("id: "
|
||||
+ PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId));
|
||||
if (userId == null) {
|
||||
userId = getResources().getString(R.string.unknown_user_id);
|
||||
}
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mUserId.setText(userId);
|
||||
|
||||
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
} else if (returnData
|
||||
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
Toast.makeText(DecryptActivity.this,
|
||||
R.string.unknown_signature_key_touch_to_look_up,
|
||||
Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
}
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.filename: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(this, data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// this request is returned after LookupUnknownKeyDialogFragment started
|
||||
// KeyServerQueryActivity and user looked uo key
|
||||
case Id.request.look_up_key_id: {
|
||||
Log.d(Constants.TAG, "Returning from Lookup Key...");
|
||||
// decrypt again without lookup
|
||||
mLookupUnknownKey = false;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
* 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 org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ActionBarDrawerToggle;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.view.ActionProvider;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import com.beardedhen.androidbootstrap.FontAwesomeText;
|
||||
|
||||
/**
|
||||
* some fundamental ideas from https://github.com/tobykurien/SherlockNavigationDrawer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class DrawerActivity extends SherlockFragmentActivity {
|
||||
private DrawerLayout mDrawerLayout;
|
||||
private ListView mDrawerList;
|
||||
private ActionBarDrawerToggle mDrawerToggle;
|
||||
|
||||
private CharSequence mDrawerTitle;
|
||||
private CharSequence mTitle;
|
||||
|
||||
private static Class[] mItemsClass = new Class[] { KeyListPublicActivity.class,
|
||||
EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class,
|
||||
KeyListSecretActivity.class, RegisteredAppsListActivity.class };
|
||||
|
||||
private static final int MENU_ID_PREFERENCE = 222;
|
||||
private static final int MENU_ID_HELP = 223;
|
||||
|
||||
protected void setupDrawerNavigation(Bundle savedInstanceState) {
|
||||
mDrawerTitle = getString(R.string.app_name);
|
||||
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
mDrawerList = (ListView) findViewById(R.id.left_drawer);
|
||||
|
||||
// set a custom shadow that overlays the main content when the drawer
|
||||
// opens
|
||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||
|
||||
NavItem mItemIconTexts[] = new NavItem[] {
|
||||
new NavItem("fa-user", getString(R.string.nav_contacts)),
|
||||
new NavItem("fa-lock", getString(R.string.nav_encrypt)),
|
||||
new NavItem("fa-unlock", getString(R.string.nav_decrypt)),
|
||||
new NavItem("fa-download", getString(R.string.nav_import)),
|
||||
new NavItem("fa-key", getString(R.string.nav_secret_keys)),
|
||||
new NavItem("fa-android", getString(R.string.nav_apps)) };
|
||||
|
||||
mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item,
|
||||
mItemIconTexts));
|
||||
|
||||
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
||||
|
||||
// enable ActionBar app icon to behave as action to toggle nav drawer
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
|
||||
// ActionBarDrawerToggle ties together the the proper interactions
|
||||
// between the sliding drawer and the action bar app icon
|
||||
mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
|
||||
mDrawerLayout, /* DrawerLayout object */
|
||||
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
|
||||
R.string.drawer_open, /* "open drawer" description for accessibility */
|
||||
R.string.drawer_close /* "close drawer" description for accessibility */
|
||||
) {
|
||||
public void onDrawerClosed(View view) {
|
||||
getSupportActionBar().setTitle(mTitle);
|
||||
// creates call to onPrepareOptionsMenu()
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
mTitle = getSupportActionBar().getTitle();
|
||||
getSupportActionBar().setTitle(mDrawerTitle);
|
||||
// creates call to onPrepareOptionsMenu()
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
};
|
||||
mDrawerLayout.setDrawerListener(mDrawerToggle);
|
||||
|
||||
// if (savedInstanceState == null) {
|
||||
// selectItem(0);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences);
|
||||
menu.add(42, MENU_ID_HELP, 101, R.string.menu_help);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
/* Called whenever we call invalidateOptionsMenu() */
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// If the nav drawer is open, hide action items related to the content
|
||||
// view
|
||||
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
|
||||
// menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
// The action bar home/up action should open or close the drawer.
|
||||
// ActionBarDrawerToggle will take care of this.
|
||||
if (mDrawerToggle.onOptionsItemSelected(getMenuItem(item))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case MENU_ID_PREFERENCE: {
|
||||
Intent intent = new Intent(this, PreferencesActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case MENU_ID_HELP: {
|
||||
Intent intent = new Intent(this, HelpActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// Handle action buttons
|
||||
// switch (item.getItemId()) {
|
||||
// case R.id.action_websearch:
|
||||
// // create intent to perform web search for this planet
|
||||
// Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
|
||||
// intent.putExtra(SearchManager.QUERY, getSupportActionBar().getTitle());
|
||||
// // catch event that there's no activity to handle intent
|
||||
// if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
// startActivity(intent);
|
||||
// } else {
|
||||
// Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();
|
||||
// }
|
||||
// return true;
|
||||
// default:
|
||||
// return super.onOptionsItemSelected(item);
|
||||
// }
|
||||
}
|
||||
|
||||
private android.view.MenuItem getMenuItem(final MenuItem item) {
|
||||
return new android.view.MenuItem() {
|
||||
@Override
|
||||
public int getItemId() {
|
||||
return item.getItemId();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean collapseActionView() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expandActionView() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider getActionProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getActionView() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getAlphabeticShortcut() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGroupId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextMenuInfo getMenuInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getNumericShortcut() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubMenu getSubMenu() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitleCondensed() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSubMenu() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActionViewExpanded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCheckable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setActionProvider(ActionProvider actionProvider) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setActionView(View view) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setActionView(int resId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setAlphabeticShortcut(char alphaChar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setCheckable(boolean checkable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setChecked(boolean checked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setEnabled(boolean enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setIcon(Drawable icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setIcon(int iconRes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setIntent(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setNumericShortcut(char numericChar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setOnMenuItemClickListener(
|
||||
OnMenuItemClickListener menuItemClickListener) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setShortcut(char numericChar, char alphaChar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShowAsAction(int actionEnum) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setShowAsActionFlags(int actionEnum) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setTitle(CharSequence title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setTitle(int title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setTitleCondensed(CharSequence title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.view.MenuItem setVisible(boolean visible) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* The click listener for ListView in the navigation drawer */
|
||||
private class DrawerItemClickListener implements ListView.OnItemClickListener {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
selectItem(position);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectItem(int position) {
|
||||
// update selected item and title, then close the drawer
|
||||
mDrawerList.setItemChecked(position, true);
|
||||
// setTitle(mDrawerTitles[position]);
|
||||
mDrawerLayout.closeDrawer(mDrawerList);
|
||||
|
||||
finish();
|
||||
overridePendingTransition(0, 0);
|
||||
|
||||
Intent intent = new Intent(this, mItemsClass[position]);
|
||||
startActivity(intent);
|
||||
// disable animation of activity start
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* When using the ActionBarDrawerToggle, you must call it during onPostCreate() and
|
||||
* onConfigurationChanged()...
|
||||
*/
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
// Sync the toggle state after onRestoreInstanceState has occurred.
|
||||
mDrawerToggle.syncState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// Pass any configuration change to the drawer toggles
|
||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
private class NavItem {
|
||||
public String icon;
|
||||
public String title;
|
||||
|
||||
public NavItem(String icon, String title) {
|
||||
super();
|
||||
this.icon = icon;
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
private class NavigationDrawerAdapter extends ArrayAdapter<NavItem> {
|
||||
Context context;
|
||||
int layoutResourceId;
|
||||
NavItem data[] = null;
|
||||
|
||||
public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) {
|
||||
super(context, layoutResourceId, data);
|
||||
this.layoutResourceId = layoutResourceId;
|
||||
this.context = context;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View row = convertView;
|
||||
NavItemHolder holder = null;
|
||||
|
||||
if (row == null) {
|
||||
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
|
||||
row = inflater.inflate(layoutResourceId, parent, false);
|
||||
|
||||
holder = new NavItemHolder();
|
||||
holder.img = (FontAwesomeText) row.findViewById(R.id.drawer_item_icon);
|
||||
holder.txtTitle = (TextView) row.findViewById(R.id.drawer_item_text);
|
||||
|
||||
row.setTag(holder);
|
||||
} else {
|
||||
holder = (NavItemHolder) row.getTag();
|
||||
}
|
||||
|
||||
NavItem item = data[position];
|
||||
holder.txtTitle.setText(item.title);
|
||||
holder.img.setIcon(item.icon);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NavItemHolder {
|
||||
FontAwesomeText img;
|
||||
TextView txtTitle;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeyEditor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SectionView;
|
||||
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class EditKeyActivity extends SherlockFragmentActivity {
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
|
||||
public static final String ACTION_EDIT_KEY = Constants.INTENT_PREFIX + "EDIT_KEY";
|
||||
|
||||
// possible extra keys
|
||||
public static final String EXTRA_USER_IDS = "user_ids";
|
||||
public static final String EXTRA_NO_PASSPHRASE = "no_passphrase";
|
||||
public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys";
|
||||
|
||||
// results when saving key
|
||||
public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String RESULT_EXTRA_USER_ID = "user_id";
|
||||
|
||||
// EDIT
|
||||
private Uri mDataUri;
|
||||
|
||||
private PGPSecretKeyRing mKeyRing = null;
|
||||
|
||||
private SectionView mUserIdsView;
|
||||
private SectionView mKeysView;
|
||||
|
||||
private String mCurrentPassPhrase = null;
|
||||
private String mNewPassPhrase = null;
|
||||
|
||||
private BootstrapButton mChangePassPhrase;
|
||||
|
||||
private CheckBox mNoPassphrase;
|
||||
|
||||
Vector<String> mUserIds;
|
||||
Vector<PGPSecretKey> mKeys;
|
||||
Vector<Integer> mKeysUsages;
|
||||
boolean masterCanSign = true;
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
mUserIds = new Vector<String>();
|
||||
mKeys = new Vector<PGPSecretKey>();
|
||||
mKeysUsages = new Vector<Integer>();
|
||||
|
||||
// Catch Intents opened from other apps
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
if (ACTION_CREATE_KEY.equals(action)) {
|
||||
handleActionCreateKey(intent);
|
||||
} else if (ACTION_EDIT_KEY.equals(action)) {
|
||||
handleActionEditKey(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intent action to create new key
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionCreateKey(Intent intent) {
|
||||
// Inflate a "Done"/"Cancel" custom action bar
|
||||
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
saveClicked();
|
||||
}
|
||||
}, R.string.btn_do_not_save, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cancelClicked();
|
||||
}
|
||||
});
|
||||
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
mCurrentPassPhrase = "";
|
||||
|
||||
if (extras != null) {
|
||||
// if userId is given, prefill the fields
|
||||
if (extras.containsKey(EXTRA_USER_IDS)) {
|
||||
Log.d(Constants.TAG, "UserIds are given!");
|
||||
mUserIds.add(extras.getString(EXTRA_USER_IDS));
|
||||
}
|
||||
|
||||
// if no passphrase is given
|
||||
if (extras.containsKey(EXTRA_NO_PASSPHRASE)) {
|
||||
boolean noPassphrase = extras.getBoolean(EXTRA_NO_PASSPHRASE);
|
||||
if (noPassphrase) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassPhrase.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// generate key
|
||||
if (extras.containsKey(EXTRA_GENERATE_DEFAULT_KEYS)) {
|
||||
boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS);
|
||||
if (generateDefaultKeys) {
|
||||
|
||||
// Send all information needed to service generate keys in other thread
|
||||
Intent serviceIntent = new Intent(this, KeychainIntentService.class);
|
||||
serviceIntent.setAction(KeychainIntentService.ACTION_GENERATE_DEFAULT_RSA_KEYS);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE,
|
||||
mCurrentPassPhrase);
|
||||
|
||||
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after generating is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this, R.string.progress_generating, ProgressDialog.STYLE_SPINNER) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get new key from data bundle returned from service
|
||||
Bundle data = message.getData();
|
||||
PGPSecretKeyRing masterKeyRing = (PGPSecretKeyRing) PgpConversionHelper
|
||||
.BytesToPGPKeyRing(data
|
||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
|
||||
PGPSecretKeyRing subKeyRing = (PGPSecretKeyRing) PgpConversionHelper
|
||||
.BytesToPGPKeyRing(data
|
||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY2));
|
||||
|
||||
// add master key
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> masterIt = masterKeyRing.getSecretKeys();
|
||||
mKeys.add(masterIt.next());
|
||||
mKeysUsages.add(Id.choice.usage.sign_only);
|
||||
|
||||
// add sub key
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> subIt = subKeyRing.getSecretKeys();
|
||||
subIt.next(); // masterkey
|
||||
mKeys.add(subIt.next());
|
||||
mKeysUsages.add(Id.choice.usage.encrypt_only);
|
||||
|
||||
buildLayout();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
serviceIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(serviceIntent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buildLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle intent action to edit existing key
|
||||
*
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionEditKey(Intent intent) {
|
||||
// Inflate a "Done"/"Cancel" custom action bar
|
||||
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
saveClicked();
|
||||
}
|
||||
});
|
||||
|
||||
mDataUri = intent.getData();
|
||||
if (mDataUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
|
||||
finish();
|
||||
return;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mDataUri);
|
||||
|
||||
long keyRingRowId = Long.valueOf(mDataUri.getLastPathSegment());
|
||||
|
||||
// get master key id using row id
|
||||
long masterKeyId = ProviderHelper.getSecretMasterKeyId(this, keyRingRowId);
|
||||
|
||||
boolean masterCanSign = ProviderHelper.getSecretMasterKeyCanSign(this, keyRingRowId);
|
||||
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
|
||||
if (passphrase == null) {
|
||||
showPassphraseDialog(masterKeyId, masterCanSign);
|
||||
} else {
|
||||
// PgpMain.setEditPassPhrase(passPhrase);
|
||||
mCurrentPassPhrase = passphrase;
|
||||
|
||||
finallyEdit(masterKeyId, masterCanSign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showPassphraseDialog(final long masterKeyId, final boolean masterCanSign) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
String passPhrase = PassphraseCacheService.getCachedPassphrase(
|
||||
EditKeyActivity.this, masterKeyId);
|
||||
mCurrentPassPhrase = passPhrase;
|
||||
finallyEdit(masterKeyId, masterCanSign);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
|
||||
EditKeyActivity.this, messenger, masterKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// show menu only on edit
|
||||
if (mDataUri != null) {
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getSupportMenuInflater().inflate(R.menu.key_edit, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_edit_cancel:
|
||||
cancelClicked();
|
||||
return true;
|
||||
case R.id.menu_key_edit_export_file:
|
||||
mExportHelper.showExportKeysDialog(mDataUri, Id.type.secret_key, Constants.path.APP_DIR
|
||||
+ "/secexport.asc");
|
||||
return true;
|
||||
case R.id.menu_key_edit_delete: {
|
||||
// Message is received after key is deleted
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mExportHelper.deleteKey(mDataUri, Id.type.secret_key, returnHandler);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void finallyEdit(final long masterKeyId, final boolean masterCanSign) {
|
||||
if (masterKeyId != 0) {
|
||||
PGPSecretKey masterKey = null;
|
||||
mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId);
|
||||
if (mKeyRing != null) {
|
||||
masterKey = PgpKeyHelper.getMasterKey(mKeyRing);
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
|
||||
mKeys.add(key);
|
||||
mKeysUsages.add(-1); // get usage when view is created
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Keyring not found with masterKeyId: " + masterKeyId);
|
||||
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
if (masterKey != null) {
|
||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||
Log.d(Constants.TAG, "Added userId " + userId);
|
||||
mUserIds.add(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ???
|
||||
if (mCurrentPassPhrase == null) {
|
||||
mCurrentPassPhrase = "";
|
||||
}
|
||||
|
||||
buildLayout();
|
||||
|
||||
if (mCurrentPassPhrase.equals("")) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassPhrase.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dialog to set a new passphrase
|
||||
*/
|
||||
private void showSetPassphraseDialog() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
|
||||
// set new returned passphrase!
|
||||
mNewPassPhrase = data
|
||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||
|
||||
updatePassPhraseButtonText();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
// set title based on isPassphraseSet()
|
||||
int title = -1;
|
||||
if (isPassphraseSet()) {
|
||||
title = R.string.title_change_pass_phrase;
|
||||
} else {
|
||||
title = R.string.title_set_passphrase;
|
||||
}
|
||||
|
||||
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
|
||||
messenger, title);
|
||||
|
||||
setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
|
||||
* id and key.
|
||||
*/
|
||||
private void buildLayout() {
|
||||
setContentView(R.layout.edit_key_activity);
|
||||
|
||||
// find views
|
||||
mChangePassPhrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_pass_phrase);
|
||||
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
|
||||
|
||||
// Build layout based on given userIds and keys
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
|
||||
mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mUserIdsView.setType(Id.type.user_id);
|
||||
mUserIdsView.setCanEdit(masterCanSign);
|
||||
mUserIdsView.setUserIds(mUserIds);
|
||||
container.addView(mUserIdsView);
|
||||
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mKeysView.setType(Id.type.key);
|
||||
mKeysView.setCanEdit(masterCanSign);
|
||||
mKeysView.setKeys(mKeys, mKeysUsages);
|
||||
container.addView(mKeysView);
|
||||
|
||||
updatePassPhraseButtonText();
|
||||
|
||||
mChangePassPhrase.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
showSetPassphraseDialog();
|
||||
}
|
||||
});
|
||||
|
||||
// disable passphrase when no passphrase checkobox is checked!
|
||||
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
// remove passphrase
|
||||
mNewPassPhrase = null;
|
||||
|
||||
mChangePassPhrase.setVisibility(View.GONE);
|
||||
} else {
|
||||
mChangePassPhrase.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private long getMasterKeyId() {
|
||||
if (mKeysView.getEditors().getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return ((KeyEditor) mKeysView.getEditors().getChildAt(0)).getValue().getKeyID();
|
||||
}
|
||||
|
||||
public boolean isPassphraseSet() {
|
||||
if (mNoPassphrase.isChecked()) {
|
||||
return true;
|
||||
} else if ((!mCurrentPassPhrase.equals(""))
|
||||
|| (mNewPassPhrase != null && !mNewPassPhrase.equals(""))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveClicked() {
|
||||
try {
|
||||
if (!isPassphraseSet()) {
|
||||
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE,
|
||||
mCurrentPassPhrase);
|
||||
data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase);
|
||||
data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS,
|
||||
getUserIds(mUserIdsView));
|
||||
ArrayList<PGPSecretKey> keys = getKeys(mKeysView);
|
||||
data.putByteArray(KeychainIntentService.SAVE_KEYRING_KEYS,
|
||||
PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys));
|
||||
data.putIntegerArrayList(KeychainIntentService.SAVE_KEYRING_KEYS_USAGES,
|
||||
getKeysUsages(mKeysView));
|
||||
data.putLong(KeychainIntentService.SAVE_KEYRING_MASTER_KEY_ID, getMasterKeyId());
|
||||
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, masterCanSign);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after saving is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
R.string.progress_saving, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, getMasterKeyId());
|
||||
ArrayList<String> userIds = null;
|
||||
try {
|
||||
userIds = getUserIds(mUserIdsView);
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "exception while getting user ids", e);
|
||||
}
|
||||
data.putExtra(RESULT_EXTRA_USER_ID, userIds.get(0));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
} catch (PgpGeneralException e) {
|
||||
Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user ids from the SectionView
|
||||
*
|
||||
* @param userIdsView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<String> getUserIds(SectionView userIdsView) throws PgpGeneralException {
|
||||
ArrayList<String> userIds = new ArrayList<String>();
|
||||
|
||||
ViewGroup userIdEditors = userIdsView.getEditors();
|
||||
|
||||
boolean gotMainUserId = false;
|
||||
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
|
||||
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
|
||||
String userId = null;
|
||||
try {
|
||||
userId = editor.getValue();
|
||||
} catch (UserIdEditor.NoNameException e) {
|
||||
throw new PgpGeneralException(this.getString(R.string.error_user_id_needs_a_name));
|
||||
} catch (UserIdEditor.NoEmailException e) {
|
||||
throw new PgpGeneralException(
|
||||
this.getString(R.string.error_user_id_needs_an_email_address));
|
||||
} catch (UserIdEditor.InvalidEmailException e) {
|
||||
throw new PgpGeneralException(e.getMessage());
|
||||
}
|
||||
|
||||
if (userId.equals("")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (editor.isMainUserId()) {
|
||||
userIds.add(0, userId);
|
||||
gotMainUserId = true;
|
||||
} else {
|
||||
userIds.add(userId);
|
||||
}
|
||||
}
|
||||
|
||||
if (userIds.size() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_a_user_id));
|
||||
}
|
||||
|
||||
if (!gotMainUserId) {
|
||||
throw new PgpGeneralException(getString(R.string.error_main_user_id_must_not_be_empty));
|
||||
}
|
||||
|
||||
return userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns keys from the SectionView
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<PGPSecretKey> getKeys(SectionView keysView) throws PgpGeneralException {
|
||||
ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
keys.add(editor.getValue());
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns usage selections of keys from the SectionView
|
||||
*
|
||||
* @param keysView
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<Integer> getKeysUsages(SectionView keysView) throws PgpGeneralException {
|
||||
ArrayList<Integer> getKeysUsages = new ArrayList<Integer>();
|
||||
|
||||
ViewGroup keyEditors = keysView.getEditors();
|
||||
|
||||
if (keyEditors.getChildCount() == 0) {
|
||||
throw new PgpGeneralException(getString(R.string.error_key_needs_master_key));
|
||||
}
|
||||
|
||||
for (int i = 0; i < keyEditors.getChildCount(); ++i) {
|
||||
KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i);
|
||||
getKeysUsages.add(editor.getUsage());
|
||||
}
|
||||
|
||||
return getKeysUsages;
|
||||
}
|
||||
|
||||
private void updatePassPhraseButtonText() {
|
||||
mChangePassPhrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
|
||||
: getString(R.string.btn_set_passphrase));
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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 java.util.ArrayList;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.ActionBar.Tab;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
|
||||
public class HelpActivity extends SherlockFragmentActivity {
|
||||
public static final String EXTRA_SELECTED_TAB = "selectedTab";
|
||||
|
||||
ViewPager mViewPager;
|
||||
TabsAdapter mTabsAdapter;
|
||||
TextView tabCenter;
|
||||
TextView tabText;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.help_activity);
|
||||
|
||||
mViewPager = (ViewPager) findViewById(R.id.pager);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
|
||||
|
||||
mTabsAdapter = new TabsAdapter(this, mViewPager);
|
||||
|
||||
int selectedTab = 0;
|
||||
Intent intent = getIntent();
|
||||
if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
|
||||
selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
|
||||
}
|
||||
|
||||
Bundle startBundle = new Bundle();
|
||||
startBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_start);
|
||||
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)),
|
||||
HelpFragmentHtml.class, startBundle, (selectedTab == 0 ? true : false));
|
||||
|
||||
Bundle nfcBundle = new Bundle();
|
||||
nfcBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_nfc_beam);
|
||||
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)),
|
||||
HelpFragmentHtml.class, nfcBundle, (selectedTab == 1 ? true : false));
|
||||
|
||||
Bundle changelogBundle = new Bundle();
|
||||
changelogBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_changelog);
|
||||
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)),
|
||||
HelpFragmentHtml.class, changelogBundle, (selectedTab == 2 ? true : false));
|
||||
|
||||
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)),
|
||||
HelpFragmentAbout.class, null, (selectedTab == 3 ? true : false));
|
||||
}
|
||||
|
||||
public static class TabsAdapter extends FragmentPagerAdapter implements ActionBar.TabListener,
|
||||
ViewPager.OnPageChangeListener {
|
||||
private final Context mContext;
|
||||
private final ActionBar mActionBar;
|
||||
private final ViewPager mViewPager;
|
||||
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
|
||||
|
||||
static final class TabInfo {
|
||||
private final Class<?> clss;
|
||||
private final Bundle args;
|
||||
|
||||
TabInfo(Class<?> _class, Bundle _args) {
|
||||
clss = _class;
|
||||
args = _args;
|
||||
}
|
||||
}
|
||||
|
||||
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
|
||||
super(activity.getSupportFragmentManager());
|
||||
mContext = activity;
|
||||
mActionBar = activity.getSupportActionBar();
|
||||
mViewPager = pager;
|
||||
mViewPager.setAdapter(this);
|
||||
mViewPager.setOnPageChangeListener(this);
|
||||
}
|
||||
|
||||
public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args, boolean selected) {
|
||||
TabInfo info = new TabInfo(clss, args);
|
||||
tab.setTag(info);
|
||||
tab.setTabListener(this);
|
||||
mTabs.add(info);
|
||||
mActionBar.addTab(tab, selected);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mTabs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
TabInfo info = mTabs.get(position);
|
||||
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
|
||||
}
|
||||
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
}
|
||||
|
||||
public void onPageSelected(int position) {
|
||||
mActionBar.setSelectedNavigationItem(position);
|
||||
}
|
||||
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
}
|
||||
|
||||
public void onTabSelected(Tab tab, FragmentTransaction ft) {
|
||||
Object tag = tab.getTag();
|
||||
for (int i = 0; i < mTabs.size(); i++) {
|
||||
if (mTabs.get(i) == tag) {
|
||||
mViewPager.setCurrentItem(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
|
||||
}
|
||||
|
||||
public void onTabReselected(Tab tab, FragmentTransaction ft) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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 org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragment;
|
||||
|
||||
public class HelpFragmentAbout extends SherlockFragment {
|
||||
|
||||
/**
|
||||
* Workaround for Android Bug. See
|
||||
* http://stackoverflow.com/questions/8748064/starting-activity-from
|
||||
* -fragment-causes-nullpointerexception
|
||||
*/
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.help_about_fragment, container, false);
|
||||
|
||||
TextView versionText = (TextView) view.findViewById(R.id.help_about_version);
|
||||
versionText.setText(getString(R.string.help_about_version) + " " + getVersion());
|
||||
|
||||
HtmlTextView aboutTextView = (HtmlTextView) view.findViewById(R.id.help_about_text);
|
||||
|
||||
// load html from raw resource (Parsing handled by HtmlTextView library)
|
||||
aboutTextView.setHtmlFromRawResource(getActivity(), R.raw.help_about);
|
||||
|
||||
// no flickering when clicking textview for Android < 4
|
||||
aboutTextView.setTextColor(getResources().getColor(android.R.color.black));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current package version.
|
||||
*
|
||||
* @return The current version.
|
||||
*/
|
||||
private String getVersion() {
|
||||
String result = "";
|
||||
try {
|
||||
PackageManager manager = getActivity().getPackageManager();
|
||||
PackageInfo info = manager.getPackageInfo(getActivity().getPackageName(), 0);
|
||||
|
||||
result = String.format("%s (%s)", info.versionName, info.versionCode);
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(Constants.TAG, "Unable to get application version: " + e.getMessage());
|
||||
result = "Unable to get application version.";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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 org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragment;
|
||||
|
||||
public class HelpFragmentHtml extends SherlockFragment {
|
||||
private Activity mActivity;
|
||||
|
||||
private int htmlFile;
|
||||
|
||||
public static final String ARG_HTML_FILE = "htmlFile";
|
||||
|
||||
/**
|
||||
* Create a new instance of HelpFragmentHtml, providing "htmlFile" as an argument.
|
||||
*/
|
||||
static HelpFragmentHtml newInstance(int htmlFile) {
|
||||
HelpFragmentHtml f = new HelpFragmentHtml();
|
||||
|
||||
// Supply html raw file input as an argument.
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_HTML_FILE, htmlFile);
|
||||
f.setArguments(args);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround for Android Bug. See
|
||||
* http://stackoverflow.com/questions/8748064/starting-activity-from
|
||||
* -fragment-causes-nullpointerexception
|
||||
*/
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
setUserVisibleHint(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mActivity = getActivity();
|
||||
|
||||
htmlFile = getArguments().getInt(ARG_HTML_FILE);
|
||||
|
||||
ScrollView scroller = new ScrollView(mActivity);
|
||||
HtmlTextView text = new HtmlTextView(mActivity);
|
||||
|
||||
// padding
|
||||
int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, mActivity
|
||||
.getResources().getDisplayMetrics());
|
||||
text.setPadding(padding, padding, padding, 0);
|
||||
|
||||
scroller.addView(text);
|
||||
|
||||
// load html from raw resource (Parsing handled by HtmlTextView library)
|
||||
text.setHtmlFromRawResource(getActivity(), htmlFile);
|
||||
|
||||
// no flickering when clicking textview for Android < 4
|
||||
text.setTextColor(getResources().getColor(android.R.color.black));
|
||||
|
||||
return scroller;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class ImportKeysActivity extends DrawerActivity implements OnNavigationListener {
|
||||
public static final String ACTION_IMPORT_KEY = Constants.INTENT_PREFIX + "IMPORT_KEY";
|
||||
public static final String ACTION_IMPORT_KEY_FROM_QR_CODE = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_QR_CODE";
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_FILE";
|
||||
public static final String ACTION_IMPORT_KEY_FROM_NFC = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_NFC";
|
||||
|
||||
// only used by IMPORT
|
||||
public static final String EXTRA_KEY_BYTES = "key_bytes";
|
||||
|
||||
// TODO: import keys from server
|
||||
// public static final String EXTRA_KEY_ID = "keyId";
|
||||
|
||||
protected boolean mDeleteAfterImport = false;
|
||||
|
||||
FileDialogFragment mFileDialog;
|
||||
ImportKeysListFragment mListFragment;
|
||||
OnNavigationListener mOnNavigationListener;
|
||||
String[] mNavigationStrings;
|
||||
|
||||
Fragment mCurrentFragment;
|
||||
|
||||
BootstrapButton mImportButton;
|
||||
|
||||
// BootstrapButton mImportSignUploadButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.import_keys_activity);
|
||||
|
||||
mImportButton = (BootstrapButton) findViewById(R.id.import_import);
|
||||
mImportButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
importKeys();
|
||||
}
|
||||
});
|
||||
// mImportSignUploadButton = (BootstrapButton) findViewById(R.id.import_sign_and_upload);
|
||||
// mImportSignUploadButton.setOnClickListener(new OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(View v) {
|
||||
// signAndUploadOnClick();
|
||||
// }
|
||||
// });
|
||||
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
|
||||
setupDrawerNavigation(savedInstanceState);
|
||||
|
||||
// set actionbar without home button if called from another app
|
||||
// ActionBarHelper.setBackButton(this);
|
||||
|
||||
// set drop down navigation
|
||||
mNavigationStrings = getResources().getStringArray(R.array.import_action_list);
|
||||
Context context = getSupportActionBar().getThemedContext();
|
||||
ArrayAdapter<CharSequence> list = ArrayAdapter.createFromResource(context,
|
||||
R.array.import_action_list, R.layout.sherlock_spinner_item);
|
||||
list.setDropDownViewResource(R.layout.sherlock_spinner_dropdown_item);
|
||||
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
||||
getSupportActionBar().setListNavigationCallbacks(list, this);
|
||||
|
||||
handleActions(savedInstanceState, getIntent());
|
||||
}
|
||||
|
||||
protected void handleActions(Bundle savedInstanceState, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Android Standard Actions
|
||||
*/
|
||||
if (Intent.ACTION_VIEW.equals(action)) {
|
||||
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
|
||||
// override action to delegate it to Keychain's ACTION_IMPORT_KEY
|
||||
action = ACTION_IMPORT_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keychain's own Actions
|
||||
*/
|
||||
if (ACTION_IMPORT_KEY.equals(action)) {
|
||||
if ("file".equals(intent.getScheme()) && intent.getDataString() != null) {
|
||||
String importFilename = intent.getData().getPath();
|
||||
|
||||
// display selected filename
|
||||
getSupportActionBar().setSelectedNavigationItem(1);
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ImportKeysFileFragment.ARG_PATH, importFilename);
|
||||
loadFragment(ImportKeysFileFragment.class, args, mNavigationStrings[1]);
|
||||
|
||||
// directly load data
|
||||
startListFragment(savedInstanceState, null, importFilename);
|
||||
} else if (extras.containsKey(EXTRA_KEY_BYTES)) {
|
||||
byte[] importData = intent.getByteArrayExtra(EXTRA_KEY_BYTES);
|
||||
|
||||
// directly load data
|
||||
startListFragment(savedInstanceState, importData, null);
|
||||
}
|
||||
} else {
|
||||
// Internal actions
|
||||
startListFragment(savedInstanceState, null, null);
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
|
||||
getSupportActionBar().setSelectedNavigationItem(1);
|
||||
loadFragment(ImportKeysFileFragment.class, null, mNavigationStrings[1]);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_QR_CODE.equals(action)) {
|
||||
getSupportActionBar().setSelectedNavigationItem(2);
|
||||
loadFragment(ImportKeysQrCodeFragment.class, null, mNavigationStrings[2]);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) {
|
||||
getSupportActionBar().setSelectedNavigationItem(3);
|
||||
loadFragment(ImportKeysNFCFragment.class, null, mNavigationStrings[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startListFragment(Bundle savedInstanceState, byte[] bytes, String filename) {
|
||||
// Check that the activity is using the layout version with
|
||||
// the fragment_container FrameLayout
|
||||
if (findViewById(R.id.import_keys_list_container) != null) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
mListFragment = ImportKeysListFragment.newInstance(bytes, filename);
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.import_keys_list_container, mListFragment)
|
||||
.commitAllowingStateLoss();
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
|
||||
// Create new fragment from our own Fragment class
|
||||
switch (itemPosition) {
|
||||
case 0:
|
||||
loadFragment(ImportKeysServerFragment.class, null, mNavigationStrings[itemPosition]);
|
||||
break;
|
||||
case 1:
|
||||
loadFragment(ImportKeysFileFragment.class, null, mNavigationStrings[itemPosition]);
|
||||
break;
|
||||
case 2:
|
||||
loadFragment(ImportKeysQrCodeFragment.class, null, mNavigationStrings[itemPosition]);
|
||||
break;
|
||||
case 3:
|
||||
loadFragment(ImportKeysClipboardFragment.class, null, mNavigationStrings[itemPosition]);
|
||||
break;
|
||||
case 4:
|
||||
loadFragment(ImportKeysNFCFragment.class, null, mNavigationStrings[itemPosition]);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void loadFragment(Class<?> clss, Bundle args, String tag) {
|
||||
mCurrentFragment = Fragment.instantiate(this, clss.getName(), args);
|
||||
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
// Replace whatever is in the fragment container with this fragment
|
||||
// and give the fragment a tag name equal to the string at the position selected
|
||||
ft.replace(R.id.import_navigation_fragment, mCurrentFragment, tag);
|
||||
// Apply changes
|
||||
ft.commit();
|
||||
}
|
||||
|
||||
public void loadCallback(byte[] importData, String importFilename) {
|
||||
mListFragment.loadNew(importData, importFilename);
|
||||
}
|
||||
|
||||
// private void importAndSignOld(final long keyId, final String expectedFingerprint) {
|
||||
// if (expectedFingerprint != null && expectedFingerprint.length() > 0) {
|
||||
//
|
||||
// Thread t = new Thread() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// try {
|
||||
// // TODO: display some sort of spinner here while the user waits
|
||||
//
|
||||
// // TODO: there should be only 1
|
||||
// HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]);
|
||||
// String encodedKey = server.get(keyId);
|
||||
//
|
||||
// PGPKeyRing keyring = PGPHelper.decodeKeyRing(new ByteArrayInputStream(
|
||||
// encodedKey.getBytes()));
|
||||
// if (keyring != null && keyring instanceof PGPPublicKeyRing) {
|
||||
// PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring;
|
||||
//
|
||||
// // make sure the fingerprints match before we cache this thing
|
||||
// String actualFingerprint = PGPHelper.convertFingerprintToHex(publicKeyRing
|
||||
// .getPublicKey().getFingerprint());
|
||||
// if (expectedFingerprint.equals(actualFingerprint)) {
|
||||
// // store the signed key in our local cache
|
||||
// int retval = PGPMain.storeKeyRingInCache(publicKeyRing);
|
||||
// if (retval != Id.return_value.ok
|
||||
// && retval != Id.return_value.updated) {
|
||||
// status.putString(EXTRA_ERROR,
|
||||
// "Failed to store signed key in local cache");
|
||||
// } else {
|
||||
// Intent intent = new Intent(ImportFromQRCodeActivity.this,
|
||||
// SignKeyActivity.class);
|
||||
// intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
// startActivityForResult(intent, Id.request.sign_key);
|
||||
// }
|
||||
// } else {
|
||||
// status.putString(
|
||||
// EXTRA_ERROR,
|
||||
// "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key.");
|
||||
// }
|
||||
// }
|
||||
// } catch (QueryException e) {
|
||||
// Log.e(TAG, "Failed to query KeyServer", e);
|
||||
// status.putString(EXTRA_ERROR, "Failed to query KeyServer");
|
||||
// status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
// } catch (IOException e) {
|
||||
// Log.e(TAG, "Failed to query KeyServer", e);
|
||||
// status.putString(EXTRA_ERROR, "Failed to query KeyServer");
|
||||
// status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// t.setName("KeyExchange Download Thread");
|
||||
// t.setDaemon(true);
|
||||
// t.start();
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Import keys with mImportData
|
||||
*/
|
||||
public void importKeys() {
|
||||
if (mListFragment.getKeyBytes() != null || mListFragment.getImportFilename() != null) {
|
||||
Log.d(Constants.TAG, "importKeys started");
|
||||
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
// get selected key ids
|
||||
List<ImportKeysListEntry> listEntries = mListFragment.getData();
|
||||
ArrayList<Long> selectedKeyIds = new ArrayList<Long>();
|
||||
for (ImportKeysListEntry entry : listEntries) {
|
||||
if (entry.isSelected()) {
|
||||
selectedKeyIds.add(entry.keyId);
|
||||
}
|
||||
}
|
||||
|
||||
data.putSerializable(KeychainIntentService.IMPORT_KEY_LIST, selectedKeyIds);
|
||||
|
||||
if (mListFragment.getKeyBytes() != null) {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
|
||||
data.putByteArray(KeychainIntentService.IMPORT_BYTES, mListFragment.getKeyBytes());
|
||||
} else {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_FILE);
|
||||
data.putString(KeychainIntentService.IMPORT_FILENAME,
|
||||
mListFragment.getImportFilename());
|
||||
}
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after importing is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
R.string.progress_importing, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED);
|
||||
int updated = returnData
|
||||
.getInt(KeychainIntentService.RESULT_IMPORT_UPDATED);
|
||||
int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD);
|
||||
String toastMessage;
|
||||
if (added > 0 && updated > 0) {
|
||||
String addedStr = getResources().getQuantityString(
|
||||
R.plurals.keys_added_and_updated_1, added, added);
|
||||
String updatedStr = getResources().getQuantityString(
|
||||
R.plurals.keys_added_and_updated_2, updated, updated);
|
||||
toastMessage = addedStr + updatedStr;
|
||||
} else if (added > 0) {
|
||||
toastMessage = getResources().getQuantityString(R.plurals.keys_added,
|
||||
added, added);
|
||||
} else if (updated > 0) {
|
||||
toastMessage = getResources().getQuantityString(R.plurals.keys_updated,
|
||||
updated, updated);
|
||||
} else {
|
||||
toastMessage = getString(R.string.no_keys_added_or_updated);
|
||||
}
|
||||
Toast.makeText(ImportKeysActivity.this, toastMessage, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
if (bad > 0) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(
|
||||
ImportKeysActivity.this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
|
||||
alert.setMessage(ImportKeysActivity.this.getResources()
|
||||
.getQuantityString(R.plurals.bad_keys_encountered, bad, bad));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
alert.create().show();
|
||||
} else if (mDeleteAfterImport) {
|
||||
// everything went well, so now delete, if that was turned on
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mListFragment.getImportFilename());
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_nothing_import, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
public void importOnClick() {
|
||||
importKeys();
|
||||
}
|
||||
|
||||
// public void signAndUploadOnClick() {
|
||||
// // first, import!
|
||||
// // importOnClick(view);
|
||||
//
|
||||
// // TODO: implement sign and upload!
|
||||
// Toast.makeText(ImportKeysActivity.this, "Not implemented right now!", Toast.LENGTH_SHORT)
|
||||
// .show();
|
||||
// }
|
||||
|
||||
/**
|
||||
* NFC
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// Check to see that the Activity started due to an Android Beam
|
||||
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
|
||||
handleActionNdefDiscovered(getIntent());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC
|
||||
*/
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
// onResume gets called after this to handle the intent
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: Parses the NDEF Message from the intent and prints to the TextView
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
void handleActionNdefDiscovered(Intent intent) {
|
||||
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
|
||||
// only one message sent during the beam
|
||||
NdefMessage msg = (NdefMessage) rawMsgs[0];
|
||||
// record 0 contains the MIME type, record 1 is the AAR, if present
|
||||
byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload();
|
||||
|
||||
Intent importIntent = new Intent(this, ImportKeysActivity.class);
|
||||
importIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY);
|
||||
importIntent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES, receivedKeyringBytes);
|
||||
|
||||
handleActions(null, importIntent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
|
||||
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 com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class ImportKeysClipboardFragment extends Fragment {
|
||||
|
||||
private ImportKeysActivity mImportActivity;
|
||||
private BootstrapButton mButton;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ImportKeysClipboardFragment newInstance() {
|
||||
ImportKeysClipboardFragment frag = new ImportKeysClipboardFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.import_keys_clipboard_fragment, container, false);
|
||||
|
||||
mButton = (BootstrapButton) view.findViewById(R.id.import_clipboard_button);
|
||||
mButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
|
||||
String sendText = "";
|
||||
if (clipboardText != null)
|
||||
sendText = clipboardText.toString();
|
||||
mImportActivity.loadCallback(sendText.getBytes(), null);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mImportActivity = (ImportKeysActivity) getActivity();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class ImportKeysFileFragment extends Fragment {
|
||||
public static final String ARG_PATH = "path";
|
||||
|
||||
private ImportKeysActivity mImportActivity;
|
||||
private EditText mFilename;
|
||||
private BootstrapButton mBrowse;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ImportKeysFileFragment newInstance(String path) {
|
||||
ImportKeysFileFragment frag = new ImportKeysFileFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_PATH, path);
|
||||
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false);
|
||||
|
||||
mFilename = (EditText) view.findViewById(R.id.import_keys_file_input);
|
||||
mBrowse = (BootstrapButton) view.findViewById(R.id.import_keys_file_browse);
|
||||
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
// open .asc or .gpg files
|
||||
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
|
||||
// or gpg types!
|
||||
FileHelper.openFile(ImportKeysFileFragment.this, mFilename.getText().toString(),
|
||||
"*/*", Id.request.filename);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mImportActivity = (ImportKeysActivity) getActivity();
|
||||
|
||||
// set default path
|
||||
String path = Constants.path.APP_DIR + "/";
|
||||
if (getArguments() != null && getArguments().containsKey(ARG_PATH)) {
|
||||
path = getArguments().getString(ARG_PATH);
|
||||
}
|
||||
mFilename.setText(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode & 0xFFFF) {
|
||||
case Id.request.filename: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
String path = null;
|
||||
try {
|
||||
path = data.getData().getPath();
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
// set filename to edittext
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!", e);
|
||||
}
|
||||
|
||||
// load data
|
||||
mImportActivity.loadCallback(null, path);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.List;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
|
||||
public class ImportKeysListFragment extends SherlockListFragment implements
|
||||
LoaderManager.LoaderCallbacks<List<ImportKeysListEntry>> {
|
||||
private static final String ARG_FILENAME = "filename";
|
||||
private static final String ARG_BYTES = "bytes";
|
||||
|
||||
private Activity mActivity;
|
||||
private ImportKeysAdapter mAdapter;
|
||||
|
||||
private byte[] mKeyBytes;
|
||||
private String mImportFilename;
|
||||
|
||||
public byte[] getKeyBytes() {
|
||||
return mKeyBytes;
|
||||
}
|
||||
|
||||
public String getImportFilename() {
|
||||
return mImportFilename;
|
||||
}
|
||||
|
||||
public List<ImportKeysListEntry> getData() {
|
||||
return mAdapter.getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ImportKeysListFragment newInstance(byte[] bytes, String filename) {
|
||||
ImportKeysListFragment frag = new ImportKeysListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(ARG_BYTES, bytes);
|
||||
args.putString(ARG_FILENAME, filename);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mActivity = getActivity();
|
||||
|
||||
if (getArguments() != null) {
|
||||
mImportFilename = getArguments().getString(ARG_FILENAME);
|
||||
mKeyBytes = getArguments().getByteArray(ARG_BYTES);
|
||||
}
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(mActivity.getString(R.string.error_nothing_import));
|
||||
|
||||
// Create an empty adapter we will use to display the loaded data.
|
||||
mAdapter = new ImportKeysAdapter(mActivity);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
// give arguments to onCreateLoader()
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
super.onListItemClick(l, v, position, id);
|
||||
|
||||
// Select checkbox!
|
||||
// Update underlying data and notify adapter of change. The adapter will
|
||||
// update the view automatically.
|
||||
ImportKeysListEntry entry = mAdapter.getItem(position);
|
||||
entry.setSelected(!entry.isSelected());
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void loadNew(byte[] importData, String importFilename) {
|
||||
this.mKeyBytes = importData;
|
||||
this.mImportFilename = importFilename;
|
||||
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<List<ImportKeysListEntry>> onCreateLoader(int id, Bundle args) {
|
||||
InputData inputData = getInputData(mKeyBytes, mImportFilename);
|
||||
return new ImportKeysListLoader(mActivity, inputData);
|
||||
}
|
||||
|
||||
private InputData getInputData(byte[] importBytes, String importFilename) {
|
||||
InputData inputData = null;
|
||||
if (importBytes != null) {
|
||||
inputData = new InputData(new ByteArrayInputStream(importBytes), importBytes.length);
|
||||
} else if (importFilename != null) {
|
||||
try {
|
||||
inputData = new InputData(new FileInputStream(importFilename),
|
||||
importFilename.length());
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "Failed to init FileInputStream!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return inputData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<List<ImportKeysListEntry>> loader,
|
||||
List<ImportKeysListEntry> data) {
|
||||
Log.d(Constants.TAG, "data: " + data);
|
||||
|
||||
// swap in the real data!
|
||||
mAdapter.setData(data);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<List<ImportKeysListEntry>> loader) {
|
||||
// Clear the data in the adapter.
|
||||
mAdapter.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 org.sufficientlysecure.keychain.R;
|
||||
|
||||
import android.content.Intent;
|
||||
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 com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class ImportKeysNFCFragment extends Fragment {
|
||||
|
||||
private BootstrapButton mButton;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ImportKeysNFCFragment newInstance() {
|
||||
ImportKeysNFCFragment frag = new ImportKeysNFCFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.import_keys_nfc_fragment, container, false);
|
||||
|
||||
mButton = (BootstrapButton) view.findViewById(R.id.import_nfc_button);
|
||||
mButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// show nfc help
|
||||
Intent intent = new Intent(getActivity(), HelpActivity.class);
|
||||
intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, 1);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.Intent;
|
||||
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.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
public class ImportKeysQrCodeFragment extends Fragment {
|
||||
|
||||
private ImportKeysActivity mImportActivity;
|
||||
private BootstrapButton mButton;
|
||||
private TextView mText;
|
||||
private ProgressBar mProgress;
|
||||
|
||||
private String[] mScannedContent;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ImportKeysQrCodeFragment newInstance() {
|
||||
ImportKeysQrCodeFragment frag = new ImportKeysQrCodeFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.import_keys_qr_code_fragment, container, false);
|
||||
|
||||
mButton = (BootstrapButton) view.findViewById(R.id.import_qrcode_button);
|
||||
mText = (TextView) view.findViewById(R.id.import_qrcode_text);
|
||||
mProgress = (ProgressBar) view.findViewById(R.id.import_qrcode_progress);
|
||||
|
||||
mButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// scan using xzing's Barcode Scanner
|
||||
new IntentIntegratorSupportV4(ImportKeysQrCodeFragment.this).initiateScan();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mImportActivity = (ImportKeysActivity) getActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode & 0xFFFF) {
|
||||
case IntentIntegratorSupportV4.REQUEST_CODE: {
|
||||
IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode,
|
||||
resultCode, data);
|
||||
if (scanResult != null && scanResult.getFormatName() != null) {
|
||||
|
||||
Log.d(Constants.TAG, "scanResult content: " + scanResult.getContents());
|
||||
|
||||
// look if it's fingerprint only
|
||||
if (scanResult.getContents().toLowerCase(Locale.ENGLISH).startsWith("openpgp4fpr")) {
|
||||
importFingerprint(scanResult.getContents().toLowerCase(Locale.ENGLISH));
|
||||
return;
|
||||
}
|
||||
|
||||
// look if it is the whole key
|
||||
String[] parts = scanResult.getContents().split(",");
|
||||
if (parts.length == 3) {
|
||||
importParts(parts);
|
||||
return;
|
||||
}
|
||||
|
||||
// fail...
|
||||
Toast.makeText(getActivity(), R.string.import_qr_code_wrong, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void importFingerprint(String uri) {
|
||||
String fingerprint = uri.split(":")[1];
|
||||
|
||||
Log.d(Constants.TAG, "fingerprint: " + fingerprint);
|
||||
|
||||
if (fingerprint.length() < 16) {
|
||||
Toast.makeText(getActivity(), R.string.import_qr_code_too_short_fingerprint,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent queryIntent = new Intent(getActivity(), KeyServerQueryActivity.class);
|
||||
queryIntent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID);
|
||||
queryIntent.putExtra(KeyServerQueryActivity.EXTRA_FINGERPRINT, fingerprint);
|
||||
startActivity(queryIntent);
|
||||
}
|
||||
|
||||
private void importParts(String[] parts) {
|
||||
int counter = Integer.valueOf(parts[0]);
|
||||
int size = Integer.valueOf(parts[1]);
|
||||
String content = parts[2];
|
||||
|
||||
Log.d(Constants.TAG, "" + counter);
|
||||
Log.d(Constants.TAG, "" + size);
|
||||
Log.d(Constants.TAG, "" + content);
|
||||
|
||||
// first qr code -> setup
|
||||
if (counter == 0) {
|
||||
mScannedContent = new String[size];
|
||||
mProgress.setMax(size);
|
||||
mProgress.setVisibility(View.VISIBLE);
|
||||
mText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (mScannedContent == null || counter > mScannedContent.length) {
|
||||
Toast.makeText(getActivity(), R.string.import_qr_code_start_with_one, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// save scanned content
|
||||
mScannedContent[counter] = content;
|
||||
|
||||
// get missing numbers
|
||||
ArrayList<Integer> missing = new ArrayList<Integer>();
|
||||
for (int i = 0; i < mScannedContent.length; i++) {
|
||||
if (mScannedContent[i] == null) {
|
||||
missing.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
// update progress and text
|
||||
int alreadyScanned = mScannedContent.length - missing.size();
|
||||
mProgress.setProgress(alreadyScanned);
|
||||
|
||||
String missingString = "";
|
||||
for (int m : missing) {
|
||||
if (!missingString.equals("")) {
|
||||
missingString += ", ";
|
||||
}
|
||||
missingString += String.valueOf(m + 1);
|
||||
}
|
||||
|
||||
String missingText = getResources().getQuantityString(R.plurals.import_qr_code_missing,
|
||||
missing.size(), missingString);
|
||||
mText.setText(missingText);
|
||||
|
||||
// finished!
|
||||
if (missing.size() == 0) {
|
||||
mText.setText(R.string.import_qr_code_finished);
|
||||
String result = "";
|
||||
for (String in : mScannedContent) {
|
||||
result += in;
|
||||
}
|
||||
mImportActivity.loadCallback(result.getBytes(), null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 org.sufficientlysecure.keychain.R;
|
||||
|
||||
import android.content.Intent;
|
||||
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 com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class ImportKeysServerFragment extends Fragment {
|
||||
private BootstrapButton mButton;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ImportKeysServerFragment newInstance() {
|
||||
ImportKeysServerFragment frag = new ImportKeysServerFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.import_keys_keyserver_fragment, container, false);
|
||||
|
||||
mButton = (BootstrapButton) view.findViewById(R.id.import_keyserver_button);
|
||||
mButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// TODO: use fragment instead of activity, handle onresult here!
|
||||
startActivityForResult(new Intent(getActivity(), KeyServerQueryActivity.class), 0);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
public class KeyListPublicActivity extends DrawerActivity {
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
setContentView(R.layout.key_list_public_activity);
|
||||
|
||||
// now setup navigation drawer in DrawerActivity...
|
||||
setupDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getSupportMenuInflater().inflate(R.menu.key_list_public, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_public_import:
|
||||
Intent intentImport = new Intent(this, ImportKeysActivity.class);
|
||||
startActivityForResult(intentImport, 0);
|
||||
|
||||
return true;
|
||||
case R.id.menu_key_list_public_export:
|
||||
mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR
|
||||
+ "/pubexport.asc");
|
||||
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 java.util.Set;
|
||||
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyListPublicAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
|
||||
import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
/**
|
||||
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
|
||||
* StickyListHeaders library which does not extend upon ListView.
|
||||
*/
|
||||
public class KeyListPublicFragment extends Fragment implements AdapterView.OnItemClickListener,
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private KeyListPublicAdapter mAdapter;
|
||||
private StickyListHeadersListView mStickyList;
|
||||
|
||||
// empty layout
|
||||
private BootstrapButton mButtonEmptyCreate;
|
||||
private BootstrapButton mButtonEmptyImport;
|
||||
|
||||
/**
|
||||
* Load custom layout with StickyListView from library
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.key_list_public_fragment, container, false);
|
||||
|
||||
mButtonEmptyCreate = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_create);
|
||||
mButtonEmptyCreate.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(getActivity(), EditKeyActivity.class);
|
||||
intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
|
||||
intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
});
|
||||
|
||||
mButtonEmptyImport = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_import);
|
||||
mButtonEmptyImport.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intentImportFromFile = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
|
||||
startActivityForResult(intentImportFromFile, 0);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// mKeyListPublicActivity = (KeyListPublicActivity) getActivity();
|
||||
mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
|
||||
|
||||
mStickyList.setOnItemClickListener(this);
|
||||
mStickyList.setAreHeadersSticky(true);
|
||||
mStickyList.setDrawingListUnderStickyHeader(false);
|
||||
mStickyList.setFastScrollEnabled(true);
|
||||
try {
|
||||
mStickyList.setFastScrollAlwaysVisible(true);
|
||||
} catch (ApiLevelTooLowException e) {
|
||||
}
|
||||
|
||||
// this view is made visible if no data is available
|
||||
mStickyList.setEmptyView(getActivity().findViewById(R.id.empty));
|
||||
|
||||
/*
|
||||
* ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
|
||||
* available for Android >= 3.0
|
||||
*/
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
|
||||
mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
android.view.MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.key_list_public_multi, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
Set<Integer> positions = mAdapter.getCurrentCheckedPosition();
|
||||
|
||||
// get IDs for checked positions as long array
|
||||
long[] ids = new long[positions.size()];
|
||||
int i = 0;
|
||||
for (int pos : positions) {
|
||||
ids[i] = mAdapter.getItemId(pos);
|
||||
i++;
|
||||
}
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_public_multi_encrypt: {
|
||||
encrypt(ids);
|
||||
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_public_multi_delete: {
|
||||
showDeleteKeyDialog(ids);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
count = 0;
|
||||
mAdapter.clearSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
|
||||
boolean checked) {
|
||||
if (checked) {
|
||||
count++;
|
||||
mAdapter.setNewSelection(position, checked);
|
||||
} else {
|
||||
count--;
|
||||
mAdapter.removeSelection(position);
|
||||
}
|
||||
|
||||
String keysSelected = getResources().getQuantityString(
|
||||
R.plurals.key_list_selected_keys, count, count);
|
||||
mode.setTitle(keysSelected);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading
|
||||
// Start out with a progress indicator.
|
||||
// setListShown(false);
|
||||
|
||||
// Create an empty adapter we will use to display the loaded data.
|
||||
mAdapter = new KeyListPublicAdapter(getActivity(), null, Id.type.public_key, USER_ID_INDEX);
|
||||
mStickyList.setAdapter(mAdapter);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID };
|
||||
|
||||
static final int USER_ID_INDEX = 2;
|
||||
|
||||
static final String SORT_ORDER = UserIds.USER_ID + " ASC";
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildPublicKeyRingsUri();
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, SORT_ORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
mStickyList.setAdapter(mAdapter);
|
||||
|
||||
// NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading
|
||||
// The list should now be shown.
|
||||
// if (isResumed()) {
|
||||
// setListShown(true);
|
||||
// } else {
|
||||
// setListShownNoAnimation(true);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// 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.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* On click on item, start key view activity
|
||||
*/
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
Intent viewIntent = null;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
viewIntent = new Intent(getActivity(), ViewKeyActivity.class);
|
||||
} else {
|
||||
viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class);
|
||||
}
|
||||
viewIntent.setData(KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(id)));
|
||||
startActivity(viewIntent);
|
||||
}
|
||||
|
||||
public void encrypt(long[] keyRingRowIds) {
|
||||
// get master key ids from row ids
|
||||
long[] keyRingIds = new long[keyRingRowIds.length];
|
||||
for (int i = 0; i < keyRingRowIds.length; i++) {
|
||||
keyRingIds[i] = ProviderHelper.getPublicMasterKeyId(getActivity(), keyRingRowIds[i]);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingIds);
|
||||
// used instead of startActivity set actionbar based on callingPackage
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog to delete key
|
||||
*
|
||||
* @param keyRingRowIds
|
||||
*/
|
||||
public void showDeleteKeyDialog(long[] keyRingRowIds) {
|
||||
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null,
|
||||
keyRingRowIds, Id.type.public_key);
|
||||
|
||||
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
|
||||
public class KeyListSecretActivity extends DrawerActivity {
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
setContentView(R.layout.key_list_secret_activity);
|
||||
|
||||
// now setup navigation drawer in DrawerActivity...
|
||||
setupDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getSupportMenuInflater().inflate(R.menu.key_list_secret, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_secret_create:
|
||||
createKey();
|
||||
|
||||
return true;
|
||||
case R.id.menu_key_list_secret_create_expert:
|
||||
createKeyExpert();
|
||||
|
||||
return true;
|
||||
case R.id.menu_key_list_secret_export:
|
||||
mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.path.APP_DIR
|
||||
+ "/secexport.asc");
|
||||
|
||||
return true;
|
||||
case R.id.menu_key_list_secret_import:
|
||||
Intent intentImportFromFile = new Intent(this, ImportKeysActivity.class);
|
||||
intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE);
|
||||
startActivityForResult(intentImportFromFile, 0);
|
||||
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(this, EditKeyActivity.class);
|
||||
intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
|
||||
intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
private void createKeyExpert() {
|
||||
Intent intent = new Intent(this, EditKeyActivity.class);
|
||||
intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 java.util.Set;
|
||||
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyListSecretAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
|
||||
public class KeyListSecretFragment extends SherlockListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {
|
||||
|
||||
private KeyListSecretActivity mKeyListSecretActivity;
|
||||
private KeyListSecretAdapter mAdapter;
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mKeyListSecretActivity = (KeyListSecretActivity) getActivity();
|
||||
|
||||
getListView().setOnItemClickListener(this);
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.list_empty));
|
||||
|
||||
/*
|
||||
* ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
|
||||
* available for Android >= 3.0
|
||||
*/
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
|
||||
getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
android.view.MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.key_list_secret_multi, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
Set<Integer> positions = mAdapter.getCurrentCheckedPosition();
|
||||
|
||||
// get IDs for checked positions as long array
|
||||
long[] ids = new long[positions.size()];
|
||||
int i = 0;
|
||||
for (int pos : positions) {
|
||||
ids[i] = mAdapter.getItemId(pos);
|
||||
i++;
|
||||
}
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_public_multi_delete: {
|
||||
showDeleteKeyDialog(ids);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
count = 0;
|
||||
mAdapter.clearSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
|
||||
boolean checked) {
|
||||
if (checked) {
|
||||
count++;
|
||||
mAdapter.setNewSelection(position, checked);
|
||||
} else {
|
||||
count--;
|
||||
mAdapter.removeSelection(position);
|
||||
}
|
||||
|
||||
String keysSelected = getResources().getQuantityString(
|
||||
R.plurals.key_list_selected_keys, count, count);
|
||||
mode.setTitle(keysSelected);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// We have a menu item to show in action bar.
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// Create an empty adapter we will use to display the loaded data.
|
||||
mAdapter = new KeyListSecretAdapter(mKeyListSecretActivity, null, 0);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID };
|
||||
static final String SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC";
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
// First, pick the base URI to use depending on whether we are
|
||||
// currently filtering.
|
||||
Uri baseUri = KeyRings.buildSecretKeyRingsUri();
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, SORT_ORDER);
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// 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.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* On click on item, start key view activity
|
||||
*/
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
Intent editIntent = new Intent(mKeyListSecretActivity, EditKeyActivity.class);
|
||||
editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(id)));
|
||||
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
|
||||
startActivityForResult(editIntent, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog to delete key
|
||||
*
|
||||
* @param keyRingRowIds
|
||||
*/
|
||||
public void showDeleteKeyDialog(long[] keyRingRowIds) {
|
||||
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null,
|
||||
keyRingRowIds, Id.type.secret_key);
|
||||
|
||||
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.Toast;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.util.KeyServer.KeyInfo;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class KeyServerQueryActivity extends SherlockFragmentActivity {
|
||||
|
||||
// possible intent actions for this activity
|
||||
public static final String ACTION_LOOK_UP_KEY_ID = Constants.INTENT_PREFIX + "LOOK_UP_KEY_ID";
|
||||
|
||||
public static final String ACTION_LOOK_UP_KEY_ID_AND_RETURN = Constants.INTENT_PREFIX
|
||||
+ "LOOK_UP_KEY_ID_AND_RETURN";
|
||||
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_FINGERPRINT = "fingerprint";
|
||||
|
||||
public static final String RESULT_EXTRA_TEXT = "text";
|
||||
|
||||
private ListView mList;
|
||||
private EditText mQuery;
|
||||
private Button mSearch;
|
||||
private Spinner mKeyServer;
|
||||
|
||||
private KeyInfoListAdapter mAdapter;
|
||||
|
||||
private int mQueryType;
|
||||
private String mQueryString;
|
||||
private long mQueryId;
|
||||
|
||||
private volatile List<KeyInfo> mSearchResult;
|
||||
|
||||
private volatile String mKeyData;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
// app icon in Action Bar clicked; go home
|
||||
Intent intent = new Intent(this, KeyListPublicActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.key_server_query);
|
||||
|
||||
mQuery = (EditText) findViewById(R.id.query);
|
||||
mSearch = (Button) findViewById(R.id.btn_search);
|
||||
mList = (ListView) findViewById(R.id.list);
|
||||
mAdapter = new KeyInfoListAdapter(this);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
mKeyServer = (Spinner) findViewById(R.id.sign_key_keyserver);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||
.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mKeyServer.setAdapter(adapter);
|
||||
if (adapter.getCount() > 0) {
|
||||
mKeyServer.setSelection(0);
|
||||
} else {
|
||||
mSearch.setEnabled(false);
|
||||
}
|
||||
|
||||
mList.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapter, View view, int position, long keyId) {
|
||||
get(keyId);
|
||||
}
|
||||
});
|
||||
|
||||
mSearch.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String query = mQuery.getText().toString();
|
||||
search(query);
|
||||
}
|
||||
});
|
||||
mQuery.setOnEditorActionListener(new OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
String query = mQuery.getText().toString();
|
||||
search(query);
|
||||
return false; // FIXME This is a hack to hide a keyboard
|
||||
// after search http://tinyurl.com/pwdc3q9
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
if (ACTION_LOOK_UP_KEY_ID.equals(action) || ACTION_LOOK_UP_KEY_ID_AND_RETURN.equals(action)) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
|
||||
if (keyId != 0) {
|
||||
String query = "0x" + PgpKeyHelper.convertKeyToHex(keyId);
|
||||
mQuery.setText(query);
|
||||
search(query);
|
||||
}
|
||||
String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
|
||||
if (fingerprint != null) {
|
||||
fingerprint = "0x" + fingerprint;
|
||||
mQuery.setText(fingerprint);
|
||||
search(fingerprint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void search(String query) {
|
||||
mQueryType = Id.keyserver.search;
|
||||
mQueryString = query;
|
||||
mAdapter.setKeys(new ArrayList<KeyInfo>());
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
private void get(long keyId) {
|
||||
mQueryType = Id.keyserver.get;
|
||||
mQueryId = keyId;
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
Log.d(Constants.TAG, "start search with service");
|
||||
|
||||
// Send all information needed to service to query keys in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_QUERY_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
String server = (String) mKeyServer.getSelectedItem();
|
||||
data.putString(KeychainIntentService.QUERY_KEY_SERVER, server);
|
||||
|
||||
data.putInt(KeychainIntentService.QUERY_KEY_TYPE, mQueryType);
|
||||
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
data.putString(KeychainIntentService.QUERY_KEY_STRING, mQueryString);
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
data.putLong(KeychainIntentService.QUERY_KEY_ID, mQueryId);
|
||||
}
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after querying is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
R.string.progress_querying, ProgressDialog.STYLE_SPINNER) {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
mSearchResult = returnData
|
||||
.getParcelableArrayList(KeychainIntentService.RESULT_QUERY_KEY_SEARCH_RESULT);
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
mKeyData = returnData
|
||||
.getString(KeychainIntentService.RESULT_QUERY_KEY_DATA);
|
||||
}
|
||||
|
||||
// TODO: IMPROVE CODE!!! some global variables can be
|
||||
// avoided!!!
|
||||
if (mQueryType == Id.keyserver.search) {
|
||||
if (mSearchResult != null) {
|
||||
Toast.makeText(
|
||||
KeyServerQueryActivity.this,
|
||||
getResources().getQuantityString(R.plurals.keys_found,
|
||||
mSearchResult.size(), mSearchResult.size()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
mAdapter.setKeys(mSearchResult);
|
||||
}
|
||||
} else if (mQueryType == Id.keyserver.get) {
|
||||
Intent orgIntent = getIntent();
|
||||
if (ACTION_LOOK_UP_KEY_ID_AND_RETURN.equals(orgIntent.getAction())) {
|
||||
if (mKeyData != null) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(RESULT_EXTRA_TEXT, mKeyData);
|
||||
setResult(RESULT_OK, intent);
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
}
|
||||
finish();
|
||||
} else {
|
||||
if (mKeyData != null) {
|
||||
Intent intent = new Intent(KeyServerQueryActivity.this,
|
||||
ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES,
|
||||
mKeyData.getBytes());
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
public class KeyInfoListAdapter extends BaseAdapter {
|
||||
protected LayoutInflater mInflater;
|
||||
|
||||
protected Activity mActivity;
|
||||
|
||||
protected List<KeyInfo> mKeys;
|
||||
|
||||
public KeyInfoListAdapter(Activity activity) {
|
||||
mActivity = activity;
|
||||
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mKeys = new ArrayList<KeyInfo>();
|
||||
}
|
||||
|
||||
public void setKeys(List<KeyInfo> keys) {
|
||||
mKeys = keys;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mKeys.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mKeys.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return mKeys.get(position).keyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
KeyInfo keyInfo = mKeys.get(position);
|
||||
|
||||
View view = mInflater.inflate(R.layout.key_server_query_result_item, null);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknown_user_id);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.no_key);
|
||||
TextView algorithm = (TextView) view.findViewById(R.id.algorithm);
|
||||
algorithm.setText("");
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText("");
|
||||
|
||||
String userId = keyInfo.userIds.get(0);
|
||||
if (userId != null) {
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mainUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mainUserId.setText(userId);
|
||||
}
|
||||
|
||||
keyId.setText(PgpKeyHelper.convertKeyIdToHex(keyInfo.keyId));
|
||||
|
||||
if (mainUserIdRest.getText().length() == 0) {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
algorithm.setText("" + keyInfo.size + "/" + keyInfo.algorithm);
|
||||
|
||||
if (keyInfo.revoked != null) {
|
||||
status.setText("revoked");
|
||||
} else {
|
||||
status.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
LinearLayout ll = (LinearLayout) view.findViewById(R.id.list);
|
||||
if (keyInfo.userIds.size() == 1) {
|
||||
ll.setVisibility(View.GONE);
|
||||
} else {
|
||||
boolean first = true;
|
||||
boolean second = true;
|
||||
for (String uid : keyInfo.userIds) {
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (!second) {
|
||||
View sep = new View(mActivity);
|
||||
sep.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 1));
|
||||
sep.setBackgroundResource(android.R.drawable.divider_horizontal_dark);
|
||||
ll.addView(sep);
|
||||
}
|
||||
TextView uidView = (TextView) mInflater.inflate(
|
||||
R.layout.key_server_query_result_user_id, null);
|
||||
uidView.setText(uid);
|
||||
ll.addView(uidView);
|
||||
second = false;
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
/**
|
||||
* gpg --send-key activity
|
||||
*
|
||||
* Sends the selected public key to a key server
|
||||
*/
|
||||
public class KeyServerUploadActivity extends SherlockFragmentActivity {
|
||||
|
||||
// Not used in sourcode, but listed in AndroidManifest!
|
||||
public static final String ACTION_EXPORT_KEY_TO_SERVER = Constants.INTENT_PREFIX
|
||||
+ "EXPORT_KEY_TO_SERVER";
|
||||
|
||||
public static final String EXTRA_KEYRING_ROW_ID = "key_row_id";
|
||||
|
||||
private BootstrapButton mUploadButton;
|
||||
private Spinner mKeyServerSpinner;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.key_server_export);
|
||||
|
||||
mUploadButton = (BootstrapButton) findViewById(R.id.btn_export_to_server);
|
||||
mKeyServerSpinner = (Spinner) findViewById(R.id.sign_key_keyserver);
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||
.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mKeyServerSpinner.setAdapter(adapter);
|
||||
if (adapter.getCount() > 0) {
|
||||
mKeyServerSpinner.setSelection(0);
|
||||
} else {
|
||||
mUploadButton.setEnabled(false);
|
||||
}
|
||||
|
||||
mUploadButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
uploadKey();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void uploadKey() {
|
||||
// Send all information needed to service to upload key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
int keyRingId = getIntent().getIntExtra(EXTRA_KEYRING_ROW_ID, -1);
|
||||
data.putInt(KeychainIntentService.UPLOAD_KEY_KEYRING_ROW_ID, keyRingId);
|
||||
|
||||
String server = (String) mKeyServerSpinner.getSelectedItem();
|
||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, server);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
Toast.makeText(KeyServerUploadActivity.this, R.string.key_send_success,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockPreferenceActivity;
|
||||
|
||||
public class PreferencesActivity extends SherlockPreferenceActivity {
|
||||
private IntegerListPreference mPassPhraseCacheTtl = null;
|
||||
private IntegerListPreference mEncryptionAlgorithm = null;
|
||||
private IntegerListPreference mHashAlgorithm = null;
|
||||
private IntegerListPreference mMessageCompression = null;
|
||||
private IntegerListPreference mFileCompression = null;
|
||||
private CheckBoxPreference mAsciiArmour = null;
|
||||
private CheckBoxPreference mForceV3Signatures = null;
|
||||
private PreferenceScreen mKeyServerPreference = null;
|
||||
private Preferences mPreferences;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mPreferences = Preferences.getPreferences(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL);
|
||||
mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl());
|
||||
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry());
|
||||
mPassPhraseCacheTtl
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mPassPhraseCacheTtl.setValue(newValue.toString());
|
||||
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry());
|
||||
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM);
|
||||
int valueIds[] = { PGPEncryptedData.AES_128, PGPEncryptedData.AES_192,
|
||||
PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH,
|
||||
PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES,
|
||||
PGPEncryptedData.IDEA, };
|
||||
String entries[] = { "AES-128", "AES-192", "AES-256", "Blowfish", "Twofish", "CAST5",
|
||||
"DES", "Triple DES", "IDEA", };
|
||||
String values[] = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mEncryptionAlgorithm.setEntries(entries);
|
||||
mEncryptionAlgorithm.setEntryValues(values);
|
||||
mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm());
|
||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||
mEncryptionAlgorithm
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mEncryptionAlgorithm.setValue(newValue.toString());
|
||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||
mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue
|
||||
.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM);
|
||||
valueIds = new int[] { HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160,
|
||||
HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256,
|
||||
HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, };
|
||||
entries = new String[] { "MD5", "RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384",
|
||||
"SHA-512", };
|
||||
values = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mHashAlgorithm.setEntries(entries);
|
||||
mHashAlgorithm.setEntryValues(values);
|
||||
mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm());
|
||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||
mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mHashAlgorithm.setValue(newValue.toString());
|
||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||
mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mMessageCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION);
|
||||
valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip,
|
||||
Id.choice.compression.zlib, Id.choice.compression.bzip2, };
|
||||
entries = new String[] {
|
||||
getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")",
|
||||
"ZIP (" + getString(R.string.compression_fast) + ")",
|
||||
"ZLIB (" + getString(R.string.compression_fast) + ")",
|
||||
"BZIP2 (" + getString(R.string.compression_very_slow) + ")", };
|
||||
values = new String[valueIds.length];
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
values[i] = "" + valueIds[i];
|
||||
}
|
||||
mMessageCompression.setEntries(entries);
|
||||
mMessageCompression.setEntryValues(values);
|
||||
mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression());
|
||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||
mMessageCompression
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mMessageCompression.setValue(newValue.toString());
|
||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||
mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue
|
||||
.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mFileCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION);
|
||||
mFileCompression.setEntries(entries);
|
||||
mFileCompression.setEntryValues(values);
|
||||
mFileCompression.setValue("" + mPreferences.getDefaultFileCompression());
|
||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||
mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mFileCompression.setValue(newValue.toString());
|
||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||
mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR);
|
||||
mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
|
||||
mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mAsciiArmour.setChecked((Boolean) newValue);
|
||||
mPreferences.setDefaultAsciiArmour((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES);
|
||||
mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures());
|
||||
mForceV3Signatures
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mForceV3Signatures.setChecked((Boolean) newValue);
|
||||
mPreferences.setForceV3Signatures((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS);
|
||||
String servers[] = mPreferences.getKeyServers();
|
||||
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
|
||||
servers.length, servers.length));
|
||||
mKeyServerPreference
|
||||
.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent intent = new Intent(PreferencesActivity.this,
|
||||
PreferencesKeyServerActivity.class);
|
||||
intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
|
||||
mPreferences.getKeyServers());
|
||||
startActivityForResult(intent, Id.request.key_server_preference);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case Id.request.key_server_preference: {
|
||||
if (resultCode == RESULT_CANCELED || data == null) {
|
||||
return;
|
||||
}
|
||||
String servers[] = data
|
||||
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
|
||||
mPreferences.setKeyServers(servers);
|
||||
mKeyServerPreference.setSummary(getResources().getQuantityString(
|
||||
R.plurals.n_key_servers, servers.length, servers.length));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockActivity;
|
||||
|
||||
public class PreferencesKeyServerActivity extends SherlockActivity implements OnClickListener,
|
||||
EditorListener {
|
||||
|
||||
public static final String EXTRA_KEY_SERVERS = "key_servers";
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
private ViewGroup mEditors;
|
||||
private View mAdd;
|
||||
private TextView mTitle;
|
||||
private TextView mSummary;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// ok
|
||||
okClicked();
|
||||
}
|
||||
}, R.string.btn_do_not_save, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
cancelClicked();
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.key_server_preference);
|
||||
|
||||
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
mTitle = (TextView) findViewById(R.id.title);
|
||||
mSummary = (TextView) findViewById(R.id.summary);
|
||||
|
||||
mTitle.setText(R.string.label_key_servers);
|
||||
|
||||
mEditors = (ViewGroup) findViewById(R.id.editors);
|
||||
mAdd = findViewById(R.id.add);
|
||||
mAdd.setOnClickListener(this);
|
||||
|
||||
Intent intent = getIntent();
|
||||
String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
|
||||
if (servers != null) {
|
||||
for (int i = 0; i < servers.length; ++i) {
|
||||
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(
|
||||
R.layout.key_server_editor, mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
view.setValue(servers[i]);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeleted(Editor editor) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
|
||||
mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void okClicked() {
|
||||
Intent data = new Intent();
|
||||
Vector<String> servers = new Vector<String>();
|
||||
for (int i = 0; i < mEditors.getChildCount(); ++i) {
|
||||
KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i);
|
||||
String tmp = editor.getValue();
|
||||
if (tmp.length() > 0) {
|
||||
servers.add(tmp);
|
||||
}
|
||||
}
|
||||
String[] dummy = new String[0];
|
||||
data.putExtra(EXTRA_KEY_SERVERS, servers.toArray(dummy));
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
|
||||
public class SelectPublicKeyActivity extends SherlockFragmentActivity {
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_SELECT_PUBLIC_KEYS = Constants.INTENT_PREFIX
|
||||
+ "SELECT_PUBLIC_KEYRINGS";
|
||||
|
||||
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
|
||||
|
||||
public static final String RESULT_EXTRA_MASTER_KEY_IDS = "master_key_ids";
|
||||
public static final String RESULT_EXTRA_USER_IDS = "user_ids";
|
||||
|
||||
SelectPublicKeyFragment mSelectFragment;
|
||||
|
||||
long selectedMasterKeyIds[];
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// ok
|
||||
okClicked();
|
||||
}
|
||||
}, R.string.btn_do_not_save, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
cancelClicked();
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.select_public_key_activity);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
handleIntent(getIntent());
|
||||
|
||||
// Check that the activity is using the layout version with
|
||||
// the fragment_container FrameLayout
|
||||
if (findViewById(R.id.select_public_key_fragment_container) != null) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds);
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.select_public_key_fragment_container, mSelectFragment).commit();
|
||||
}
|
||||
|
||||
// TODO: reimplement!
|
||||
// mFilterLayout = findViewById(R.id.layout_filter);
|
||||
// mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
// mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
//
|
||||
// mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
// public void onClick(View v) {
|
||||
// handleIntent(new Intent());
|
||||
// }
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
// TODO: reimplement search!
|
||||
|
||||
// String searchString = null;
|
||||
// if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
// searchString = intent.getStringExtra(SearchManager.QUERY);
|
||||
// if (searchString != null && searchString.trim().length() == 0) {
|
||||
// searchString = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (searchString == null) {
|
||||
// mFilterLayout.setVisibility(View.GONE);
|
||||
// } else {
|
||||
// mFilterLayout.setVisibility(View.VISIBLE);
|
||||
// mFilterInfo.setText(getString(R.string.filterInfo, searchString));
|
||||
// }
|
||||
|
||||
// preselected master keys
|
||||
selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void okClicked() {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_IDS, mSelectFragment.getSelectedMasterKeyIds());
|
||||
data.putExtra(RESULT_EXTRA_USER_IDS, mSelectFragment.getSelectedUserIds());
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
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.widget.ListView;
|
||||
|
||||
public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids";
|
||||
|
||||
private Activity mActivity;
|
||||
private SelectKeyCursorAdapter mAdapter;
|
||||
private ListView mListView;
|
||||
|
||||
private long mSelectedMasterKeyIds[];
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static SelectPublicKeyFragment newInstance(long[] preselectedKeyIds) {
|
||||
SelectPublicKeyFragment frag = new SelectPublicKeyFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putLongArray(ARG_PRESELECTED_KEY_IDS, preselectedKeyIds);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mActivity = getSherlockActivity();
|
||||
mListView = getListView();
|
||||
|
||||
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.list_empty));
|
||||
|
||||
mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.public_key);
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects items based on master key ids in list view
|
||||
*
|
||||
* @param masterKeyIds
|
||||
*/
|
||||
private void preselectMasterKeyIds(long[] masterKeyIds) {
|
||||
if (masterKeyIds != null) {
|
||||
for (int i = 0; i < mListView.getCount(); ++i) {
|
||||
long keyId = mAdapter.getMasterKeyId(i);
|
||||
for (int j = 0; j < masterKeyIds.length; ++j) {
|
||||
if (keyId == masterKeyIds[j]) {
|
||||
mListView.setItemChecked(i, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all selected master key ids
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public long[] getSelectedMasterKeyIds() {
|
||||
// mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key
|
||||
// ids!
|
||||
Vector<Long> vector = new Vector<Long>();
|
||||
for (int i = 0; i < mListView.getCount(); ++i) {
|
||||
if (mListView.isItemChecked(i)) {
|
||||
vector.add(mAdapter.getMasterKeyId(i));
|
||||
}
|
||||
}
|
||||
|
||||
// convert to long array
|
||||
long[] selectedMasterKeyIds = new long[vector.size()];
|
||||
for (int i = 0; i < vector.size(); ++i) {
|
||||
selectedMasterKeyIds[i] = vector.get(i);
|
||||
}
|
||||
|
||||
return selectedMasterKeyIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all selected user ids
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getSelectedUserIds() {
|
||||
Vector<String> userIds = new Vector<String>();
|
||||
for (int i = 0; i < mListView.getCount(); ++i) {
|
||||
if (mListView.isItemChecked(i)) {
|
||||
userIds.add((String) mAdapter.getUserId(i));
|
||||
}
|
||||
}
|
||||
|
||||
// make empty array to not return null
|
||||
String userIdArray[] = new String[0];
|
||||
return userIds.toArray(userIdArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildPublicKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
long now = new Date().getTime() / 1000;
|
||||
String[] projection = new String[] {
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID,
|
||||
"(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
|
||||
+ Keys.CAN_ENCRYPT + " = '1') AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys."
|
||||
+ Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '"
|
||||
+ now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys."
|
||||
+ Keys.EXPIRY + " >= '" + now + "')) AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
|
||||
String inMasterKeyList = null;
|
||||
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
|
||||
inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN (";
|
||||
for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) {
|
||||
if (i != 0) {
|
||||
inMasterKeyList += ", ";
|
||||
}
|
||||
inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]);
|
||||
}
|
||||
inMasterKeyList += ")";
|
||||
}
|
||||
|
||||
// if (searchString != null && searchString.trim().length() > 0) {
|
||||
// String[] chunks = searchString.trim().split(" +");
|
||||
// qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME
|
||||
// + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "."
|
||||
// + Keys._ID);
|
||||
// for (int i = 0; i < chunks.length; ++i) {
|
||||
// qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
|
||||
// qb.appendWhereEscapeString("%" + chunks[i] + "%");
|
||||
// }
|
||||
// qb.appendWhere("))");
|
||||
//
|
||||
// if (inIdList != null) {
|
||||
// qb.appendWhere(" OR (" + inIdList + ")");
|
||||
// }
|
||||
// }
|
||||
|
||||
String orderBy = UserIds.USER_ID + " ASC";
|
||||
if (inMasterKeyList != null) {
|
||||
// sort by selected master keys
|
||||
orderBy = inMasterKeyList + " DESC, " + orderBy;
|
||||
}
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, projection, null, null, orderBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
|
||||
// preselect given master keys
|
||||
preselectMasterKeyIds(mSelectedMasterKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// 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.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
|
||||
public class SelectSecretKeyActivity extends SherlockFragmentActivity {
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_SELECT_SECRET_KEY = Constants.INTENT_PREFIX
|
||||
+ "SELECT_SECRET_KEYRING";
|
||||
|
||||
public static final String EXTRA_FILTER_CERTIFY = "filter_certify";
|
||||
|
||||
public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String RESULT_EXTRA_USER_ID = "user_id";
|
||||
|
||||
private boolean mFilterCertify = false;
|
||||
private SelectSecretKeyFragment mSelectFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.select_secret_key_activity);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
// TODO: reimplement!
|
||||
// mFilterLayout = findViewById(R.id.layout_filter);
|
||||
// mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
// mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
//
|
||||
// mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
// public void onClick(View v) {
|
||||
// handleIntent(new Intent());
|
||||
// }
|
||||
// });
|
||||
|
||||
mFilterCertify = getIntent().getBooleanExtra(EXTRA_FILTER_CERTIFY, false);
|
||||
|
||||
handleIntent(getIntent());
|
||||
|
||||
// Check that the activity is using the layout version with
|
||||
// the fragment_container FrameLayout
|
||||
if (findViewById(R.id.select_secret_key_fragment_container) != null) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
mSelectFragment = SelectSecretKeyFragment.newInstance(mFilterCertify);
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.select_secret_key_fragment_container, mSelectFragment).commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is executed by SelectSecretKeyFragment after clicking on an item
|
||||
*
|
||||
* @param masterKeyId
|
||||
* @param userId
|
||||
*/
|
||||
public void afterListSelection(long masterKeyId, String userId) {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, masterKeyId);
|
||||
data.putExtra(RESULT_EXTRA_USER_ID, (String) userId);
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
// TODO: reimplement!
|
||||
|
||||
// String searchString = null;
|
||||
// if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
// searchString = intent.getStringExtra(SearchManager.QUERY);
|
||||
// if (searchString != null && searchString.trim().length() == 0) {
|
||||
// searchString = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (searchString == null) {
|
||||
// mFilterLayout.setVisibility(View.GONE);
|
||||
// } else {
|
||||
// mFilterLayout.setVisibility(View.VISIBLE);
|
||||
// mFilterInfo.setText(getString(R.string.filterInfo, searchString));
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// TODO: reimplement!
|
||||
// menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon(
|
||||
// android.R.drawable.ic_menu_search);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
|
||||
|
||||
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.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockListFragment;
|
||||
|
||||
public class SelectSecretKeyFragment extends SherlockListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private SelectSecretKeyActivity mActivity;
|
||||
private SelectKeyCursorAdapter mAdapter;
|
||||
private ListView mListView;
|
||||
|
||||
private boolean mFilterCertify;
|
||||
|
||||
private static final String ARG_FILTER_CERTIFY = "filter_certify";
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static SelectSecretKeyFragment newInstance(boolean filterCertify) {
|
||||
SelectSecretKeyFragment frag = new SelectSecretKeyFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putBoolean(ARG_FILTER_CERTIFY, filterCertify);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mFilterCertify = getArguments().getBoolean(ARG_FILTER_CERTIFY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mActivity = (SelectSecretKeyActivity) getSherlockActivity();
|
||||
mListView = getListView();
|
||||
|
||||
mListView.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
long masterKeyId = mAdapter.getMasterKeyId(position);
|
||||
String userId = mAdapter.getUserId(position);
|
||||
|
||||
// return data to activity, which results in finishing it
|
||||
mActivity.afterListSelection(masterKeyId, userId);
|
||||
}
|
||||
});
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.list_empty));
|
||||
|
||||
mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.secret_key);
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildSecretKeyRingsUri();
|
||||
|
||||
String CapFilter = null;
|
||||
if (mFilterCertify) {
|
||||
CapFilter = "(cert > 0)";
|
||||
}
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
long now = new Date().getTime() / 1000;
|
||||
String[] projection = new String[] {
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID,
|
||||
"(SELECT COUNT(cert_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS cert_keys WHERE cert_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND cert_keys."
|
||||
+ Keys.CAN_CERTIFY + " = '1') AS cert",
|
||||
"(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
|
||||
+ Keys.CAN_SIGN + " = '1') AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
|
||||
+ Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_SIGN
|
||||
+ " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
|
||||
+ "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
|
||||
+ " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
|
||||
// if (searchString != null && searchString.trim().length() > 0) {
|
||||
// String[] chunks = searchString.trim().split(" +");
|
||||
// qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME
|
||||
// + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "."
|
||||
// + Keys._ID);
|
||||
// for (int i = 0; i < chunks.length; ++i) {
|
||||
// qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
|
||||
// qb.appendWhereEscapeString("%" + chunks[i] + "%");
|
||||
// }
|
||||
// qb.appendWhere(")");
|
||||
// }
|
||||
|
||||
String orderBy = UserIds.USER_ID + " ASC";
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, projection, CapFilter, null, orderBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// 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.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
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.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class SelectSecretKeyLayoutFragment extends Fragment {
|
||||
|
||||
private TextView mKeyUserId;
|
||||
private TextView mKeyUserIdRest;
|
||||
private BootstrapButton mSelectKeyButton;
|
||||
|
||||
private SelectSecretKeyCallback mCallback;
|
||||
|
||||
private static final int REQUEST_CODE_SELECT_KEY = 8882;
|
||||
|
||||
public interface SelectSecretKeyCallback {
|
||||
void onKeySelected(long secretKeyId);
|
||||
}
|
||||
|
||||
public void setCallback(SelectSecretKeyCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public void selectKey(long secretKeyId) {
|
||||
if (secretKeyId == Id.key.none) {
|
||||
mKeyUserId.setText(R.string.api_settings_no_key);
|
||||
mKeyUserIdRest.setText("");
|
||||
} else {
|
||||
String uid = getResources().getString(R.string.unknown_user_id);
|
||||
String uidExtra = "";
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
|
||||
getActivity(), secretKeyId);
|
||||
if (keyRing != null) {
|
||||
PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing);
|
||||
if (key != null) {
|
||||
String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key);
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
uid = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
uidExtra = "<" + chunks[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
mKeyUserId.setText(uid);
|
||||
mKeyUserIdRest.setText(uidExtra);
|
||||
}
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
mKeyUserId.requestFocus();
|
||||
mKeyUserId.setError(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.select_secret_key_layout_fragment, container, false);
|
||||
|
||||
mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id);
|
||||
mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest);
|
||||
mSelectKeyButton = (BootstrapButton) view
|
||||
.findViewById(R.id.select_secret_key_select_key_button);
|
||||
mSelectKeyButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startSelectKeyActivity();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void startSelectKeyActivity() {
|
||||
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
|
||||
startActivityForResult(intent, REQUEST_CODE_SELECT_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode & 0xFFFF) {
|
||||
case REQUEST_CODE_SELECT_KEY: {
|
||||
long secretKeyId;
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Bundle bundle = data.getExtras();
|
||||
secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
|
||||
|
||||
selectKey(secretKeyId);
|
||||
|
||||
// remove displayed errors
|
||||
mKeyUserId.setError(null);
|
||||
|
||||
// give value back to callback
|
||||
mCallback.onKeySelected(secretKeyId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
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.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
/**
|
||||
* gpg --sign-key
|
||||
*
|
||||
* signs the specified public key with the specified secret master key
|
||||
*/
|
||||
public class SignKeyActivity extends SherlockFragmentActivity implements
|
||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
|
||||
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
|
||||
private long mPubKeyId = 0;
|
||||
private long mMasterKeyId = 0;
|
||||
|
||||
private BootstrapButton mSignButton;
|
||||
private CheckBox mUploadKeyCheckbox;
|
||||
private Spinner mSelectKeyserverSpinner;
|
||||
|
||||
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.sign_key_activity);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.sign_key_select_key_fragment);
|
||||
mSelectKeyFragment.setCallback(this);
|
||||
|
||||
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.sign_key_keyserver);
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||
.getKeyServers());
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mSelectKeyserverSpinner.setAdapter(adapter);
|
||||
|
||||
mUploadKeyCheckbox = (CheckBox) findViewById(R.id.sign_key_upload_checkbox);
|
||||
if (!mUploadKeyCheckbox.isChecked()) {
|
||||
mSelectKeyserverSpinner.setEnabled(false);
|
||||
} else {
|
||||
mSelectKeyserverSpinner.setEnabled(true);
|
||||
}
|
||||
|
||||
mUploadKeyCheckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (!isChecked) {
|
||||
mSelectKeyserverSpinner.setEnabled(false);
|
||||
} else {
|
||||
mSelectKeyserverSpinner.setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mSignButton = (BootstrapButton) findViewById(R.id.sign_key_sign_button);
|
||||
mSignButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mPubKeyId != 0) {
|
||||
if (mMasterKeyId == 0) {
|
||||
mSelectKeyFragment.setError(getString(R.string.select_key_to_sign));
|
||||
} else {
|
||||
initiateSigning();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mPubKeyId = getIntent().getLongExtra(EXTRA_KEY_ID, 0);
|
||||
if (mPubKeyId == 0) {
|
||||
Log.e(Constants.TAG, "No pub key id given!");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void showPassphraseDialog(final long secretKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
startSigning();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
|
||||
messenger, secretKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles the UI bits of the signing process on the UI thread
|
||||
*/
|
||||
private void initiateSigning() {
|
||||
PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, mPubKeyId);
|
||||
if (pubring != null) {
|
||||
// if we have already signed this key, dont bother doing it again
|
||||
boolean alreadySigned = false;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> itr = pubring.getPublicKey(mPubKeyId).getSignatures();
|
||||
while (itr.hasNext()) {
|
||||
PGPSignature sig = itr.next();
|
||||
if (sig.getKeyID() == mMasterKeyId) {
|
||||
alreadySigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadySigned) {
|
||||
/*
|
||||
* get the user's passphrase for this key (if required)
|
||||
*/
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
|
||||
if (passphrase == null) {
|
||||
showPassphraseDialog(mMasterKeyId);
|
||||
return; // bail out; need to wait until the user has entered the passphrase
|
||||
// before trying again
|
||||
} else {
|
||||
startSigning();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(this, R.string.key_has_already_been_signed, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kicks off the actual signing process on a background thread
|
||||
*/
|
||||
private void startSigning() {
|
||||
// Send all information needed to service to sign key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putLong(KeychainIntentService.SIGN_KEY_MASTER_KEY_ID, mMasterKeyId);
|
||||
data.putLong(KeychainIntentService.SIGN_KEY_PUB_KEY_ID, mPubKeyId);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after signing is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
R.string.progress_signing, ProgressDialog.STYLE_SPINNER) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
Toast.makeText(SignKeyActivity.this, R.string.key_sign_success,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
// check if we need to send the key to the server or not
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
/*
|
||||
* upload the newly signed key to the key server
|
||||
*/
|
||||
uploadKey();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
private void uploadKey() {
|
||||
// Send all information needed to service to upload key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putLong(KeychainIntentService.UPLOAD_KEY_KEYRING_ROW_ID, mPubKeyId);
|
||||
|
||||
Spinner keyServer = (Spinner) findViewById(R.id.sign_key_keyserver);
|
||||
String server = (String) keyServer.getSelectedItem();
|
||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, server);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
||||
Toast.makeText(SignKeyActivity.this, R.string.key_send_success,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
finish();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* callback from select key fragment
|
||||
*/
|
||||
@Override
|
||||
public void onKeySelected(long secretKeyId) {
|
||||
mMasterKeyId = secretKeyId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2013 Bahtiar 'kalkin' Gadimov
|
||||
*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.actionbarsherlock.view.Menu;
|
||||
import com.actionbarsherlock.view.MenuItem;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class ViewKeyActivity extends SherlockFragmentActivity implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
protected Uri mDataUri;
|
||||
|
||||
private TextView mName;
|
||||
private TextView mEmail;
|
||||
private TextView mComment;
|
||||
private TextView mAlgorithm;
|
||||
private TextView mKeyId;
|
||||
private TextView mExpiry;
|
||||
private TextView mCreation;
|
||||
private TextView mFingerprint;
|
||||
private BootstrapButton mActionEncrypt;
|
||||
|
||||
private ListView mUserIds;
|
||||
private ListView mKeys;
|
||||
|
||||
private static final int LOADER_ID_KEYRING = 0;
|
||||
private static final int LOADER_ID_USER_IDS = 1;
|
||||
private static final int LOADER_ID_KEYS = 2;
|
||||
private ViewKeyUserIdsAdapter mUserIdsAdapter;
|
||||
private ViewKeyKeysAdapter mKeysAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setIcon(android.R.color.transparent);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
|
||||
setContentView(R.layout.view_key_activity);
|
||||
|
||||
mName = (TextView) findViewById(R.id.name);
|
||||
mEmail = (TextView) findViewById(R.id.email);
|
||||
mComment = (TextView) findViewById(R.id.comment);
|
||||
mKeyId = (TextView) findViewById(R.id.key_id);
|
||||
mAlgorithm = (TextView) findViewById(R.id.algorithm);
|
||||
mCreation = (TextView) findViewById(R.id.creation);
|
||||
mExpiry = (TextView) findViewById(R.id.expiry);
|
||||
mFingerprint = (TextView) findViewById(R.id.fingerprint);
|
||||
mActionEncrypt = (BootstrapButton) findViewById(R.id.action_encrypt);
|
||||
mUserIds = (ListView) findViewById(R.id.user_ids);
|
||||
mKeys = (ListView) findViewById(R.id.keys);
|
||||
|
||||
Intent intent = getIntent();
|
||||
mDataUri = intent.getData();
|
||||
if (mDataUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
|
||||
finish();
|
||||
return;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mDataUri);
|
||||
loadData(mDataUri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getSupportMenuInflater().inflate(R.menu.key_view, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
Intent homeIntent = new Intent(this, KeyListPublicActivity.class);
|
||||
homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(homeIntent);
|
||||
return true;
|
||||
case R.id.menu_key_view_update:
|
||||
updateFromKeyserver(mDataUri);
|
||||
return true;
|
||||
case R.id.menu_key_view_sign:
|
||||
signKey(mDataUri);
|
||||
return true;
|
||||
case R.id.menu_key_view_export_keyserver:
|
||||
uploadToKeyserver(mDataUri);
|
||||
return true;
|
||||
case R.id.menu_key_view_export_file:
|
||||
mExportHelper.showExportKeysDialog(mDataUri, Id.type.public_key, Constants.path.APP_DIR
|
||||
+ "/pubexport.asc");
|
||||
return true;
|
||||
case R.id.menu_key_view_share_default_fingerprint:
|
||||
shareKey(mDataUri, true);
|
||||
return true;
|
||||
case R.id.menu_key_view_share_default:
|
||||
shareKey(mDataUri, false);
|
||||
return true;
|
||||
case R.id.menu_key_view_share_qr_code_fingerprint:
|
||||
shareKeyQrCode(mDataUri, true);
|
||||
return true;
|
||||
case R.id.menu_key_view_share_qr_code:
|
||||
shareKeyQrCode(mDataUri, false);
|
||||
return true;
|
||||
case R.id.menu_key_view_share_nfc:
|
||||
shareNfc();
|
||||
return true;
|
||||
case R.id.menu_key_view_share_clipboard:
|
||||
copyToClipboard(mDataUri);
|
||||
return true;
|
||||
case R.id.menu_key_view_delete: {
|
||||
// Message is received after key is deleted
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mExportHelper.deleteKey(mDataUri, Id.type.public_key, returnHandler);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void loadData(Uri dataUri) {
|
||||
mActionEncrypt.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// TODO: don't get object here!!! solve this differently!
|
||||
PGPPublicKeyRing ring = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(
|
||||
ViewKeyActivity.this, mDataUri);
|
||||
PGPPublicKey publicKey = ring.getPublicKey();
|
||||
|
||||
long[] encryptionKeyIds = new long[] { publicKey.getKeyID() };
|
||||
Intent intent = new Intent(ViewKeyActivity.this, EncryptActivity.class);
|
||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
|
||||
// used instead of startActivity set actionbar based on callingPackage
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
});
|
||||
|
||||
mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0);
|
||||
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
// mUserIds.setEmptyView(findViewById(android.R.id.empty));
|
||||
// mUserIds.setClickable(true);
|
||||
// mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
// @Override
|
||||
// public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) {
|
||||
// }
|
||||
// });
|
||||
|
||||
mKeysAdapter = new ViewKeyKeysAdapter(this, null, 0);
|
||||
|
||||
mKeys.setAdapter(mKeysAdapter);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
|
||||
}
|
||||
|
||||
static final String[] KEYRING_PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID };
|
||||
static final int KEYRING_INDEX_ID = 0;
|
||||
static final int KEYRING_INDEX_MASTER_KEY_ID = 1;
|
||||
static final int KEYRING_INDEX_USER_ID = 2;
|
||||
|
||||
static final String[] USER_IDS_PROJECTION = new String[] { UserIds._ID, UserIds.USER_ID,
|
||||
UserIds.RANK, };
|
||||
// not the main user id
|
||||
static final String USER_IDS_SELECTION = UserIds.RANK + " > 0 ";
|
||||
static final String USER_IDS_SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC";
|
||||
|
||||
static final String[] KEYS_PROJECTION = new String[] { Keys._ID, Keys.KEY_ID,
|
||||
Keys.IS_MASTER_KEY, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.CAN_CERTIFY, Keys.CAN_SIGN,
|
||||
Keys.CAN_ENCRYPT, Keys.CREATION, Keys.EXPIRY };
|
||||
static final String KEYS_SORT_ORDER = Keys.RANK + " ASC";
|
||||
static final int KEYS_INDEX_ID = 0;
|
||||
static final int KEYS_INDEX_KEY_ID = 1;
|
||||
static final int KEYS_INDEX_IS_MASTER_KEY = 2;
|
||||
static final int KEYS_INDEX_ALGORITHM = 3;
|
||||
static final int KEYS_INDEX_KEY_SIZE = 4;
|
||||
static final int KEYS_INDEX_CAN_CERTIFY = 5;
|
||||
static final int KEYS_INDEX_CAN_SIGN = 6;
|
||||
static final int KEYS_INDEX_CAN_ENCRYPT = 7;
|
||||
static final int KEYS_INDEX_CREATION = 8;
|
||||
static final int KEYS_INDEX_EXPIRY = 9;
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
switch (id) {
|
||||
case LOADER_ID_KEYRING: {
|
||||
Uri baseUri = mDataUri;
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(this, baseUri, KEYRING_PROJECTION, null, null, null);
|
||||
}
|
||||
case LOADER_ID_USER_IDS: {
|
||||
Uri baseUri = UserIds.buildUserIdsUri(mDataUri);
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, USER_IDS_SELECTION, null,
|
||||
USER_IDS_SORT_ORDER);
|
||||
}
|
||||
case LOADER_ID_KEYS: {
|
||||
Uri baseUri = Keys.buildKeysUri(mDataUri);
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(this, baseUri, KEYS_PROJECTION, null, null, KEYS_SORT_ORDER);
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_KEYRING:
|
||||
if (data.moveToFirst()) {
|
||||
// get name, email, and comment from USER_ID
|
||||
String[] mainUserId = PgpKeyHelper.splitUserId(data
|
||||
.getString(KEYRING_INDEX_USER_ID));
|
||||
setTitle(mainUserId[0]);
|
||||
mName.setText(mainUserId[0]);
|
||||
mEmail.setText(mainUserId[1]);
|
||||
mComment.setText(mainUserId[2]);
|
||||
}
|
||||
|
||||
break;
|
||||
case LOADER_ID_USER_IDS:
|
||||
mUserIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
case LOADER_ID_KEYS:
|
||||
// the first key here is our master key
|
||||
if (data.moveToFirst()) {
|
||||
// get key id from MASTER_KEY_ID
|
||||
long keyId = data.getLong(KEYS_INDEX_KEY_ID);
|
||||
|
||||
String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
mKeyId.setText(keyIdStr);
|
||||
|
||||
// get creation date from CREATION
|
||||
if (data.isNull(KEYS_INDEX_CREATION)) {
|
||||
mCreation.setText(R.string.none);
|
||||
} else {
|
||||
Date creationDate = new Date(data.getLong(KEYS_INDEX_CREATION) * 1000);
|
||||
|
||||
mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format(
|
||||
creationDate));
|
||||
}
|
||||
|
||||
// get creation date from EXPIRY
|
||||
if (data.isNull(KEYS_INDEX_EXPIRY)) {
|
||||
mExpiry.setText(R.string.none);
|
||||
} else {
|
||||
Date expiryDate = new Date(data.getLong(KEYS_INDEX_EXPIRY) * 1000);
|
||||
|
||||
mExpiry.setText(DateFormat.getDateFormat(getApplicationContext()).format(
|
||||
expiryDate));
|
||||
}
|
||||
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
|
||||
data.getInt(KEYS_INDEX_ALGORITHM), data.getInt(KEYS_INDEX_KEY_SIZE));
|
||||
mAlgorithm.setText(algorithmStr);
|
||||
|
||||
// TODO: Can this be done better? fingerprint in db?
|
||||
String fingerprint = PgpKeyHelper.getFingerPrint(this, keyId);
|
||||
|
||||
fingerprint = fingerprint.replace(" ", "\n");
|
||||
mFingerprint.setText(fingerprint);
|
||||
|
||||
// TODO: get image with getUserAttributes() on key and then
|
||||
// PGPUserAttributeSubpacketVector
|
||||
}
|
||||
|
||||
mKeysAdapter.swapCursor(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_KEYRING:
|
||||
// TODO?
|
||||
break;
|
||||
case LOADER_ID_USER_IDS:
|
||||
mUserIdsAdapter.swapCursor(null);
|
||||
break;
|
||||
case LOADER_ID_KEYS:
|
||||
mKeysAdapter.swapCursor(null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadToKeyserver(Uri dataUri) {
|
||||
long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment());
|
||||
|
||||
Intent uploadIntent = new Intent(this, KeyServerUploadActivity.class);
|
||||
uploadIntent.setAction(KeyServerUploadActivity.ACTION_EXPORT_KEY_TO_SERVER);
|
||||
uploadIntent.putExtra(KeyServerUploadActivity.EXTRA_KEYRING_ROW_ID, (int) keyRingRowId);
|
||||
startActivityForResult(uploadIntent, Id.request.export_to_server);
|
||||
}
|
||||
|
||||
private void updateFromKeyserver(Uri dataUri) {
|
||||
long updateKeyId = 0;
|
||||
PGPPublicKeyRing updateRing = (PGPPublicKeyRing) ProviderHelper
|
||||
.getPGPKeyRing(this, dataUri);
|
||||
|
||||
if (updateRing != null) {
|
||||
updateKeyId = PgpKeyHelper.getMasterKey(updateRing).getKeyID();
|
||||
}
|
||||
if (updateKeyId == 0) {
|
||||
Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent queryIntent = new Intent(this, KeyServerQueryActivity.class);
|
||||
queryIntent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID_AND_RETURN);
|
||||
queryIntent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, updateKeyId);
|
||||
|
||||
// TODO: lookup??
|
||||
startActivityForResult(queryIntent, Id.request.look_up_key_id);
|
||||
}
|
||||
|
||||
private void signKey(Uri dataUri) {
|
||||
long keyId = 0;
|
||||
PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, dataUri);
|
||||
|
||||
if (signKey != null) {
|
||||
keyId = PgpKeyHelper.getMasterKey(signKey).getKeyID();
|
||||
}
|
||||
if (keyId == 0) {
|
||||
Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent signIntent = new Intent(this, SignKeyActivity.class);
|
||||
signIntent.putExtra(SignKeyActivity.EXTRA_KEY_ID, keyId);
|
||||
startActivity(signIntent);
|
||||
}
|
||||
|
||||
private void shareKey(Uri dataUri, boolean fingerprintOnly) {
|
||||
String content = null;
|
||||
if (fingerprintOnly) {
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||
|
||||
// TODO: dublicated in ShareQrCodeDialog
|
||||
content = "openpgp4fpr:";
|
||||
|
||||
String fingerprint = PgpKeyHelper.convertKeyToHex(masterKeyId);
|
||||
|
||||
content = content + fingerprint;
|
||||
} else {
|
||||
// get public keyring as ascii armored string
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this,
|
||||
dataUri, new long[] { masterKeyId });
|
||||
|
||||
content = keyringArmored.get(0);
|
||||
|
||||
// Android will fail with android.os.TransactionTooLargeException if key is too big
|
||||
// see http://www.lonestarprod.com/?p=34
|
||||
if (content.length() >= 86389) {
|
||||
Toast.makeText(getApplicationContext(), R.string.key_too_big_for_sharing,
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// let user choose application
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, content);
|
||||
sendIntent.setType("text/plain");
|
||||
startActivity(Intent.createChooser(sendIntent,
|
||||
getResources().getText(R.string.action_share_key_with)));
|
||||
}
|
||||
|
||||
private void shareKeyQrCode(Uri dataUri, boolean fingerprintOnly) {
|
||||
ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(dataUri,
|
||||
fingerprintOnly);
|
||||
dialog.show(getSupportFragmentManager(), "shareQrCodeDialog");
|
||||
}
|
||||
|
||||
private void copyToClipboard(Uri dataUri) {
|
||||
// get public keyring as ascii armored string
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, dataUri,
|
||||
new long[] { masterKeyId });
|
||||
|
||||
ClipboardReflection.copyToClipboard(this, keyringArmored.get(0));
|
||||
Toast.makeText(getApplicationContext(), R.string.key_copied_to_clipboard, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void shareNfc() {
|
||||
ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance();
|
||||
dialog.show(getSupportFragmentManager(), "shareNfcDialog");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2013 Bahtiar 'kalkin' Gadimov
|
||||
*
|
||||
* 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 org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NdefRecord;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.NfcAdapter.CreateNdefMessageCallback;
|
||||
import android.nfc.NfcAdapter.OnNdefPushCompleteCallback;
|
||||
import android.nfc.NfcEvent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback,
|
||||
OnNdefPushCompleteCallback, LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
// NFC
|
||||
private NfcAdapter mNfcAdapter;
|
||||
private byte[] mSharedKeyringBytes;
|
||||
private static final int NFC_SENT = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
initNfc(mDataUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: Initialize NFC sharing if OS and device supports it
|
||||
*/
|
||||
private void initNfc(Uri dataUri) {
|
||||
// check if NFC Beam is supported (>= Android 4.1)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// Check for available NFC Adapter
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
if (mNfcAdapter != null) {
|
||||
// init nfc
|
||||
|
||||
// get public keyring as byte array
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||
mSharedKeyringBytes = ProviderHelper.getKeyRingsAsByteArray(this, dataUri,
|
||||
new long[] { masterKeyId });
|
||||
|
||||
// Register callback to set NDEF message
|
||||
mNfcAdapter.setNdefPushMessageCallback(this, this);
|
||||
// Register callback to listen for message-sent success
|
||||
mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: Implementation for the CreateNdefMessageCallback interface
|
||||
*/
|
||||
@Override
|
||||
public NdefMessage createNdefMessage(NfcEvent event) {
|
||||
/**
|
||||
* When a device receives a push with an AAR in it, the application specified in the AAR is
|
||||
* guaranteed to run. The AAR overrides the tag dispatch system. You can add it back in to
|
||||
* guarantee that this activity starts when receiving a beamed message. For now, this code
|
||||
* uses the tag dispatch system.
|
||||
*/
|
||||
NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
|
||||
mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: Implementation for the OnNdefPushCompleteCallback interface
|
||||
*/
|
||||
@Override
|
||||
public void onNdefPushComplete(NfcEvent arg0) {
|
||||
// A handler is needed to send messages to the activity when this
|
||||
// callback occurs, because it happens from a binder thread
|
||||
mNfcHandler.obtainMessage(NFC_SENT).sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: This handler receives a message from onNdefPushComplete
|
||||
*/
|
||||
private final Handler mNfcHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case NFC_SENT:
|
||||
Toast.makeText(getApplicationContext(), R.string.nfc_successfull, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
protected LayoutInflater mInflater;
|
||||
protected Activity mActivity;
|
||||
|
||||
protected List<ImportKeysListEntry> data;
|
||||
|
||||
public ImportKeysAdapter(Activity activity) {
|
||||
super(activity, -1);
|
||||
mActivity = activity;
|
||||
mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public void setData(List<ImportKeysListEntry> data) {
|
||||
clear();
|
||||
if (data != null) {
|
||||
this.data = data;
|
||||
|
||||
// add data to extended ArrayAdapter
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
addAll(data);
|
||||
} else {
|
||||
for (ImportKeysListEntry entry : data) {
|
||||
add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ImportKeysListEntry> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
ImportKeysListEntry entry = data.get(position);
|
||||
|
||||
View view = mInflater.inflate(R.layout.import_keys_list_entry, null);
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknown_user_id);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.no_key);
|
||||
TextView fingerprint = (TextView) view.findViewById(R.id.fingerprint);
|
||||
TextView algorithm = (TextView) view.findViewById(R.id.algorithm);
|
||||
algorithm.setText("");
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText("");
|
||||
|
||||
String userId = entry.userIds.get(0);
|
||||
if (userId != null) {
|
||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
||||
|
||||
if (userIdSplit[0] != null && userIdSplit[0].length() > 0) {
|
||||
// show red user id if it is a secret key
|
||||
if (entry.secretKey) {
|
||||
userId = mActivity.getString(R.string.secret_key) + " " + userId;
|
||||
mainUserId.setTextColor(Color.RED);
|
||||
} else {
|
||||
mainUserId.setText(userIdSplit[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (userIdSplit[1] != null && userIdSplit[1].length() > 0) {
|
||||
mainUserIdRest.setText(userIdSplit[1]);
|
||||
mainUserIdRest.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
keyId.setText(entry.hexKeyId);
|
||||
fingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint);
|
||||
|
||||
algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
|
||||
|
||||
if (entry.revoked) {
|
||||
status.setText("revoked");
|
||||
} else {
|
||||
status.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
LinearLayout ll = (LinearLayout) view.findViewById(R.id.list);
|
||||
if (entry.userIds.size() == 1) {
|
||||
ll.setVisibility(View.GONE);
|
||||
} else {
|
||||
boolean first = true;
|
||||
boolean second = true;
|
||||
for (String uid : entry.userIds) {
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (!second) {
|
||||
View sep = new View(mActivity);
|
||||
sep.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 1));
|
||||
sep.setBackgroundResource(android.R.drawable.divider_horizontal_dark);
|
||||
ll.addView(sep);
|
||||
}
|
||||
TextView uidView = (TextView) mInflater.inflate(
|
||||
R.layout.import_keys_list_entry_user_id, null);
|
||||
uidView.setText(uid);
|
||||
ll.addView(uidView);
|
||||
second = false;
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox cBox = (CheckBox) view.findViewById(R.id.selected);
|
||||
cBox.setChecked(entry.isSelected());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.util.AlgorithmNames;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
|
||||
public class ImportKeysListEntry implements Serializable {
|
||||
private static final long serialVersionUID = -7797972103284992662L;
|
||||
public ArrayList<String> userIds;
|
||||
public long keyId;
|
||||
|
||||
public boolean revoked;
|
||||
// public Date date;
|
||||
public String fingerPrint;
|
||||
public String hexKeyId;
|
||||
public int bitStrength;
|
||||
public String algorithm;
|
||||
public boolean secretKey;
|
||||
AlgorithmNames algorithmNames;
|
||||
|
||||
private boolean selected;
|
||||
|
||||
/**
|
||||
* Constructor for later querying from keyserver
|
||||
*/
|
||||
public ImportKeysListEntry() {
|
||||
secretKey = false;
|
||||
userIds = new ArrayList<String>();
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor based on key object, used for import from NFC, QR Codes, files
|
||||
*
|
||||
* @param pgpKey
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public ImportKeysListEntry(PGPKeyRing pgpKeyRing) {
|
||||
// selected is default
|
||||
this.selected = true;
|
||||
|
||||
if (pgpKeyRing instanceof PGPSecretKeyRing) {
|
||||
secretKey = true;
|
||||
} else {
|
||||
secretKey = false;
|
||||
}
|
||||
|
||||
userIds = new ArrayList<String>();
|
||||
for (String userId : new IterableIterator<String>(pgpKeyRing.getPublicKey().getUserIDs())) {
|
||||
userIds.add(userId);
|
||||
}
|
||||
this.keyId = pgpKeyRing.getPublicKey().getKeyID();
|
||||
|
||||
this.revoked = pgpKeyRing.getPublicKey().isRevoked();
|
||||
this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
|
||||
.getFingerprint());
|
||||
this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength();
|
||||
int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
|
||||
if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL
|
||||
|| algorithm == PGPPublicKey.RSA_SIGN) {
|
||||
this.algorithm = "RSA";
|
||||
} else if (algorithm == PGPPublicKey.DSA) {
|
||||
this.algorithm = "DSA";
|
||||
} else if (algorithm == PGPPublicKey.ELGAMAL_ENCRYPT
|
||||
|| algorithm == PGPPublicKey.ELGAMAL_GENERAL) {
|
||||
this.algorithm = "ElGamal";
|
||||
} else if (algorithm == PGPPublicKey.EC || algorithm == PGPPublicKey.ECDSA) {
|
||||
this.algorithm = "ECC";
|
||||
} else {
|
||||
// TODO: with resources
|
||||
this.algorithm = "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
public class ImportKeysListLoader extends AsyncTaskLoader<List<ImportKeysListEntry>> {
|
||||
Context mContext;
|
||||
|
||||
InputData mInputData;
|
||||
|
||||
ArrayList<ImportKeysListEntry> data = new ArrayList<ImportKeysListEntry>();
|
||||
|
||||
public ImportKeysListLoader(Context context, InputData inputData) {
|
||||
super(context);
|
||||
this.mContext = context;
|
||||
this.mInputData = inputData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ImportKeysListEntry> loadInBackground() {
|
||||
if (mInputData == null) {
|
||||
Log.e(Constants.TAG, "Input data is null!");
|
||||
return data;
|
||||
}
|
||||
|
||||
generateListOfKeyrings(mInputData);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReset() {
|
||||
super.onReset();
|
||||
|
||||
// Ensure the loader is stopped
|
||||
onStopLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverResult(List<ImportKeysListEntry> data) {
|
||||
super.deliverResult(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all PGPKeyRing objects from input
|
||||
*
|
||||
* @param keyringBytes
|
||||
* @return
|
||||
*/
|
||||
private void generateListOfKeyrings(InputData inputData) {
|
||||
PositionAwareInputStream progressIn = new PositionAwareInputStream(
|
||||
inputData.getInputStream());
|
||||
|
||||
// need to have access to the bufferedInput, so we can reuse it for the possible
|
||||
// PGPObject chunks after the first one, e.g. files with several consecutive ASCII
|
||||
// armour blocks
|
||||
BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
|
||||
try {
|
||||
|
||||
// read all available blocks... (asc files can contain many blocks with BEGIN END)
|
||||
while (bufferedInput.available() > 0) {
|
||||
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
PGPKeyRing newKeyring = (PGPKeyRing) obj;
|
||||
addToData(newKeyring);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Exception on parsing key file!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addToData(PGPKeyRing keyring) {
|
||||
ImportKeysListEntry item = new ImportKeysListEntry(keyring);
|
||||
data.add(item);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Implements StickyListHeadersAdapter from library
|
||||
*/
|
||||
public class KeyListPublicAdapter extends CursorAdapter implements StickyListHeadersAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
private int mSectionColumnIndex;
|
||||
private int mIndexUserId;
|
||||
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
|
||||
|
||||
public KeyListPublicAdapter(Context context, Cursor c, int flags, int sectionColumnIndex) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mSectionColumnIndex = sectionColumnIndex;
|
||||
initIndex(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
initIndex(newCursor);
|
||||
|
||||
return super.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column indexes for performance reasons just once in constructor and swapCursor. For a
|
||||
* performance comparison see http://stackoverflow.com/a/17999582
|
||||
*
|
||||
* @param cursor
|
||||
*/
|
||||
private void initIndex(Cursor cursor) {
|
||||
if (cursor != null) {
|
||||
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind cursor data to the item list view
|
||||
*
|
||||
* NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. Thus
|
||||
* no ViewHolder is required here.
|
||||
*/
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknown_user_id);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
|
||||
String userId = cursor.getString(mIndexUserId);
|
||||
if (userId != null) {
|
||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
||||
|
||||
if (userIdSplit[0] != null && userIdSplit[0].length() > 0) {
|
||||
mainUserId.setText(userIdSplit[0]);
|
||||
}
|
||||
|
||||
if (userIdSplit[1] != null && userIdSplit[1].length() > 0) {
|
||||
mainUserIdRest.setText(userIdSplit[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.key_list_item, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new header view and binds the section headers to it. It uses the ViewHolder
|
||||
* pattern. Most functionality is similar to getView() from Android's CursorAdapter.
|
||||
*
|
||||
* NOTE: The variables mDataValid and mCursor are available due to the super class
|
||||
* CursorAdapter.
|
||||
*/
|
||||
@Override
|
||||
public View getHeaderView(int position, View convertView, ViewGroup parent) {
|
||||
HeaderViewHolder holder;
|
||||
if (convertView == null) {
|
||||
holder = new HeaderViewHolder();
|
||||
convertView = mInflater.inflate(R.layout.key_list_public_header, parent, false);
|
||||
holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (HeaderViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
if (!mDataValid) {
|
||||
// no data available at this point
|
||||
Log.d(Constants.TAG, "getHeaderView: No data available at this point!");
|
||||
return convertView;
|
||||
}
|
||||
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("couldn't move cursor to position " + position);
|
||||
}
|
||||
|
||||
// set header text as first char in user id
|
||||
String userId = mCursor.getString(mSectionColumnIndex);
|
||||
String headerText = convertView.getResources().getString(R.string.unknown_user_id);
|
||||
if (userId != null && userId.length() > 0) {
|
||||
headerText = "" + mCursor.getString(mSectionColumnIndex).subSequence(0, 1).charAt(0);
|
||||
}
|
||||
holder.text.setText(headerText);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header IDs should be static, position=1 should always return the same Id that is.
|
||||
*/
|
||||
@Override
|
||||
public long getHeaderId(int position) {
|
||||
if (!mDataValid) {
|
||||
// no data available at this point
|
||||
Log.d(Constants.TAG, "getHeaderView: No data available at this point!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mCursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("couldn't move cursor to position " + position);
|
||||
}
|
||||
|
||||
// return the first character of the name as ID because this is what
|
||||
// headers private HashMap<Integer, Boolean> mSelection = new HashMap<Integer,
|
||||
// Boolean>();are based upon
|
||||
String userId = mCursor.getString(mSectionColumnIndex);
|
||||
if (userId != null && userId.length() > 0) {
|
||||
return userId.subSequence(0, 1).charAt(0);
|
||||
} else {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
class HeaderViewHolder {
|
||||
TextView text;
|
||||
}
|
||||
|
||||
/** -------------------------- MULTI-SELECTION METHODS -------------- */
|
||||
public void setNewSelection(int position, boolean value) {
|
||||
mSelection.put(position, value);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public boolean isPositionChecked(int position) {
|
||||
Boolean result = mSelection.get(position);
|
||||
return result == null ? false : result;
|
||||
}
|
||||
|
||||
public Set<Integer> getCurrentCheckedPosition() {
|
||||
return mSelection.keySet();
|
||||
}
|
||||
|
||||
public void removeSelection(int position) {
|
||||
mSelection.remove(position);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
mSelection.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
// let the adapter handle setting up the row views
|
||||
View v = super.getView(position, convertView, parent);
|
||||
|
||||
/**
|
||||
* Change color for multi-selection
|
||||
*/
|
||||
// default color
|
||||
v.setBackgroundColor(Color.TRANSPARENT);
|
||||
if (mSelection.get(position) != null) {
|
||||
// this is a selected position, change color!
|
||||
v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class KeyListSecretAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private int mIndexUserId;
|
||||
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
|
||||
|
||||
public KeyListSecretAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
initIndex(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
initIndex(newCursor);
|
||||
|
||||
return super.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column indexes for performance reasons just once in constructor and swapCursor. For a
|
||||
* performance comparison see http://stackoverflow.com/a/17999582
|
||||
*
|
||||
* @param cursor
|
||||
*/
|
||||
private void initIndex(Cursor cursor) {
|
||||
if (cursor != null) {
|
||||
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknown_user_id);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
|
||||
String userId = cursor.getString(mIndexUserId);
|
||||
if (userId != null) {
|
||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
||||
|
||||
if (userIdSplit[0] != null && userIdSplit[0].length() > 0) {
|
||||
mainUserId.setText(userIdSplit[0]);
|
||||
}
|
||||
|
||||
if (userIdSplit[1] != null && userIdSplit[1].length() > 0) {
|
||||
mainUserIdRest.setText(userIdSplit[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.key_list_item, null);
|
||||
}
|
||||
|
||||
/** -------------------------- MULTI-SELECTION METHODS -------------- */
|
||||
public void setNewSelection(int position, boolean value) {
|
||||
mSelection.put(position, value);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public boolean isPositionChecked(int position) {
|
||||
Boolean result = mSelection.get(position);
|
||||
return result == null ? false : result;
|
||||
}
|
||||
|
||||
public Set<Integer> getCurrentCheckedPosition() {
|
||||
return mSelection.keySet();
|
||||
}
|
||||
|
||||
public void removeSelection(int position) {
|
||||
mSelection.remove(position);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
mSelection.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
// let the adapter handle setting up the row views
|
||||
View v = super.getView(position, convertView, parent);
|
||||
|
||||
/**
|
||||
* Change color for multi-selection
|
||||
*/
|
||||
// default color
|
||||
v.setBackgroundColor(Color.TRANSPARENT);
|
||||
if (mSelection.get(position) != null) {
|
||||
// this is a selected position, change color!
|
||||
v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
public class KeyValueSpinnerAdapter extends ArrayAdapter<String> {
|
||||
private final HashMap<Integer, String> mData;
|
||||
private final int[] mKeys;
|
||||
private final String[] mValues;
|
||||
|
||||
static <K, V extends Comparable<? super V>> SortedSet<Map.Entry<K, V>> entriesSortedByValues(
|
||||
Map<K, V> map) {
|
||||
SortedSet<Map.Entry<K, V>> sortedEntries = new TreeSet<Map.Entry<K, V>>(
|
||||
new Comparator<Map.Entry<K, V>>() {
|
||||
@Override
|
||||
public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) {
|
||||
return e1.getValue().compareTo(e2.getValue());
|
||||
}
|
||||
});
|
||||
sortedEntries.addAll(map.entrySet());
|
||||
return sortedEntries;
|
||||
}
|
||||
|
||||
public KeyValueSpinnerAdapter(Context context, HashMap<Integer, String> objects) {
|
||||
// To make the drop down a simple text box
|
||||
super(context, android.R.layout.simple_spinner_item);
|
||||
mData = objects;
|
||||
|
||||
// To make the drop down view a radio button list
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
|
||||
SortedSet<Map.Entry<Integer, String>> sorted = entriesSortedByValues(objects);
|
||||
|
||||
// Assign hash keys with a position so that we can present and retrieve them
|
||||
int i = 0;
|
||||
mKeys = new int[mData.size()];
|
||||
mValues = new String[mData.size()];
|
||||
for (Map.Entry<Integer, String> entry : sorted) {
|
||||
mKeys[i] = entry.getKey();
|
||||
mValues[i] = entry.getValue();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mData.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value
|
||||
*/
|
||||
@Override
|
||||
public String getItem(int position) {
|
||||
// return the value based on the position. This is displayed in the list.
|
||||
return mValues[position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns item key
|
||||
*/
|
||||
public long getItemId(int position) {
|
||||
// Return an id to represent the item.
|
||||
|
||||
return mKeys[position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find position from key
|
||||
*/
|
||||
public int getPosition(long itemId) {
|
||||
for (int i = 0; i < mKeys.length; i++) {
|
||||
if ((int) itemId == mKeys[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SelectKeyCursorAdapter extends CursorAdapter {
|
||||
|
||||
protected int mKeyType;
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
private ListView mListView;
|
||||
|
||||
private int mIndexUserId;
|
||||
private int mIndexMasterKeyId;
|
||||
private int mIndexProjectionValid;
|
||||
private int mIndexProjectionAvailable;
|
||||
|
||||
public final static String PROJECTION_ROW_AVAILABLE = "available";
|
||||
public final static String PROJECTION_ROW_VALID = "valid";
|
||||
|
||||
public SelectKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView,
|
||||
int keyType) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mListView = listView;
|
||||
mKeyType = keyType;
|
||||
|
||||
initIndex(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
initIndex(newCursor);
|
||||
|
||||
return super.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column indexes for performance reasons just once in constructor and swapCursor. For a
|
||||
* performance comparison see http://stackoverflow.com/a/17999582
|
||||
*
|
||||
* @param cursor
|
||||
*/
|
||||
private void initIndex(Cursor cursor) {
|
||||
if (cursor != null) {
|
||||
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
||||
mIndexMasterKeyId = cursor.getColumnIndexOrThrow(KeyRings.MASTER_KEY_ID);
|
||||
mIndexProjectionValid = cursor.getColumnIndexOrThrow(PROJECTION_ROW_VALID);
|
||||
mIndexProjectionAvailable = cursor.getColumnIndexOrThrow(PROJECTION_ROW_AVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
public String getUserId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getString(mIndexUserId);
|
||||
}
|
||||
|
||||
public long getMasterKeyId(int position) {
|
||||
mCursor.moveToPosition(position);
|
||||
return mCursor.getLong(mIndexMasterKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
boolean valid = cursor.getInt(mIndexProjectionValid) > 0;
|
||||
|
||||
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mainUserId.setText(R.string.unknown_user_id);
|
||||
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mainUserIdRest.setText("");
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(R.string.no_key);
|
||||
TextView status = (TextView) view.findViewById(R.id.status);
|
||||
status.setText(R.string.unknown_status);
|
||||
|
||||
String userId = cursor.getString(mIndexUserId);
|
||||
if (userId != null) {
|
||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
||||
|
||||
if (userIdSplit[0] != null && userIdSplit[0].length() > 0) {
|
||||
mainUserId.setText(userIdSplit[0]);
|
||||
}
|
||||
|
||||
if (userIdSplit[1] != null && userIdSplit[1].length() > 0) {
|
||||
mainUserIdRest.setText(userIdSplit[1]);
|
||||
}
|
||||
}
|
||||
|
||||
long masterKeyId = cursor.getLong(mIndexMasterKeyId);
|
||||
keyId.setText(PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
||||
|
||||
if (valid) {
|
||||
if (mKeyType == Id.type.public_key) {
|
||||
status.setText(R.string.can_encrypt);
|
||||
} else {
|
||||
status.setText(R.string.can_sign);
|
||||
}
|
||||
} else {
|
||||
if (cursor.getInt(mIndexProjectionAvailable) > 0) {
|
||||
// has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or
|
||||
// expired
|
||||
status.setText(R.string.expired);
|
||||
} else {
|
||||
status.setText(R.string.no_key);
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox selected = (CheckBox) view.findViewById(R.id.selected);
|
||||
if (mKeyType == Id.type.public_key) {
|
||||
selected.setVisibility(View.VISIBLE);
|
||||
|
||||
if (!valid) {
|
||||
mListView.setItemChecked(cursor.getPosition(), false);
|
||||
}
|
||||
|
||||
selected.setChecked(mListView.isItemChecked(cursor.getPosition()));
|
||||
selected.setEnabled(valid);
|
||||
} else {
|
||||
selected.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
status.setText(status.getText() + " ");
|
||||
|
||||
view.setEnabled(valid);
|
||||
mainUserId.setEnabled(valid);
|
||||
mainUserIdRest.setEnabled(valid);
|
||||
keyId.setEnabled(valid);
|
||||
status.setEnabled(valid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.select_key_item, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ViewKeyKeysAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private int mIndexKeyId;
|
||||
private int mIndexAlgorithm;
|
||||
private int mIndexKeySize;
|
||||
private int mIndexIsMasterKey;
|
||||
private int mIndexCanCertify;
|
||||
private int mIndexCanEncrypt;
|
||||
private int mIndexCanSign;
|
||||
|
||||
public ViewKeyKeysAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
|
||||
initIndex(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
initIndex(newCursor);
|
||||
|
||||
return super.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column indexes for performance reasons just once in constructor and swapCursor. For a
|
||||
* performance comparison see http://stackoverflow.com/a/17999582
|
||||
*
|
||||
* @param cursor
|
||||
*/
|
||||
private void initIndex(Cursor cursor) {
|
||||
if (cursor != null) {
|
||||
mIndexKeyId = cursor.getColumnIndexOrThrow(Keys.KEY_ID);
|
||||
mIndexAlgorithm = cursor.getColumnIndexOrThrow(Keys.ALGORITHM);
|
||||
mIndexKeySize = cursor.getColumnIndexOrThrow(Keys.KEY_SIZE);
|
||||
mIndexIsMasterKey = cursor.getColumnIndexOrThrow(Keys.IS_MASTER_KEY);
|
||||
mIndexCanCertify = cursor.getColumnIndexOrThrow(Keys.CAN_CERTIFY);
|
||||
mIndexCanEncrypt = cursor.getColumnIndexOrThrow(Keys.CAN_ENCRYPT);
|
||||
mIndexCanSign = cursor.getColumnIndexOrThrow(Keys.CAN_SIGN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId));
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm),
|
||||
cursor.getInt(mIndexKeySize));
|
||||
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
keyId.setText(keyIdStr);
|
||||
|
||||
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
||||
keyDetails.setText("(" + algorithmStr + ")");
|
||||
|
||||
ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
||||
if (cursor.getInt(mIndexIsMasterKey) != 1) {
|
||||
masterKeyIcon.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
masterKeyIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
||||
if (cursor.getInt(mIndexCanCertify) != 1) {
|
||||
certifyIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
certifyIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||
if (cursor.getInt(mIndexCanEncrypt) != 1) {
|
||||
encryptIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
encryptIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
||||
if (cursor.getInt(mIndexCanSign) != 1) {
|
||||
signIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
signIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.view_key_keys_item, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private int mIndexUserId;
|
||||
|
||||
public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
|
||||
initIndex(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
initIndex(newCursor);
|
||||
|
||||
return super.swapCursor(newCursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get column indexes for performance reasons just once in constructor and swapCursor. For a
|
||||
* performance comparison see http://stackoverflow.com/a/17999582
|
||||
*
|
||||
* @param cursor
|
||||
*/
|
||||
private void initIndex(Cursor cursor) {
|
||||
if (cursor != null) {
|
||||
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
String userIdStr = cursor.getString(mIndexUserId);
|
||||
|
||||
TextView userId = (TextView) view.findViewById(R.id.userId);
|
||||
userId.setText(userIdStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.view_key_userids_item, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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.dialog;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
|
||||
public class DeleteFileDialogFragment extends SherlockDialogFragment {
|
||||
private static final String ARG_DELETE_FILE = "delete_file";
|
||||
|
||||
/**
|
||||
* Creates new instance of this delete file dialog fragment
|
||||
*/
|
||||
public static DeleteFileDialogFragment newInstance(String deleteFile) {
|
||||
DeleteFileDialogFragment frag = new DeleteFileDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putString(ARG_DELETE_FILE, deleteFile);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
|
||||
final String deleteFile = getArguments().getString(ARG_DELETE_FILE);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFile));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(activity, KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_DELETE_FILE_SECURELY);
|
||||
data.putString(KeychainIntentService.DELETE_FILE, deleteFile);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance(
|
||||
R.string.progress_deleting_securely, ProgressDialog.STYLE_HORIZONTAL);
|
||||
|
||||
// Message is received after deleting is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(activity, deletingDialog) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Toast.makeText(activity, R.string.file_delete_successful,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
deletingDialog.show(activity.getSupportFragmentManager(), "deletingDialog");
|
||||
|
||||
// start service with intent
|
||||
activity.startService(intent);
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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.dialog;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
|
||||
public class DeleteKeyDialogFragment extends SherlockDialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file";
|
||||
private static final String ARG_KEY_TYPE = "key_type";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
/**
|
||||
* Creates new instance of this delete file dialog fragment
|
||||
*/
|
||||
public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds,
|
||||
int keyType) {
|
||||
DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
args.putLongArray(ARG_DELETE_KEY_RING_ROW_IDS, keyRingRowIds);
|
||||
args.putInt(ARG_KEY_TYPE, keyType);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
final long[] keyRingRowIds = getArguments().getLongArray(ARG_DELETE_KEY_RING_ROW_IDS);
|
||||
final int keyType = getArguments().getInt(ARG_KEY_TYPE);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(R.string.warning);
|
||||
|
||||
if (keyRingRowIds.length == 1) {
|
||||
// TODO: better way to do this?
|
||||
String userId = activity.getString(R.string.unknown_user_id);
|
||||
|
||||
if (keyType == Id.type.public_key) {
|
||||
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByRowId(activity,
|
||||
keyRingRowIds[0]);
|
||||
userId = PgpKeyHelper.getMainUserIdSafe(activity,
|
||||
PgpKeyHelper.getMasterKey(keyRing));
|
||||
} else {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByRowId(activity,
|
||||
keyRingRowIds[0]);
|
||||
userId = PgpKeyHelper.getMainUserIdSafe(activity,
|
||||
PgpKeyHelper.getMasterKey(keyRing));
|
||||
}
|
||||
|
||||
builder.setMessage(getString(
|
||||
keyType == Id.type.public_key ? R.string.key_deletion_confirmation
|
||||
: R.string.secret_key_deletion_confirmation, userId));
|
||||
} else {
|
||||
builder.setMessage(R.string.key_deletion_confirmation_multi);
|
||||
}
|
||||
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
if (keyType == Id.type.public_key) {
|
||||
for (long keyRowId : keyRingRowIds) {
|
||||
ProviderHelper.deletePublicKeyRing(activity, keyRowId);
|
||||
}
|
||||
} else {
|
||||
for (long keyRowId : keyRingRowIds) {
|
||||
ProviderHelper.deleteSecretKeyRing(activity, keyRowId);
|
||||
}
|
||||
}
|
||||
|
||||
dismiss();
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.dialog;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class FileDialogFragment extends SherlockDialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_TITLE = "title";
|
||||
private static final String ARG_MESSAGE = "message";
|
||||
private static final String ARG_DEFAULT_FILE = "default_file";
|
||||
private static final String ARG_CHECKBOX_TEXT = "checkbox_text";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
|
||||
public static final String MESSAGE_DATA_FILENAME = "filename";
|
||||
public static final String MESSAGE_DATA_CHECKED = "checked";
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
private EditText mFilename;
|
||||
private BootstrapButton mBrowse;
|
||||
private CheckBox mCheckBox;
|
||||
|
||||
private static final int REQUEST_CODE = 0x00007004;
|
||||
|
||||
/**
|
||||
* Creates new instance of this file dialog fragment
|
||||
*/
|
||||
public static FileDialogFragment newInstance(Messenger messenger, String title, String message,
|
||||
String defaultFile, String checkboxText) {
|
||||
FileDialogFragment frag = new FileDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
args.putString(ARG_TITLE, title);
|
||||
args.putString(ARG_MESSAGE, message);
|
||||
args.putString(ARG_DEFAULT_FILE, defaultFile);
|
||||
args.putString(ARG_CHECKBOX_TEXT, checkboxText);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
String title = getArguments().getString(ARG_TITLE);
|
||||
String message = getArguments().getString(ARG_MESSAGE);
|
||||
String defaultFile = getArguments().getString(ARG_DEFAULT_FILE);
|
||||
String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) activity
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setTitle(title);
|
||||
alert.setMessage(message);
|
||||
|
||||
View view = inflater.inflate(R.layout.file_dialog, null);
|
||||
|
||||
mFilename = (EditText) view.findViewById(R.id.input);
|
||||
mFilename.setText(defaultFile);
|
||||
mBrowse = (BootstrapButton) 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, mFilename.getText().toString(), "*/*",
|
||||
REQUEST_CODE);
|
||||
}
|
||||
});
|
||||
|
||||
mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
|
||||
if (checkboxText == null) {
|
||||
mCheckBox.setEnabled(false);
|
||||
mCheckBox.setVisibility(View.GONE);
|
||||
} else {
|
||||
mCheckBox.setEnabled(true);
|
||||
mCheckBox.setVisibility(View.VISIBLE);
|
||||
mCheckBox.setText(checkboxText);
|
||||
}
|
||||
|
||||
alert.setView(view);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
boolean checked = false;
|
||||
if (mCheckBox.isEnabled()) {
|
||||
checked = mCheckBox.isChecked();
|
||||
}
|
||||
|
||||
// return resulting data back to activity
|
||||
Bundle data = new Bundle();
|
||||
data.putString(MESSAGE_DATA_FILENAME, mFilename.getText().toString());
|
||||
data.putBoolean(MESSAGE_DATA_CHECKED, checked);
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY, data);
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates filename in dialog, normally called in onActivityResult in activity using the
|
||||
* FileDialog
|
||||
*
|
||||
* @param messageId
|
||||
* @param progress
|
||||
* @param max
|
||||
*/
|
||||
private void setFilename(String filename) {
|
||||
AlertDialog dialog = (AlertDialog) getDialog();
|
||||
EditText filenameEditText = (EditText) dialog.findViewById(R.id.input);
|
||||
|
||||
if (filenameEditText != null) {
|
||||
filenameEditText.setText(filename);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode & 0xFFFF) {
|
||||
case REQUEST_CODE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = data.getData().getPath();
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
// set filename used in export/import dialogs
|
||||
setFilename(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!", e);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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.dialog;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.ui.KeyServerQueryActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
|
||||
|
||||
public class LookupUnknownKeyDialogFragment extends SherlockDialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_UNKNOWN_KEY_ID = "unknown_key_id";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
public static final int MESSAGE_CANCEL = 2;
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*
|
||||
* @param messenger
|
||||
* @param unknownKeyId
|
||||
* @return
|
||||
*/
|
||||
public static LookupUnknownKeyDialogFragment newInstance(Messenger messenger, long unknownKeyId) {
|
||||
LookupUnknownKeyDialogFragment frag = new LookupUnknownKeyDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(ARG_UNKNOWN_KEY_ID, unknownKeyId);
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
final long unknownKeyId = getArguments().getLong(ARG_UNKNOWN_KEY_ID);
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.title_unknown_signature_key);
|
||||
alert.setMessage(getString(R.string.lookup_unknown_key,
|
||||
PgpKeyHelper.convertKeyIdToHex(unknownKeyId)));
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY);
|
||||
|
||||
Intent intent = new Intent(activity, KeyServerQueryActivity.class);
|
||||
intent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID);
|
||||
intent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, unknownKeyId);
|
||||
startActivityForResult(intent, Id.request.look_up_key_id);
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
}
|
||||
});
|
||||
alert.setCancelable(true);
|
||||
alert.setOnCancelListener(new OnCancelListener() {
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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.dialog;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
|
||||
public class PassphraseDialogFragment extends SherlockDialogFragment implements OnEditorActionListener {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_SECRET_KEY_ID = "secret_key_id";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
public static final int MESSAGE_CANCEL = 2;
|
||||
|
||||
private Messenger mMessenger;
|
||||
private EditText mPassphraseEditText;
|
||||
private boolean canKB;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*
|
||||
* @param secretKeyId
|
||||
* secret key id you want to use
|
||||
* @param messenger
|
||||
* to communicate back after caching the passphrase
|
||||
* @return
|
||||
* @throws PgpGeneralException
|
||||
*/
|
||||
public static PassphraseDialogFragment newInstance(Context context, Messenger messenger,
|
||||
long secretKeyId) throws PgpGeneralException {
|
||||
// check if secret key has a passphrase
|
||||
if (!(secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none)) {
|
||||
if (!PassphraseCacheService.hasPassphrase(context, secretKeyId)) {
|
||||
throw new PgpGeneralException("No passphrase! No passphrase dialog needed!");
|
||||
}
|
||||
}
|
||||
|
||||
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(ARG_SECRET_KEY_ID, secretKeyId);
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
final long secretKeyId = getArguments().getLong(ARG_SECRET_KEY_ID);
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setTitle(R.string.title_authentication);
|
||||
|
||||
final PGPSecretKey secretKey;
|
||||
|
||||
if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) {
|
||||
secretKey = null;
|
||||
alert.setMessage(R.string.passphrase_for_symmetric_encryption);
|
||||
} else {
|
||||
// TODO: by master key id???
|
||||
secretKey = PgpKeyHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity,
|
||||
secretKeyId));
|
||||
// secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
|
||||
|
||||
if (secretKey == null) {
|
||||
alert.setTitle(R.string.title_key_not_found);
|
||||
alert.setMessage(getString(R.string.key_not_found, secretKeyId));
|
||||
alert.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
canKB = false;
|
||||
return alert.create();
|
||||
}
|
||||
String userId = PgpKeyHelper.getMainUserIdSafe(activity, secretKey);
|
||||
|
||||
Log.d(Constants.TAG, "User id: '" + userId + "'");
|
||||
alert.setMessage(getString(R.string.passphrase_for, userId));
|
||||
}
|
||||
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.passphrase_dialog, null);
|
||||
alert.setView(view);
|
||||
|
||||
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
long curKeyIndex = 1;
|
||||
boolean keyOK = true;
|
||||
String passPhrase = mPassphraseEditText.getText().toString();
|
||||
long keyId;
|
||||
PGPSecretKey clickSecretKey = secretKey;
|
||||
|
||||
if (clickSecretKey != null) {
|
||||
while (keyOK == true) {
|
||||
if (clickSecretKey != null) { // check again for loop
|
||||
try {
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
passPhrase.toCharArray());
|
||||
PGPPrivateKey testKey = clickSecretKey
|
||||
.extractPrivateKey(keyDecryptor);
|
||||
if (testKey == null) {
|
||||
if (!clickSecretKey.isMasterKey()) {
|
||||
Toast.makeText(activity,
|
||||
R.string.error_could_not_extract_private_key,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
return;
|
||||
} else {
|
||||
clickSecretKey = PgpKeyHelper.getKeyNum(ProviderHelper
|
||||
.getPGPSecretKeyRingByKeyId(activity, secretKeyId),
|
||||
curKeyIndex);
|
||||
curKeyIndex++; // does post-increment work like C?
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
keyOK = false;
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
Toast.makeText(activity, R.string.wrong_passphrase,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(activity, R.string.error_could_not_extract_private_key,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
return; // ran out of keys to try
|
||||
}
|
||||
}
|
||||
keyId = secretKey.getKeyID();
|
||||
} else {
|
||||
keyId = Id.key.symmetric;
|
||||
}
|
||||
|
||||
// cache the new passphrase
|
||||
Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
|
||||
PassphraseCacheService.addCachedPassphrase(activity, keyId, passPhrase);
|
||||
if (keyOK == false && clickSecretKey.getKeyID() != keyId) {
|
||||
PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(),
|
||||
passPhrase);
|
||||
}
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY);
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
canKB = true;
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle arg0) {
|
||||
super.onActivityCreated(arg0);
|
||||
if (canKB) {
|
||||
// request focus and open soft keyboard
|
||||
mPassphraseEditText.requestFocus();
|
||||
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
|
||||
mPassphraseEditText.setOnEditorActionListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
super.onCancel(dialog);
|
||||
|
||||
dismiss();
|
||||
sendMessageToHandler(MESSAGE_CANCEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
||||
*/
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
|
||||
bt.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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.dialog;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnKeyListener;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
|
||||
public class ProgressDialogFragment extends SherlockDialogFragment {
|
||||
private static final String ARG_MESSAGE_ID = "message_id";
|
||||
private static final String ARG_STYLE = "style";
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public static ProgressDialogFragment newInstance(int messageId, int style) {
|
||||
ProgressDialogFragment frag = new ProgressDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_MESSAGE_ID, messageId);
|
||||
args.putInt(ARG_STYLE, style);
|
||||
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates progress of dialog
|
||||
*
|
||||
* @param messageId
|
||||
* @param progress
|
||||
* @param max
|
||||
*/
|
||||
public void setProgress(int messageId, int progress, int max) {
|
||||
setProgress(getString(messageId), progress, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates progress of dialog
|
||||
*
|
||||
* @param messageId
|
||||
* @param progress
|
||||
* @param max
|
||||
*/
|
||||
public void setProgress(int progress, int max) {
|
||||
ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
|
||||
dialog.setProgress(progress);
|
||||
dialog.setMax(max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates progress of dialog
|
||||
*
|
||||
* @param messageId
|
||||
* @param progress
|
||||
* @param max
|
||||
*/
|
||||
public void setProgress(String message, int progress, int max) {
|
||||
ProgressDialog dialog = (ProgressDialog) getDialog();
|
||||
|
||||
dialog.setMessage(message);
|
||||
dialog.setProgress(progress);
|
||||
dialog.setMax(max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Activity activity = getActivity();
|
||||
|
||||
ProgressDialog dialog = new ProgressDialog(activity);
|
||||
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
dialog.setCancelable(false);
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
|
||||
int messageId = getArguments().getInt(ARG_MESSAGE_ID);
|
||||
int style = getArguments().getInt(ARG_STYLE);
|
||||
|
||||
dialog.setMessage(getString(messageId));
|
||||
dialog.setProgressStyle(style);
|
||||
|
||||
// Disable the back button
|
||||
OnKeyListener keyListener = new OnKeyListener() {
|
||||
|
||||
@Override
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
dialog.setOnKeyListener(keyListener);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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.dialog;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
|
||||
public class SetPassphraseDialogFragment extends SherlockDialogFragment implements OnEditorActionListener {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_TITLE = "title";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
|
||||
public static final String MESSAGE_NEW_PASSPHRASE = "new_passphrase";
|
||||
|
||||
private Messenger mMessenger;
|
||||
private EditText mPassphraseEditText;
|
||||
private EditText mPassphraseAgainEditText;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*
|
||||
* @param title
|
||||
* title of dialog
|
||||
* @param messenger
|
||||
* to communicate back after setting the passphrase
|
||||
* @return
|
||||
*/
|
||||
public static SetPassphraseDialogFragment newInstance(Messenger messenger, int title) {
|
||||
SetPassphraseDialogFragment frag = new SetPassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_TITLE, title);
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
int title = getArguments().getInt(ARG_TITLE);
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setTitle(title);
|
||||
alert.setMessage(R.string.enter_passphrase_twice);
|
||||
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.passphrase_repeat_dialog, null);
|
||||
alert.setView(view);
|
||||
|
||||
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
|
||||
mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
|
||||
String passPhrase1 = mPassphraseEditText.getText().toString();
|
||||
String passPhrase2 = mPassphraseAgainEditText.getText().toString();
|
||||
if (!passPhrase1.equals(passPhrase2)) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.passphrases_do_not_match)), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (passPhrase1.equals("")) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.passphrase_must_not_be_empty)),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// return resulting data back to activity
|
||||
Bundle data = new Bundle();
|
||||
data.putString(MESSAGE_NEW_PASSPHRASE, passPhrase1);
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY, data);
|
||||
}
|
||||
});
|
||||
|
||||
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle arg0) {
|
||||
super.onActivityCreated(arg0);
|
||||
|
||||
// request focus and open soft keyboard
|
||||
mPassphraseEditText.requestFocus();
|
||||
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
|
||||
mPassphraseAgainEditText.setOnEditorActionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
||||
*/
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
|
||||
bt.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what
|
||||
* Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.dialog;
|
||||
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class ShareNfcDialogFragment extends SherlockDialogFragment {
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static ShareNfcDialogFragment newInstance() {
|
||||
ShareNfcDialogFragment frag = new ShareNfcDialogFragment();
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final FragmentActivity activity = getActivity();
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_info);
|
||||
alert.setTitle(R.string.share_nfc_dialog);
|
||||
alert.setCancelable(true);
|
||||
|
||||
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
HtmlTextView textView = new HtmlTextView(getActivity());
|
||||
textView.setPadding(8, 8, 8, 8);
|
||||
alert.setView(textView);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
textView.setText(getString(R.string.error) + ": "
|
||||
+ getString(R.string.error_jelly_bean_needed));
|
||||
} else {
|
||||
// check if NFC Adapter is available
|
||||
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
|
||||
if (nfcAdapter == null) {
|
||||
textView.setText(getString(R.string.error) + ": "
|
||||
+ getString(R.string.error_nfc_needed));
|
||||
} else {
|
||||
// nfc works...
|
||||
textView.setHtmlFromRawResource(getActivity(), R.raw.nfc_beam_share);
|
||||
|
||||
alert.setNegativeButton(R.string.menu_beam_preferences,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Intent intentSettings = new Intent(
|
||||
Settings.ACTION_NFCSHARING_SETTINGS);
|
||||
startActivity(intentSettings);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// no flickering when clicking textview for Android < 4
|
||||
// aboutTextView.setTextColor(getResources().getColor(android.R.color.black));
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 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.dialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockDialogFragment;
|
||||
|
||||
public class ShareQrCodeDialogFragment extends SherlockDialogFragment {
|
||||
private static final String ARG_URI = "uri";
|
||||
private static final String ARG_FINGERPRINT_ONLY = "fingerprint_only";
|
||||
|
||||
private ImageView mImage;
|
||||
private TextView mText;
|
||||
|
||||
private ArrayList<String> mContentList;
|
||||
private int mCounter;
|
||||
|
||||
private static final int QR_CODE_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*
|
||||
* @param content
|
||||
* Content to be shared via QR Codes
|
||||
* @return
|
||||
*/
|
||||
public static ShareQrCodeDialogFragment newInstance(Uri dataUri, boolean fingerprintOnly) {
|
||||
ShareQrCodeDialogFragment frag = new ShareQrCodeDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_URI, dataUri);
|
||||
args.putBoolean(ARG_FINGERPRINT_ONLY, fingerprintOnly);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_URI);
|
||||
boolean fingerprintOnly = getArguments().getBoolean(ARG_FINGERPRINT_ONLY);
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
|
||||
alert.setTitle(R.string.share_qr_code_dialog_title);
|
||||
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.share_qr_code_dialog, null);
|
||||
alert.setView(view);
|
||||
|
||||
mImage = (ImageView) view.findViewById(R.id.share_qr_code_dialog_image);
|
||||
mText = (TextView) view.findViewById(R.id.share_qr_code_dialog_text);
|
||||
|
||||
// TODO
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
|
||||
|
||||
String content = null;
|
||||
if (fingerprintOnly) {
|
||||
content = "openpgp4fpr:";
|
||||
|
||||
String fingerprint = PgpKeyHelper.convertKeyToHex(masterKeyId);
|
||||
|
||||
mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " "
|
||||
+ fingerprint);
|
||||
|
||||
content = content + fingerprint;
|
||||
|
||||
Log.d(Constants.TAG, "content: " + content);
|
||||
|
||||
alert.setPositiveButton(R.string.btn_okay, null);
|
||||
} else {
|
||||
mText.setText(R.string.share_qr_code_dialog_start);
|
||||
|
||||
// get public keyring as ascii armored string
|
||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
||||
getActivity(), dataUri, new long[] { masterKeyId });
|
||||
|
||||
// TODO: binary?
|
||||
|
||||
content = keyringArmored.get(0);
|
||||
|
||||
// OnClickListener are set in onResume to prevent automatic dismissing of Dialogs
|
||||
// http://stackoverflow.com/questions/2620444/how-to-prevent-a-dialog-from-closing-when-a-button-is-clicked
|
||||
alert.setPositiveButton(R.string.btn_next, null);
|
||||
alert.setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
mContentList = splitString(content, 1000);
|
||||
|
||||
// start with first
|
||||
mCounter = 0;
|
||||
updateQrCode();
|
||||
|
||||
return alert.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
AlertDialog alertDialog = (AlertDialog) getDialog();
|
||||
final Button backButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
|
||||
final Button nextButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
|
||||
backButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mCounter > 0) {
|
||||
mCounter--;
|
||||
updateQrCode();
|
||||
updateDialog(backButton, nextButton);
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
});
|
||||
nextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
if (mCounter < mContentList.size() - 1) {
|
||||
mCounter++;
|
||||
updateQrCode();
|
||||
updateDialog(backButton, nextButton);
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateQrCode() {
|
||||
// Content: <counter>,<size>,<content>
|
||||
mImage.setImageBitmap(QrCodeUtils.getQRCodeBitmap(mCounter + "," + mContentList.size()
|
||||
+ "," + mContentList.get(mCounter), QR_CODE_SIZE));
|
||||
}
|
||||
|
||||
private void updateDialog(Button backButton, Button nextButton) {
|
||||
if (mCounter == 0) {
|
||||
backButton.setText(android.R.string.cancel);
|
||||
} else {
|
||||
backButton.setText(R.string.btn_back);
|
||||
}
|
||||
if (mCounter == mContentList.size() - 1) {
|
||||
nextButton.setText(android.R.string.ok);
|
||||
} else {
|
||||
nextButton.setText(R.string.btn_next);
|
||||
}
|
||||
|
||||
mText.setText(getResources().getString(R.string.share_qr_code_dialog_progress,
|
||||
mCounter + 1, mContentList.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Split String by number of characters
|
||||
*
|
||||
* @param text
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
private ArrayList<String> splitString(String text, int size) {
|
||||
ArrayList<String> strings = new ArrayList<String>();
|
||||
int index = 0;
|
||||
while (index < text.length()) {
|
||||
strings.add(text.substring(index, Math.min(index + size, text.length())));
|
||||
index += size;
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
public interface Editor {
|
||||
public interface EditorListener {
|
||||
public void onDeleted(Editor editor);
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ListView;
|
||||
|
||||
/**
|
||||
* Automatically calculate height of ListView based on contained items. This enables to put this
|
||||
* ListView into a ScrollView without messing up.
|
||||
*
|
||||
* from
|
||||
* http://stackoverflow.com/questions/2419246/how-do-i-create-a-listview-thats-not-in-a-scrollview-
|
||||
* or-has-the-scrollview-dis
|
||||
*/
|
||||
public class FixedListView extends ListView {
|
||||
|
||||
public FixedListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public FixedListView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public FixedListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// Calculate height of the entire list by providing a very large
|
||||
// height hint. But do not use the highest 2 bits of this integer;
|
||||
// those are reserved for the MeasureSpec mode.
|
||||
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
|
||||
super.onMeasure(widthMeasureSpec, expandSpec);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* A list preference which persists its values as integers instead of strings. Code reading the
|
||||
* values should use {@link android.content.SharedPreferences#getInt}. When using XML-declared
|
||||
* arrays for entry values, the arrays should be regular string arrays containing valid integer
|
||||
* values.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class IntegerListPreference extends ListPreference {
|
||||
|
||||
public IntegerListPreference(Context context) {
|
||||
super(context);
|
||||
|
||||
verifyEntryValues(null);
|
||||
}
|
||||
|
||||
public IntegerListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
verifyEntryValues(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryValues(CharSequence[] entryValues) {
|
||||
CharSequence[] oldValues = getEntryValues();
|
||||
super.setEntryValues(entryValues);
|
||||
verifyEntryValues(oldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryValues(int entryValuesResId) {
|
||||
CharSequence[] oldValues = getEntryValues();
|
||||
super.setEntryValues(entryValuesResId);
|
||||
verifyEntryValues(oldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPersistedString(String defaultReturnValue) {
|
||||
// During initial load, there's no known default value
|
||||
int defaultIntegerValue = Integer.MIN_VALUE;
|
||||
if (defaultReturnValue != null) {
|
||||
defaultIntegerValue = Integer.parseInt(defaultReturnValue);
|
||||
}
|
||||
|
||||
// When the list preference asks us to read a string, instead read an
|
||||
// integer.
|
||||
int value = getPersistedInt(defaultIntegerValue);
|
||||
return Integer.toString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean persistString(String value) {
|
||||
// When asked to save a string, instead save an integer
|
||||
return persistInt(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
private void verifyEntryValues(CharSequence[] oldValues) {
|
||||
CharSequence[] entryValues = getEntryValues();
|
||||
if (entryValues == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (CharSequence entryValue : entryValues) {
|
||||
try {
|
||||
Integer.parseInt(entryValue.toString());
|
||||
} catch (NumberFormatException nfe) {
|
||||
super.setEntryValues(oldValues);
|
||||
throw nfe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.util.Choice;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.DatePicker;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private PGPSecretKey mKey;
|
||||
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
private boolean mIsMasterKey;
|
||||
BootstrapButton mDeleteButton;
|
||||
TextView mAlgorithm;
|
||||
TextView mKeyId;
|
||||
Spinner mUsage;
|
||||
TextView mCreationDate;
|
||||
BootstrapButton mExpiryDateButton;
|
||||
GregorianCalendar mExpiryDate;
|
||||
|
||||
private int mDatePickerResultCount = 0;
|
||||
private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = new DatePickerDialog.OnDateSetListener() {
|
||||
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
|
||||
if (mDatePickerResultCount++ == 0) // Note: Ignore results after the first one - android
|
||||
// sends multiples.
|
||||
{
|
||||
GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth);
|
||||
setExpiryDate(date);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public KeyEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public KeyEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mAlgorithm = (TextView) findViewById(R.id.algorithm);
|
||||
mKeyId = (TextView) findViewById(R.id.keyId);
|
||||
mCreationDate = (TextView) findViewById(R.id.creation);
|
||||
mExpiryDateButton = (BootstrapButton) findViewById(R.id.expiry);
|
||||
mUsage = (Spinner) findViewById(R.id.usage);
|
||||
Choice choices[] = {
|
||||
new Choice(Id.choice.usage.sign_only, getResources().getString(
|
||||
R.string.choice_sign_only)),
|
||||
new Choice(Id.choice.usage.encrypt_only, getResources().getString(
|
||||
R.string.choice_encrypt_only)),
|
||||
new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString(
|
||||
R.string.choice_sign_and_encrypt)), };
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mUsage.setAdapter(adapter);
|
||||
|
||||
mDeleteButton = (BootstrapButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
|
||||
setExpiryDate(null);
|
||||
|
||||
mExpiryDateButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
GregorianCalendar date = mExpiryDate;
|
||||
if (date == null) {
|
||||
date = new GregorianCalendar();
|
||||
}
|
||||
|
||||
DatePickerDialog dialog = new DatePickerDialog(getContext(),
|
||||
mExpiryDateSetListener, date.get(Calendar.YEAR), date.get(Calendar.MONTH),
|
||||
date.get(Calendar.DAY_OF_MONTH));
|
||||
mDatePickerResultCount = 0;
|
||||
dialog.setCancelable(true);
|
||||
dialog.setButton(Dialog.BUTTON_NEGATIVE,
|
||||
getContext().getString(R.string.btn_no_date),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (mDatePickerResultCount++ == 0) // Note: Ignore results after the
|
||||
// first
|
||||
// one - android sends multiples.
|
||||
{
|
||||
setExpiryDate(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setCanEdit(boolean bCanEdit) {
|
||||
if (!bCanEdit) {
|
||||
mDeleteButton.setVisibility(View.INVISIBLE);
|
||||
mUsage.setEnabled(false);
|
||||
mExpiryDateButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void setValue(PGPSecretKey key, boolean isMasterKey, int usage) {
|
||||
mKey = key;
|
||||
|
||||
mIsMasterKey = isMasterKey;
|
||||
if (mIsMasterKey) {
|
||||
mDeleteButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(key));
|
||||
String keyId1Str = PgpKeyHelper.convertKeyIdToHex(key.getKeyID());
|
||||
String keyId2Str = PgpKeyHelper.convertKeyIdToHex(key.getKeyID() >> 32);
|
||||
mKeyId.setText(keyId1Str + " " + keyId2Str);
|
||||
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
|
||||
boolean isDSAKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.DSA);
|
||||
if (!isElGamalKey) {
|
||||
choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString(
|
||||
R.string.choice_sign_only)));
|
||||
}
|
||||
if (!mIsMasterKey && !isDSAKey) {
|
||||
choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString(
|
||||
R.string.choice_encrypt_only)));
|
||||
}
|
||||
if (!isElGamalKey && !isDSAKey) {
|
||||
choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString(
|
||||
R.string.choice_sign_and_encrypt)));
|
||||
}
|
||||
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mUsage.setAdapter(adapter);
|
||||
|
||||
// Set value in choice dropdown to key
|
||||
int selectId = 0;
|
||||
if (PgpKeyHelper.isEncryptionKey(key)) {
|
||||
if (PgpKeyHelper.isSigningKey(key)) {
|
||||
selectId = Id.choice.usage.sign_and_encrypt;
|
||||
} else {
|
||||
selectId = Id.choice.usage.encrypt_only;
|
||||
}
|
||||
} else {
|
||||
// set usage if it is predefined
|
||||
if (usage != -1) {
|
||||
selectId = usage;
|
||||
} else {
|
||||
selectId = Id.choice.usage.sign_only;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (int i = 0; i < choices.size(); ++i) {
|
||||
if (choices.get(i).getId() == selectId) {
|
||||
mUsage.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GregorianCalendar cal = new GregorianCalendar();
|
||||
cal.setTime(PgpKeyHelper.getCreationDate(key));
|
||||
mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime()));
|
||||
cal = new GregorianCalendar();
|
||||
Date date = PgpKeyHelper.getExpiryDate(key);
|
||||
if (date == null) {
|
||||
setExpiryDate(null);
|
||||
} else {
|
||||
cal.setTime(PgpKeyHelper.getExpiryDate(key));
|
||||
setExpiryDate(cal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public PGPSecretKey getValue() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup) getParent();
|
||||
if (v == mDeleteButton) {
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
|
||||
private void setExpiryDate(GregorianCalendar date) {
|
||||
mExpiryDate = date;
|
||||
if (date == null) {
|
||||
mExpiryDateButton.setText(getContext().getString(R.string.none));
|
||||
} else {
|
||||
mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime()));
|
||||
}
|
||||
}
|
||||
|
||||
public GregorianCalendar getExpiryDate() {
|
||||
return mExpiryDate;
|
||||
}
|
||||
|
||||
public int getUsage() {
|
||||
return ((Choice) mUsage.getSelectedItem()).getId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
BootstrapButton mDeleteButton;
|
||||
TextView mServer;
|
||||
|
||||
public KeyServerEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public KeyServerEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mServer = (TextView) findViewById(R.id.server);
|
||||
|
||||
mDeleteButton = (BootstrapButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
mServer.setText(value);
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return mServer.getText().toString().trim();
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup) getParent();
|
||||
if (v == mDeleteButton) {
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
|
||||
import org.sufficientlysecure.keychain.util.Choice;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class SectionView extends LinearLayout implements OnClickListener, EditorListener {
|
||||
private LayoutInflater mInflater;
|
||||
private BootstrapButton mPlusButton;
|
||||
private ViewGroup mEditors;
|
||||
private TextView mTitle;
|
||||
private int mType = 0;
|
||||
|
||||
private Choice mNewKeyAlgorithmChoice;
|
||||
private int mNewKeySize;
|
||||
private boolean canEdit = true;
|
||||
|
||||
private SherlockFragmentActivity mActivity;
|
||||
|
||||
private ProgressDialogFragment mGeneratingDialog;
|
||||
|
||||
public SectionView(Context context) {
|
||||
super(context);
|
||||
mActivity = (SherlockFragmentActivity) context;
|
||||
}
|
||||
|
||||
public SectionView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mActivity = (SherlockFragmentActivity) context;
|
||||
}
|
||||
|
||||
public ViewGroup getEditors() {
|
||||
return mEditors;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
mType = type;
|
||||
switch (type) {
|
||||
case Id.type.user_id: {
|
||||
mTitle.setText(R.string.section_user_ids);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.type.key: {
|
||||
mTitle.setText(R.string.section_keys);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setCanEdit(boolean bCanEdit) {
|
||||
canEdit = bCanEdit;
|
||||
if (!canEdit) {
|
||||
mPlusButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mPlusButton = (BootstrapButton) findViewById(R.id.plusbutton);
|
||||
mPlusButton.setOnClickListener(this);
|
||||
|
||||
mEditors = (ViewGroup) findViewById(R.id.editors);
|
||||
mTitle = (TextView) findViewById(R.id.title);
|
||||
|
||||
updateEditorsVisible();
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void onDeleted(Editor editor) {
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
protected void updateEditorsVisible() {
|
||||
final boolean hasChildren = mEditors.getChildCount() > 0;
|
||||
mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public void onClick(View v) {
|
||||
if (canEdit) {
|
||||
switch (mType) {
|
||||
case Id.type.user_id: {
|
||||
UserIdEditor view = (UserIdEditor) mInflater.inflate(
|
||||
R.layout.edit_key_user_id_item, mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
if (mEditors.getChildCount() == 0) {
|
||||
view.setIsMainUserId(true);
|
||||
}
|
||||
mEditors.addView(view);
|
||||
break;
|
||||
}
|
||||
|
||||
case Id.type.key: {
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
|
||||
|
||||
View view = mInflater.inflate(R.layout.create_key_dialog, null);
|
||||
dialog.setView(view);
|
||||
dialog.setTitle(R.string.title_create_key);
|
||||
|
||||
boolean wouldBeMasterKey = (mEditors.getChildCount() == 0);
|
||||
|
||||
final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm);
|
||||
Vector<Choice> choices = new Vector<Choice>();
|
||||
choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString(
|
||||
R.string.dsa)));
|
||||
if (!wouldBeMasterKey) {
|
||||
choices.add(new Choice(Id.choice.algorithm.elgamal, getResources().getString(
|
||||
R.string.elgamal)));
|
||||
}
|
||||
|
||||
choices.add(new Choice(Id.choice.algorithm.rsa, getResources().getString(
|
||||
R.string.rsa)));
|
||||
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
algorithm.setAdapter(adapter);
|
||||
// make RSA the default
|
||||
for (int i = 0; i < choices.size(); ++i) {
|
||||
if (choices.get(i).getId() == Id.choice.algorithm.rsa) {
|
||||
algorithm.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final Spinner keySize = (Spinner) view.findViewById(R.id.create_key_size);
|
||||
ArrayAdapter<CharSequence> keySizeAdapter = ArrayAdapter.createFromResource(
|
||||
getContext(), R.array.key_size_spinner_values,
|
||||
android.R.layout.simple_spinner_item);
|
||||
keySizeAdapter
|
||||
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
keySize.setAdapter(keySizeAdapter);
|
||||
keySize.setSelection(3); // Default to 4096 for the key length
|
||||
dialog.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
try {
|
||||
int nKeyIndex = keySize.getSelectedItemPosition();
|
||||
switch (nKeyIndex) {
|
||||
case 0:
|
||||
mNewKeySize = 512;
|
||||
break;
|
||||
case 1:
|
||||
mNewKeySize = 1024;
|
||||
break;
|
||||
case 2:
|
||||
mNewKeySize = 2048;
|
||||
break;
|
||||
case 3:
|
||||
mNewKeySize = 4096;
|
||||
break;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
mNewKeySize = 0;
|
||||
}
|
||||
|
||||
mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem();
|
||||
createKey();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.setCancelable(true);
|
||||
dialog.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.create().show();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
}
|
||||
|
||||
public void setUserIds(Vector<String> list) {
|
||||
if (mType != Id.type.user_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEditors.removeAllViews();
|
||||
for (String userId : list) {
|
||||
UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item,
|
||||
mEditors, false);
|
||||
view.setEditorListener(this);
|
||||
view.setValue(userId);
|
||||
if (mEditors.getChildCount() == 0) {
|
||||
view.setIsMainUserId(true);
|
||||
}
|
||||
view.setCanEdit(canEdit);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
public void setKeys(Vector<PGPSecretKey> list, Vector<Integer> usages) {
|
||||
if (mType != Id.type.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEditors.removeAllViews();
|
||||
|
||||
// go through all keys and set view based on them
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors,
|
||||
false);
|
||||
view.setEditorListener(this);
|
||||
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||
view.setValue(list.get(i), isMasterKey, usages.get(i));
|
||||
view.setCanEdit(canEdit);
|
||||
mEditors.addView(view);
|
||||
}
|
||||
|
||||
this.updateEditorsVisible();
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(mActivity, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_GENERATE_KEY);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
String passPhrase;
|
||||
if (mEditors.getChildCount() > 0) {
|
||||
PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
|
||||
passPhrase = PassphraseCacheService
|
||||
.getCachedPassphrase(mActivity, masterKey.getKeyID());
|
||||
|
||||
data.putByteArray(KeychainIntentService.GENERATE_KEY_MASTER_KEY,
|
||||
PgpConversionHelper.PGPSecretKeyToBytes(masterKey));
|
||||
} else {
|
||||
passPhrase = "";
|
||||
}
|
||||
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passPhrase);
|
||||
data.putInt(KeychainIntentService.GENERATE_KEY_ALGORITHM, mNewKeyAlgorithmChoice.getId());
|
||||
data.putInt(KeychainIntentService.GENERATE_KEY_KEY_SIZE, mNewKeySize);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// show progress dialog
|
||||
mGeneratingDialog = ProgressDialogFragment.newInstance(R.string.progress_generating,
|
||||
ProgressDialog.STYLE_SPINNER);
|
||||
|
||||
// Message is received after generating is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity,
|
||||
mGeneratingDialog) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get new key from data bundle returned from service
|
||||
Bundle data = message.getData();
|
||||
PGPSecretKeyRing newKeyRing = (PGPSecretKeyRing) PgpConversionHelper
|
||||
.BytesToPGPKeyRing(data
|
||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
|
||||
|
||||
boolean isMasterKey = (mEditors.getChildCount() == 0);
|
||||
|
||||
// take only the key from this ring
|
||||
PGPSecretKey newKey = null;
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> it = newKeyRing.getSecretKeys();
|
||||
|
||||
if (isMasterKey) {
|
||||
newKey = it.next();
|
||||
} else {
|
||||
// first one is the master key
|
||||
it.next();
|
||||
newKey = it.next();
|
||||
}
|
||||
|
||||
// add view with new key
|
||||
KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item,
|
||||
mEditors, false);
|
||||
view.setEditorListener(SectionView.this);
|
||||
view.setValue(newKey, isMasterKey, -1);
|
||||
mEditors.addView(view);
|
||||
SectionView.this.updateEditorsVisible();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
mGeneratingDialog.show(mActivity.getSupportFragmentManager(), "dialog");
|
||||
|
||||
// start service with intent
|
||||
mActivity.startService(intent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Eric Frohnhoefer
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Copied from StickyListHeaders lib example
|
||||
*
|
||||
* @author Eric Frohnhoefer
|
||||
*/
|
||||
public class UnderlineTextView extends TextView {
|
||||
private final Paint mPaint = new Paint();
|
||||
private int mUnderlineHeight = 0;
|
||||
|
||||
public UnderlineTextView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public UnderlineTextView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public UnderlineTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
Resources r = getResources();
|
||||
mUnderlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2,
|
||||
r.getDisplayMetrics());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(int left, int top, int right, int bottom) {
|
||||
super.setPadding(left, top, right, bottom + mUnderlineHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
// Draw the underline the same color as the text
|
||||
mPaint.setColor(getTextColors().getDefaultColor());
|
||||
canvas.drawRect(0, getHeight() - mUnderlineHeight, getWidth(), getHeight(), mPaint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
|
||||
private EditorListener mEditorListener = null;
|
||||
|
||||
private BootstrapButton mDeleteButton;
|
||||
private RadioButton mIsMainUserId;
|
||||
private EditText mName;
|
||||
private EditText mEmail;
|
||||
private EditText mComment;
|
||||
|
||||
// see http://www.regular-expressions.info/email.html
|
||||
// RFC 2822 if we omit the syntax using double quotes and square brackets
|
||||
// android.util.Patterns.EMAIL_ADDRESS is only available as of Android 2.2+
|
||||
private static final Pattern EMAIL_PATTERN = Pattern
|
||||
.compile(
|
||||
"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public static class NoNameException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773343L;
|
||||
|
||||
public NoNameException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCanEdit(boolean bCanEdit) {
|
||||
if (!bCanEdit) {
|
||||
mDeleteButton.setVisibility(View.INVISIBLE);
|
||||
mName.setEnabled(false);
|
||||
mIsMainUserId.setEnabled(false);
|
||||
mEmail.setEnabled(false);
|
||||
mComment.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NoEmailException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773344L;
|
||||
|
||||
public NoEmailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidEmailException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773345L;
|
||||
|
||||
public InvalidEmailException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public UserIdEditor(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public UserIdEditor(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
setDrawingCacheEnabled(true);
|
||||
setAlwaysDrawnWithCacheEnabled(true);
|
||||
|
||||
mDeleteButton = (BootstrapButton) findViewById(R.id.delete);
|
||||
mDeleteButton.setOnClickListener(this);
|
||||
mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId);
|
||||
mIsMainUserId.setOnClickListener(this);
|
||||
|
||||
mName = (EditText) findViewById(R.id.name);
|
||||
mEmail = (EditText) findViewById(R.id.email);
|
||||
mComment = (EditText) findViewById(R.id.comment);
|
||||
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setValue(String userId) {
|
||||
mName.setText("");
|
||||
mComment.setText("");
|
||||
mEmail.setText("");
|
||||
|
||||
Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$");
|
||||
Matcher matcher = withComment.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
mName.setText(matcher.group(1));
|
||||
mComment.setText(matcher.group(2));
|
||||
mEmail.setText(matcher.group(3));
|
||||
return;
|
||||
}
|
||||
|
||||
Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$");
|
||||
matcher = withoutComment.matcher(userId);
|
||||
if (matcher.matches()) {
|
||||
mName.setText(matcher.group(1));
|
||||
mEmail.setText(matcher.group(2));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public String getValue() throws NoNameException, NoEmailException, InvalidEmailException {
|
||||
String name = ("" + mName.getText()).trim();
|
||||
String email = ("" + mEmail.getText()).trim();
|
||||
String comment = ("" + mComment.getText()).trim();
|
||||
|
||||
if (email.length() > 0) {
|
||||
Matcher emailMatcher = EMAIL_PATTERN.matcher(email);
|
||||
if (!emailMatcher.matches()) {
|
||||
throw new InvalidEmailException(getContext().getString(R.string.error_invalid_email,
|
||||
email));
|
||||
}
|
||||
}
|
||||
|
||||
String userId = name;
|
||||
if (comment.length() > 0) {
|
||||
userId += " (" + comment + ")";
|
||||
}
|
||||
if (email.length() > 0) {
|
||||
userId += " <" + email + ">";
|
||||
}
|
||||
|
||||
if (userId.equals("")) {
|
||||
// ok, empty one...
|
||||
return userId;
|
||||
}
|
||||
|
||||
// otherwise make sure that name and email exist
|
||||
if (name.equals("")) {
|
||||
throw new NoNameException("need a name");
|
||||
}
|
||||
|
||||
if (email.equals("")) {
|
||||
throw new NoEmailException("need an email");
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
final ViewGroup parent = (ViewGroup) getParent();
|
||||
if (v == mDeleteButton) {
|
||||
boolean wasMainUserId = mIsMainUserId.isChecked();
|
||||
parent.removeView(this);
|
||||
if (mEditorListener != null) {
|
||||
mEditorListener.onDeleted(this);
|
||||
}
|
||||
if (wasMainUserId && parent.getChildCount() > 0) {
|
||||
UserIdEditor editor = (UserIdEditor) parent.getChildAt(0);
|
||||
editor.setIsMainUserId(true);
|
||||
}
|
||||
} else if (v == mIsMainUserId) {
|
||||
for (int i = 0; i < parent.getChildCount(); ++i) {
|
||||
UserIdEditor editor = (UserIdEditor) parent.getChildAt(i);
|
||||
if (editor == this) {
|
||||
editor.setIsMainUserId(true);
|
||||
} else {
|
||||
editor.setIsMainUserId(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setIsMainUserId(boolean value) {
|
||||
mIsMainUserId.setChecked(value);
|
||||
}
|
||||
|
||||
public boolean isMainUserId() {
|
||||
return mIsMainUserId.isChecked();
|
||||
}
|
||||
|
||||
public void setEditorListener(EditorListener listener) {
|
||||
mEditorListener = listener;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user