New Gradle project structure

This commit is contained in:
Dominik Schürmann
2014-01-27 14:18:25 +01:00
parent 08e57019b0
commit cb3ca37db9
359 changed files with 0 additions and 215 deletions

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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) {
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}
};
}

View File

@@ -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;
}
}

View File

@@ -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";
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}