keep up with master

This commit is contained in:
Ashley Hughes 2014-03-07 15:16:13 +00:00
commit fba0e2af1d
168 changed files with 2898 additions and 3031 deletions

3
.gitignore vendored
View File

@ -28,3 +28,6 @@ pom.xml.*
#IntelliJ IDEA
.idea
*.iml
#OS Specific
[Tt]humbs.db

16
.travis.yml Normal file
View File

@ -0,0 +1,16 @@
language: java
jdk: oraclejdk7
before_install:
# Install base Android SDK
- sudo apt-get update -qq
- if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs; fi
- wget http://dl.google.com/android/android-sdk_r22.3-linux.tgz
- tar xzf android-sdk_r22.3-linux.tgz
- export ANDROID_HOME=$PWD/android-sdk-linux
- export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
# Install required Android components.
- echo "y" | android update sdk -a --filter build-tools-19.0.3,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force
install: echo "Installation done"
script: gradle assemble -S -q

View File

@ -1,3 +1,3 @@
task wrapper(type: Wrapper) {
gradleVersion = '1.10'
}
}

View File

@ -5,7 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.3'
classpath 'com.android.tools.build:gradle:0.9.0'
}
}
@ -18,7 +18,7 @@ dependencies {
android {
compileSdkVersion 19
buildToolsVersion "19.0.1"
buildToolsVersion "19.0.3"
defaultConfig {
minSdkVersion 9

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sufficientlysecure.keychain.demo"
android:versionCode="2"
android:versionName="1.1">
android:versionCode="3"
android:versionName="2">
<uses-sdk
android:minSdkVersion="9"

View File

@ -112,11 +112,11 @@ public class IntentActivity extends PreferenceActivity {
byte[] pubkey = null;
try {
pubkey = TEST_PUBKEY.getBytes("UTF-8");
intent.putExtra(OpenKeychainIntents.IMPORT_KEY_EXTRA_KEY_BYTES, pubkey);
startActivity(intent);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
Log.e(Constants.TAG, "UnsupportedEncodingException", e);
}
intent.putExtra(OpenKeychainIntents.IMPORT_KEY_EXTRA_KEY_BYTES, pubkey);
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(IntentActivity.this, "Activity not found!", Toast.LENGTH_LONG).show();
}

View File

@ -33,7 +33,6 @@ import android.widget.Toast;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpConstants;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.io.ByteArrayInputStream;
@ -73,25 +72,25 @@ public class OpenPgpProviderActivity extends Activity {
mSign.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sign(new Bundle());
sign(new Intent());
}
});
mEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encrypt(new Bundle());
encrypt(new Intent());
}
});
mSignAndEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
signAndEncrypt(new Bundle());
signAndEncrypt(new Intent());
}
});
mDecryptAndVerify.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
decryptAndVerify(new Bundle());
decryptAndVerify(new Intent());
}
});
@ -169,11 +168,11 @@ public class OpenPgpProviderActivity extends Activity {
}
@Override
public void onReturn(Bundle result) {
switch (result.getInt(OpenPgpConstants.RESULT_CODE)) {
case OpenPgpConstants.RESULT_CODE_SUCCESS: {
public void onReturn(Intent result) {
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
case OpenPgpApi.RESULT_CODE_SUCCESS: {
try {
Log.d(OpenPgpConstants.TAG, "result: " + os.toByteArray().length
Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length
+ " str=" + os.toString("UTF-8"));
if (returnToCiphertextField) {
@ -185,15 +184,15 @@ public class OpenPgpProviderActivity extends Activity {
Log.e(Constants.TAG, "UnsupportedEncodingException", e);
}
if (result.getBoolean(OpenPgpConstants.RESULT_SIGNATURE, false)) {
if (result.hasExtra(OpenPgpApi.RESULT_SIGNATURE)) {
OpenPgpSignatureResult sigResult
= result.getParcelable(OpenPgpConstants.RESULT_SIGNATURE);
= result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
handleSignature(sigResult);
}
break;
}
case OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED: {
PendingIntent pi = result.getParcelable(OpenPgpConstants.RESULT_INTENT);
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
try {
OpenPgpProviderActivity.this.startIntentSenderForResult(pi.getIntentSender(),
requestCode, null, 0, 0, 0);
@ -202,8 +201,8 @@ public class OpenPgpProviderActivity extends Activity {
}
break;
}
case OpenPgpConstants.RESULT_CODE_ERROR: {
OpenPgpError error = result.getParcelable(OpenPgpConstants.RESULT_ERRORS);
case OpenPgpApi.RESULT_CODE_ERROR: {
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
handleError(error);
break;
}
@ -211,46 +210,50 @@ public class OpenPgpProviderActivity extends Activity {
}
}
public void sign(Bundle params) {
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
public void sign(Intent data) {
data.setAction(OpenPgpApi.ACTION_SIGN);
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
api.sign(params, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN));
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN));
}
public void encrypt(Bundle params) {
params.putStringArray(OpenPgpConstants.PARAMS_USER_IDS, mEncryptUserIds.getText().toString().split(","));
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
public void encrypt(Intent data) {
data.setAction(OpenPgpApi.ACTION_ENCRYPT);
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
api.encrypt(params, is, os, new MyCallback(true, os, REQUEST_CODE_ENCRYPT));
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_ENCRYPT));
}
public void signAndEncrypt(Bundle params) {
params.putStringArray(OpenPgpConstants.PARAMS_USER_IDS, mEncryptUserIds.getText().toString().split(","));
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
public void signAndEncrypt(Intent data) {
data.setAction(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
InputStream is = getInputstream(false);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
api.signAndEncrypt(params, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN_AND_ENCRYPT));
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN_AND_ENCRYPT));
}
public void decryptAndVerify(Bundle params) {
params.putBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
public void decryptAndVerify(Intent data) {
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
InputStream is = getInputstream(true);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
api.decryptAndVerify(params, is, os, new MyCallback(false, os, REQUEST_CODE_DECRYPT_AND_VERIFY));
api.executeApiAsync(data, is, os, new MyCallback(false, os, REQUEST_CODE_DECRYPT_AND_VERIFY));
}
@Override
@ -261,29 +264,28 @@ public class OpenPgpProviderActivity extends Activity {
// try again after user interaction
if (resultCode == RESULT_OK) {
/*
* The params originally given to the pgp method are are again
* The data originally given to the pgp method are are again
* returned here to be used when calling again after user interaction.
*
* They also contain results from the user interaction which happened,
* for example selected key ids.
*/
Bundle params = data.getBundleExtra(OpenPgpConstants.PI_RESULT_PARAMS);
switch (requestCode) {
case REQUEST_CODE_SIGN: {
sign(params);
sign(data);
break;
}
case REQUEST_CODE_ENCRYPT: {
encrypt(params);
encrypt(data);
break;
}
case REQUEST_CODE_SIGN_AND_ENCRYPT: {
signAndEncrypt(params);
signAndEncrypt(data);
break;
}
case REQUEST_CODE_DECRYPT_AND_VERIFY: {
decryptAndVerify(params);
decryptAndVerify(data);
break;
}
}

View File

@ -1,6 +1,6 @@
#Fri Feb 14 01:26:40 CET 2014
#Thu Mar 06 22:23:44 CET 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip

View File

@ -5,7 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.3'
classpath 'com.android.tools.build:gradle:0.9.0'
}
}
@ -13,7 +13,7 @@ apply plugin: 'android-library'
android {
compileSdkVersion 19
buildToolsVersion '19.0.1'
buildToolsVersion '19.0.3'
// NOTE: We are using the old folder structure to also support Eclipse
sourceSets {

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="keychain-api-library" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

View File

@ -0,0 +1,20 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -2,5 +2,6 @@
<resources>
<string name="openpgp_list_preference_none">None</string>
<string name="openpgp_install_openkeychain_via">Install OpenKeychain via %s</string>
</resources>

View File

@ -18,68 +18,7 @@ package org.openintents.openpgp;
interface IOpenPgpService {
/**
* General extras
* --------------
*
* Bundle params:
* int api_version (required)
* boolean ascii_armor (request ascii armor for ouput)
*
* returned Bundle:
* int result_code (0, 1, or 2 (see OpenPgpConstants))
* OpenPgpError error (if result_code == 0)
* Intent intent (if result_code == 2)
*
*/
/**
* Sign only
*
* optional params:
* String passphrase (for key passphrase)
*/
Bundle sign(in Bundle params, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
/**
* Encrypt
*
* Bundle params:
* long[] key_ids
* or
* String[] user_ids (= emails of recipients) (if more than one key has this user_id, a PendingIntent is returned)
*
* optional params:
* String passphrase (for key passphrase)
*/
Bundle encrypt(in Bundle params, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
/**
* Sign and encrypt
*
* Bundle params:
* same as in encrypt()
*/
Bundle signAndEncrypt(in Bundle params, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
/**
* Decrypts and verifies given input bytes. This methods handles encrypted-only, signed-and-encrypted,
* and also signed-only input.
*
* returned Bundle:
* OpenPgpSignatureResult signature_result
*/
Bundle decryptAndVerify(in Bundle params, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
/**
* Retrieves key ids based on given user ids (=emails)
*
* Bundle params:
* String[] user_ids
*
* returned Bundle:
* long[] key_ids
*/
Bundle getKeyIds(in Bundle params);
// see OpenPgpApi for documentation
Intent execute(in Intent data, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
}

View File

@ -25,10 +25,8 @@ public class OpenPgpSignatureResult implements Parcelable {
// successfully verified signature, with certified public key
public static final int SIGNATURE_SUCCESS_CERTIFIED = 1;
// no public key was found for this signature verification
// you can retrieve the key with
// getKeys(new String[] {String.valueOf(signatureResult.getKeyId)}, true, callback)
public static final int SIGNATURE_UNKNOWN_PUB_KEY = 2;
// successfully verified signature, but with certified public key
// successfully verified signature, but with uncertified public key
public static final int SIGNATURE_SUCCESS_UNCERTIFIED = 3;
int status;
@ -40,24 +38,40 @@ public class OpenPgpSignatureResult implements Parcelable {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public boolean isSignatureOnly() {
return signatureOnly;
}
public void setSignatureOnly(boolean signatureOnly) {
this.signatureOnly = signatureOnly;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public long getKeyId() {
return keyId;
}
public void setKeyId(long keyId) {
this.keyId = keyId;
}
public OpenPgpSignatureResult() {
}
public OpenPgpSignatureResult(int signatureStatus, String signatureUserId,
boolean signatureOnly, long keyId) {
boolean signatureOnly, long keyId) {
this.status = signatureStatus;
this.signatureOnly = signatureOnly;
this.userId = signatureUserId;

View File

@ -16,137 +16,213 @@
package org.openintents.openpgp.util;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpError;
import java.io.InputStream;
import java.io.OutputStream;
public class OpenPgpApi {
public static final String TAG = "OpenPgp API";
public static final int API_VERSION = 2;
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
/**
* General extras
* --------------
*
* required extras:
* int EXTRA_API_VERSION (always required)
*
* returned extras:
* int RESULT_CODE (RESULT_CODE_ERROR, RESULT_CODE_SUCCESS or RESULT_CODE_USER_INTERACTION_REQUIRED)
* OpenPgpError RESULT_ERROR (if RESULT_CODE == RESULT_CODE_ERROR)
* PendingIntent RESULT_INTENT (if RESULT_CODE == RESULT_CODE_USER_INTERACTION_REQUIRED)
*/
/**
* Sign only
*
* optional extras:
* boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for ouput)
* String EXTRA_PASSPHRASE (key passphrase)
*/
public static final String ACTION_SIGN = "org.openintents.openpgp.action.SIGN";
/**
* Encrypt
*
* required extras:
* String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT)
* or
* long[] EXTRA_KEY_IDS
*
* optional extras:
* boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for ouput)
* String EXTRA_PASSPHRASE (key passphrase)
*/
public static final String ACTION_ENCRYPT = "org.openintents.openpgp.action.ENCRYPT";
/**
* Sign and encrypt
*
* required extras:
* String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT)
* or
* long[] EXTRA_KEY_IDS
*
* optional extras:
* boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for ouput)
* String EXTRA_PASSPHRASE (key passphrase)
*/
public static final String ACTION_SIGN_AND_ENCRYPT = "org.openintents.openpgp.action.SIGN_AND_ENCRYPT";
/**
* Decrypts and verifies given input stream. This methods handles encrypted-only, signed-and-encrypted,
* and also signed-only input.
*
* If OpenPgpSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY
* in addition a PendingIntent is returned via RESULT_INTENT to download missing keys.
*
* optional extras:
* boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for ouput)
*
* returned extras:
* OpenPgpSignatureResult RESULT_SIGNATURE
*/
public static final String ACTION_DECRYPT_VERIFY = "org.openintents.openpgp.action.DECRYPT_VERIFY";
/**
* Get key ids based on given user ids (=emails)
*
* required extras:
* String[] EXTRA_USER_IDS
*
* returned extras:
* long[] EXTRA_KEY_IDS
*/
public static final String ACTION_GET_KEY_IDS = "org.openintents.openpgp.action.GET_KEY_IDS";
/**
* This action returns RESULT_CODE_SUCCESS if the OpenPGP Provider already has the key
* corresponding to the given key id in its database.
*
* It returns RESULT_CODE_USER_INTERACTION_REQUIRED if the Provider does not have the key.
* The PendingIntent from RESULT_INTENT can be used to retrieve those from a keyserver.
*
* required extras:
* long EXTRA_KEY_ID
*/
public static final String ACTION_GET_KEY = "org.openintents.openpgp.action.GET_KEY";
/* Intent extras */
public static final String EXTRA_API_VERSION = "api_version";
// SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
// request ASCII Armor for output
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
public static final String EXTRA_REQUEST_ASCII_ARMOR = "ascii_armor";
// ENCRYPT, SIGN_AND_ENCRYPT
public static final String EXTRA_USER_IDS = "user_ids";
public static final String EXTRA_KEY_IDS = "key_ids";
// optional extras:
public static final String EXTRA_PASSPHRASE = "passphrase";
// GET_KEY
public static final String EXTRA_KEY_ID = "key_id";
/* Service Intent returns */
public static final String RESULT_CODE = "result_code";
// get actual error object from RESULT_ERROR
public static final int RESULT_CODE_ERROR = 0;
// success!
public static final int RESULT_CODE_SUCCESS = 1;
// get PendingIntent from RESULT_INTENT, start PendingIntent with startIntentSenderForResult,
// and execute service method again in onActivityResult
public static final int RESULT_CODE_USER_INTERACTION_REQUIRED = 2;
public static final String RESULT_ERROR = "error";
public static final String RESULT_INTENT = "intent";
// DECRYPT_VERIFY
public static final String RESULT_SIGNATURE = "signature";
IOpenPgpService mService;
Context mContext;
private static final int OPERATION_SIGN = 0;
private static final int OPERATION_ENCRYPT = 1;
private static final int OPERATION_SIGN_ENCRYPT = 2;
private static final int OPERATION_DECRYPT_VERIFY = 3;
private static final int OPERATION_GET_KEY_IDS = 4;
public OpenPgpApi(Context context, IOpenPgpService service) {
this.mContext = context;
this.mService = service;
}
public Bundle sign(InputStream is, final OutputStream os) {
return executeApi(OPERATION_SIGN, new Bundle(), is, os);
}
public Bundle sign(Bundle params, InputStream is, final OutputStream os) {
return executeApi(OPERATION_SIGN, params, is, os);
}
public void sign(Bundle params, InputStream is, final OutputStream os, IOpenPgpCallback callback) {
executeApiAsync(OPERATION_SIGN, params, is, os, callback);
}
public Bundle encrypt(InputStream is, final OutputStream os) {
return executeApi(OPERATION_ENCRYPT, new Bundle(), is, os);
}
public Bundle encrypt(Bundle params, InputStream is, final OutputStream os) {
return executeApi(OPERATION_ENCRYPT, params, is, os);
}
public void encrypt(Bundle params, InputStream is, final OutputStream os, IOpenPgpCallback callback) {
executeApiAsync(OPERATION_ENCRYPT, params, is, os, callback);
}
public Bundle signAndEncrypt(InputStream is, final OutputStream os) {
return executeApi(OPERATION_SIGN_ENCRYPT, new Bundle(), is, os);
}
public Bundle signAndEncrypt(Bundle params, InputStream is, final OutputStream os) {
return executeApi(OPERATION_SIGN_ENCRYPT, params, is, os);
}
public void signAndEncrypt(Bundle params, InputStream is, final OutputStream os, IOpenPgpCallback callback) {
executeApiAsync(OPERATION_SIGN_ENCRYPT, params, is, os, callback);
}
public Bundle decryptAndVerify(InputStream is, final OutputStream os) {
return executeApi(OPERATION_DECRYPT_VERIFY, new Bundle(), is, os);
}
public Bundle decryptAndVerify(Bundle params, InputStream is, final OutputStream os) {
return executeApi(OPERATION_DECRYPT_VERIFY, params, is, os);
}
public void decryptAndVerify(Bundle params, InputStream is, final OutputStream os, IOpenPgpCallback callback) {
executeApiAsync(OPERATION_DECRYPT_VERIFY, params, is, os, callback);
}
public Bundle getKeyIds(Bundle params) {
return executeApi(OPERATION_GET_KEY_IDS, params, null, null);
}
public interface IOpenPgpCallback {
void onReturn(final Bundle result);
void onReturn(final Intent result);
}
private class OpenPgpAsyncTask extends AsyncTask<Void, Integer, Bundle> {
int operationId;
Bundle params;
private class OpenPgpAsyncTask extends AsyncTask<Void, Integer, Intent> {
Intent data;
InputStream is;
OutputStream os;
IOpenPgpCallback callback;
private OpenPgpAsyncTask(int operationId, Bundle params, InputStream is, OutputStream os, IOpenPgpCallback callback) {
this.operationId = operationId;
this.params = params;
private OpenPgpAsyncTask(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) {
this.data = data;
this.is = is;
this.os = os;
this.callback = callback;
}
@Override
protected Bundle doInBackground(Void... unused) {
return executeApi(operationId, params, is, os);
protected Intent doInBackground(Void... unused) {
return executeApi(data, is, os);
}
protected void onPostExecute(Bundle result) {
protected void onPostExecute(Intent result) {
callback.onReturn(result);
}
}
private void executeApiAsync(int operationId, Bundle params, InputStream is, OutputStream os, IOpenPgpCallback callback) {
new OpenPgpAsyncTask(operationId, params, is, os, callback).execute((Void[]) null);
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void executeApiAsync(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) {
OpenPgpAsyncTask task = new OpenPgpAsyncTask(data, is, os, callback);
// don't serialize async tasks!
// http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
} else {
task.execute((Void[]) null);
}
}
private Bundle executeApi(int operationId, Bundle params, InputStream is, OutputStream os) {
public Intent executeApi(Intent data, InputStream is, OutputStream os) {
try {
params.putInt(OpenPgpConstants.PARAMS_API_VERSION, OpenPgpConstants.API_VERSION);
data.putExtra(EXTRA_API_VERSION, OpenPgpApi.API_VERSION);
Bundle result = null;
Intent result = null;
if (operationId == OPERATION_GET_KEY_IDS) {
result = mService.getKeyIds(params);
if (ACTION_GET_KEY_IDS.equals(data.getAction())) {
result = mService.execute(data, null, null);
return result;
} else {
// send the input and output pfds
// pipe the input and output
ParcelFileDescriptor input = ParcelFileDescriptorUtil.pipeFrom(is,
new ParcelFileDescriptorUtil.IThreadListener() {
@Override
public void onThreadFinished(Thread thread) {
Log.d(OpenPgpConstants.TAG, "Copy to service finished");
//Log.d(OpenPgpApi.TAG, "Copy to service finished");
}
});
ParcelFileDescriptor output = ParcelFileDescriptorUtil.pipeTo(os,
@ -154,45 +230,30 @@ public class OpenPgpApi {
@Override
public void onThreadFinished(Thread thread) {
Log.d(OpenPgpConstants.TAG, "Service finished writing!");
//Log.d(OpenPgpApi.TAG, "Service finished writing!");
}
});
// blocks until result is ready
switch (operationId) {
case OPERATION_SIGN:
result = mService.sign(params, input, output);
break;
case OPERATION_ENCRYPT:
result = mService.encrypt(params, input, output);
break;
case OPERATION_SIGN_ENCRYPT:
result = mService.signAndEncrypt(params, input, output);
break;
case OPERATION_DECRYPT_VERIFY:
result = mService.decryptAndVerify(params, input, output);
break;
}
result = mService.execute(data, input, output);
// close() is required to halt the TransferThread
output.close();
// set class loader to current context to allow unparcelling
// of OpenPgpError and OpenPgpSignatureResult
// http://stackoverflow.com/a/3806769
result.setClassLoader(mContext.getClassLoader());
result.setExtrasClassLoader(mContext.getClassLoader());
return result;
}
} catch (Exception e) {
Log.e(OpenPgpConstants.TAG, "Exception", e);
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
Log.e(OpenPgpApi.TAG, "Exception", e);
Intent result = new Intent();
result.putExtra(RESULT_CODE, RESULT_CODE_ERROR);
result.putExtra(RESULT_ERROR,
new OpenPgpError(OpenPgpError.CLIENT_SIDE_ERROR, e.getMessage()));
return result;
}
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.openintents.openpgp.util;
public class OpenPgpConstants {
public static final String TAG = "OpenPgp API";
public static final int API_VERSION = 1;
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
/* Bundle params */
public static final String PARAMS_API_VERSION = "api_version";
// request ASCII Armor for output
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
public static final String PARAMS_REQUEST_ASCII_ARMOR = "ascii_armor";
// (for encrypt method)
public static final String PARAMS_USER_IDS = "user_ids";
public static final String PARAMS_KEY_IDS = "key_ids";
// optional parameter:
public static final String PARAMS_PASSPHRASE = "passphrase";
/* Service Bundle returns */
public static final String RESULT_CODE = "result_code";
public static final String RESULT_SIGNATURE = "signature";
public static final String RESULT_ERRORS = "error";
public static final String RESULT_INTENT = "intent";
// get actual error object from RESULT_ERRORS
public static final int RESULT_CODE_ERROR = 0;
// success!
public static final int RESULT_CODE_SUCCESS = 1;
// executeServiceMethod intent and do it again with params from intent
public static final int RESULT_CODE_USER_INTERACTION_REQUIRED = 2;
/* PendingIntent returns */
public static final String PI_RESULT_PARAMS = "params";
}

View File

@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
@ -30,18 +31,24 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.TextView;
import org.sufficientlysecure.keychain.api.R;
import java.util.ArrayList;
import java.util.List;
import org.sufficientlysecure.keychain.api.R;
/**
* Does not extend ListPreference, but is very similar to it!
* http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/preference/ListPreference.java/?v=source
*/
public class OpenPgpListPreference extends DialogPreference {
private ArrayList<OpenPgpProviderEntry> mProviderList = new ArrayList<OpenPgpProviderEntry>();
private static final String OPENKEYCHAIN_PACKAGE = "org.sufficientlysecure.keychain";
private static final String MARKET_INTENT_URI_BASE = "market://details?id=%s";
private static final Intent MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse(
String.format(MARKET_INTENT_URI_BASE, OPENKEYCHAIN_PACKAGE)));
private ArrayList<OpenPgpProviderEntry> mLegacyList = new ArrayList<OpenPgpProviderEntry>();
private ArrayList<OpenPgpProviderEntry> mList = new ArrayList<OpenPgpProviderEntry>();
private String mSelectedPackage;
public OpenPgpListPreference(Context context, AttributeSet attrs) {
@ -59,16 +66,25 @@ public class OpenPgpListPreference extends DialogPreference {
* @param simpleName
* @param icon
*/
public void addProvider(int position, String packageName, String simpleName, Drawable icon) {
mProviderList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon));
public void addLegacyProvider(int position, String packageName, String simpleName, Drawable icon) {
mLegacyList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon));
}
@Override
protected void onPrepareDialogBuilder(Builder builder) {
// get providers
mProviderList.clear();
Intent intent = new Intent(OpenPgpConstants.SERVICE_INTENT);
mList.clear();
// add "none"-entry
mList.add(0, new OpenPgpProviderEntry("",
getContext().getString(R.string.openpgp_list_preference_none),
getContext().getResources().getDrawable(R.drawable.ic_action_cancel_launchersize)));
// add all additional (legacy) providers
mList.addAll(mLegacyList);
// search for OpenPGP providers...
ArrayList<OpenPgpProviderEntry> providerList = new ArrayList<OpenPgpProviderEntry>();
Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT);
List<ResolveInfo> resInfo = getContext().getPackageManager().queryIntentServices(intent, 0);
if (!resInfo.isEmpty()) {
for (ResolveInfo resolveInfo : resInfo) {
@ -80,25 +96,40 @@ public class OpenPgpListPreference extends DialogPreference {
.getPackageManager()));
Drawable icon = resolveInfo.serviceInfo.loadIcon(getContext().getPackageManager());
mProviderList.add(new OpenPgpProviderEntry(packageName, simpleName, icon));
providerList.add(new OpenPgpProviderEntry(packageName, simpleName, icon));
}
}
// add "none"-entry
mProviderList.add(0, new OpenPgpProviderEntry("",
getContext().getString(R.string.openpgp_list_preference_none),
getContext().getResources().getDrawable(R.drawable.ic_action_cancel_launchersize)));
if (providerList.isEmpty()) {
// add install links if provider list is empty
resInfo = getContext().getPackageManager().queryIntentActivities
(MARKET_INTENT, 0);
for (ResolveInfo resolveInfo : resInfo) {
Intent marketIntent = new Intent(MARKET_INTENT);
marketIntent.setPackage(resolveInfo.activityInfo.packageName);
Drawable icon = resolveInfo.activityInfo.loadIcon(getContext().getPackageManager());
String marketName = String.valueOf(resolveInfo.activityInfo.applicationInfo
.loadLabel(getContext().getPackageManager()));
String simpleName = String.format(getContext().getString(R.string
.openpgp_install_openkeychain_via), marketName);
mList.add(new OpenPgpProviderEntry(OPENKEYCHAIN_PACKAGE, simpleName,
icon, marketIntent));
}
} else {
// add provider
mList.addAll(providerList);
}
// Init ArrayAdapter with OpenPGP Providers
ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(),
android.R.layout.select_dialog_singlechoice, android.R.id.text1, mProviderList) {
android.R.layout.select_dialog_singlechoice, android.R.id.text1, mList) {
public View getView(int position, View convertView, ViewGroup parent) {
// User super class to create the View
View v = super.getView(position, convertView, parent);
TextView tv = (TextView) v.findViewById(android.R.id.text1);
// Put the image on the TextView
tv.setCompoundDrawablesWithIntrinsicBounds(mProviderList.get(position).icon, null,
tv.setCompoundDrawablesWithIntrinsicBounds(mList.get(position).icon, null,
null, null);
// Add margin between image and text (support various screen densities)
@ -114,7 +145,22 @@ public class OpenPgpListPreference extends DialogPreference {
@Override
public void onClick(DialogInterface dialog, int which) {
mSelectedPackage = mProviderList.get(which).packageName;
OpenPgpProviderEntry entry = mList.get(which);
if (entry.intent != null) {
/*
* Intents are called as activity
*
* Current approach is to assume the user installed the app.
* If he does not, the selected package is not valid.
*
* However applications should always consider this could happen,
* as the user might remove the currently used OpenPGP app.
*/
getContext().startActivity(entry.intent);
}
mSelectedPackage = entry.packageName;
/*
* Clicking on an item simulates the positive button click, and dismisses
@ -144,9 +190,9 @@ public class OpenPgpListPreference extends DialogPreference {
}
private int getIndexOfProviderList(String packageName) {
for (OpenPgpProviderEntry app : mProviderList) {
for (OpenPgpProviderEntry app : mList) {
if (app.packageName.equals(packageName)) {
return mProviderList.indexOf(app);
return mList.indexOf(app);
}
}
@ -177,7 +223,7 @@ public class OpenPgpListPreference extends DialogPreference {
}
public String getEntryByValue(String packageName) {
for (OpenPgpProviderEntry app : mProviderList) {
for (OpenPgpProviderEntry app : mList) {
if (app.packageName.equals(packageName)) {
return app.simpleName;
}
@ -190,6 +236,7 @@ public class OpenPgpListPreference extends DialogPreference {
private String packageName;
private String simpleName;
private Drawable icon;
private Intent intent;
public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon) {
this.packageName = packageName;
@ -197,6 +244,11 @@ public class OpenPgpListPreference extends DialogPreference {
this.icon = icon;
}
public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon, Intent intent) {
this(packageName, simpleName, icon);
this.intent = intent;
}
@Override
public String toString() {
return simpleName;

View File

@ -52,7 +52,7 @@ public class OpenPgpUtils {
}
public static boolean isAvailable(Context context) {
Intent intent = new Intent(OpenPgpConstants.SERVICE_INTENT);
Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT);
List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0);
if (!resInfo.isEmpty()) {
return true;

View File

@ -81,21 +81,21 @@ public class ParcelFileDescriptorUtil {
}
mOut.flush(); // just to be safe
} catch (IOException e) {
//Log.e(OpenPgpConstants.TAG, "TransferThread" + getId() + ": writing failed", e);
//Log.e(OpenPgpApi.TAG, "TransferThread" + getId() + ": writing failed", e);
} finally {
try {
mIn.close();
} catch (IOException e) {
//Log.e(OpenPgpConstants.TAG, "TransferThread" + getId(), e);
//Log.e(OpenPgpApi.TAG, "TransferThread" + getId(), e);
}
try {
mOut.close();
} catch (IOException e) {
//Log.e(OpenPgpConstants.TAG, "TransferThread" + getId(), e);
//Log.e(OpenPgpApi.TAG, "TransferThread" + getId(), e);
}
}
if (mListener != null) {
//Log.d(OpenPgpConstants.TAG, "TransferThread " + getId() + " finished!");
//Log.d(OpenPgpApi.TAG, "TransferThread " + getId() + " finished!");
mListener.onThreadFinished(this);
}
}

View File

@ -3,7 +3,7 @@ apply plugin: 'android'
dependencies {
compile 'com.android.support:support-v4:19.0.1'
compile 'com.android.support:appcompat-v7:19.0.1'
compile project(':libraries:keychain-api-library')
compile project(':OpenPGP-Keychain-API:libraries:keychain-api-library')
compile project(':libraries:HtmlTextView')
compile project(':libraries:StickyListHeaders:library')
compile project(':libraries:AndroidBootstrap')
@ -18,7 +18,7 @@ dependencies {
android {
compileSdkVersion 19
buildToolsVersion "19.0.1"
buildToolsVersion "19.0.3"
defaultConfig {
minSdkVersion 9

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sufficientlysecure.keychain"
android:installLocation="auto"
android:versionCode="23100"
android:versionName="2.3.1">
android:versionCode="23103"
android:versionName="2.3.1 beta3">
<!--
General remarks
@ -22,7 +22,8 @@
Remarks about the ugly android:pathPattern:
- We are matching all files with a specific file ending.
This is done in an ugly way because of Android limitations.
Read http://stackoverflow.com/questions/1733195/android-intent-filter-for-a-particular-file-extension and http://stackoverflow.com/questions/3400072/pathpattern-to-match-file-extension-does-not-work-if-a-period-exists-elsewhere-i/8599921
Read http://stackoverflow.com/questions/1733195/android-intent-filter-for-a-particular-file-extension
and http://stackoverflow.com/questions/3400072/pathpattern-to-match-file-extension-does-not-work-if-a-period-exists-elsewhere-i/8599921
for more information.
- Do _not_ set mimeType for gpg!
Cyanogenmod's file manager will only show Keychain for gpg files if no mimeType is set!
@ -68,30 +69,12 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.SEARCH" /> -->
<!-- </intent-filter> -->
<!-- <meta-data -->
<!-- android:name="android.app.searchable" -->
<!-- android:resource="@xml/searchable_public_keys" /> -->
</activity>
<activity
android:name=".ui.KeyListSecretActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_manage_secret_keys"
android:launchMode="singleTop">
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.SEARCH" /> -->
<!-- </intent-filter> -->
<!-- <meta-data -->
<!-- android:name="android.app.searchable" -->
<!-- android:resource="@xml/searchable_secret_keys" /> -->
</activity>
<activity
android:name=".ui.EditKeyActivity"
@ -121,30 +104,12 @@
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_recipients"
android:launchMode="singleTop">
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.SEARCH" /> -->
<!-- </intent-filter> -->
<!-- <meta-data -->
<!-- android:name="android.app.searchable" -->
<!-- android:resource="@xml/searchable_public_keys" /> -->
</activity>
<activity
android:name=".ui.SelectSecretKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_secret_key"
android:launchMode="singleTop">
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.SEARCH" /> -->
<!-- </intent-filter> -->
<!-- <meta-data -->
<!-- android:name="android.app.searchable" -->
<!-- android:resource="@xml/searchable_secret_keys" /> -->
</activity>
<activity
android:name=".ui.EncryptActivity"
@ -276,7 +241,16 @@
<activity
android:name=".ui.PreferencesActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_preferences" />
android:label="@string/title_preferences" >
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.ui.PREFS_GEN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="org.sufficientlysecure.keychain.ui.PREFS_ADV" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ui.PreferencesKeyServerActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@ -454,10 +428,6 @@
<!--<intent-filter>-->
<!--<action android:name="org.sufficientlysecure.keychain.service.remote.IExtendedApiService" />-->
<!--</intent-filter>-->
<!--<meta-data-->
<!--android:name="api_version"-->
<!--android:value="1" />-->
<!--</service>-->
<!-- TODO: authority! Make this API with content provider uris -->

View File

@ -51,7 +51,7 @@ public final class Constants {
public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour";
public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression";
public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression";
public static final String PASS_PHRASE_CACHE_TTL = "passPhraseCacheTtl";
public static final String PASS_PHRASE_CACHE_TTL = "passphraseCacheTtl";
public static final String LANGUAGE = "language";
public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
public static final String KEY_SERVERS = "keyServers";

View File

@ -30,7 +30,7 @@ public final class Id {
public static final class menu {
public static final class option {
public static final int new_pass_phrase = 0x21070001;
public static final int new_passphrase = 0x21070001;
public static final int create = 0x21070002;
public static final int about = 0x21070003;
public static final int manage_public_keys = 0x21070004;
@ -85,12 +85,12 @@ public final class Id {
}
public static final class dialog {
public static final int pass_phrase = 0x21070001;
public static final int passphrase = 0x21070001;
public static final int encrypting = 0x21070002;
public static final int decrypting = 0x21070003;
public static final int new_pass_phrase = 0x21070004;
public static final int pass_phrases_do_not_match = 0x21070005;
public static final int no_pass_phrase = 0x21070006;
public static final int new_passphrase = 0x21070004;
public static final int passphrases_do_not_match = 0x21070005;
public static final int no_passphrase = 0x21070006;
public static final int saving = 0x21070007;
public static final int delete_key = 0x21070008;
public static final int import_keys = 0x21070009;

View File

@ -18,8 +18,8 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
import android.os.Bundle;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.openpgp.PGPCompressedData;
@ -36,6 +36,7 @@ import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
@ -53,7 +54,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
@ -75,9 +76,10 @@ public class PgpDecryptVerify {
private InputData data;
private OutputStream outStream;
private ProgressDialogUpdater progress;
boolean assumeSymmetric;
String passphrase;
private ProgressDialogUpdater progressDialogUpdater;
private boolean assumeSymmetric;
private String passphrase;
private long enforcedKeyId;
private PgpDecryptVerify(Builder builder) {
// private Constructor can only be called from Builder
@ -85,9 +87,10 @@ public class PgpDecryptVerify {
this.data = builder.data;
this.outStream = builder.outStream;
this.progress = builder.progress;
this.progressDialogUpdater = builder.progressDialogUpdater;
this.assumeSymmetric = builder.assumeSymmetric;
this.passphrase = builder.passphrase;
this.enforcedKeyId = builder.enforcedKeyId;
}
public static class Builder {
@ -97,9 +100,10 @@ public class PgpDecryptVerify {
private OutputStream outStream;
// optional
private ProgressDialogUpdater progress = null;
private ProgressDialogUpdater progressDialogUpdater = null;
private boolean assumeSymmetric = false;
private String passphrase = "";
private long enforcedKeyId = 0;
public Builder(Context context, InputData data, OutputStream outStream) {
this.context = context;
@ -107,8 +111,8 @@ public class PgpDecryptVerify {
this.outStream = outStream;
}
public Builder progress(ProgressDialogUpdater progress) {
this.progress = progress;
public Builder progressDialogUpdater(ProgressDialogUpdater progressDialogUpdater) {
this.progressDialogUpdater = progressDialogUpdater;
return this;
}
@ -122,20 +126,32 @@ public class PgpDecryptVerify {
return this;
}
/**
* Allow this key id alone for decryption.
* This means only ciphertexts encrypted for this private key can be decrypted.
*
* @param enforcedKeyId
* @return
*/
public Builder enforcedKeyId(long enforcedKeyId) {
this.enforcedKeyId = enforcedKeyId;
return this;
}
public PgpDecryptVerify build() {
return new PgpDecryptVerify(this);
}
}
public void updateProgress(int message, int current, int total) {
if (progress != null) {
progress.setProgress(message, current, total);
if (progressDialogUpdater != null) {
progressDialogUpdater.setProgress(message, current, total);
}
}
public void updateProgress(int current, int total) {
if (progress != null) {
progress.setProgress(current, total);
if (progressDialogUpdater != null) {
progressDialogUpdater.setProgress(current, total);
}
}
@ -177,9 +193,8 @@ public class PgpDecryptVerify {
* @throws PGPException
* @throws SignatureException
*/
public Bundle execute()
public PgpDecryptVerifyResult execute()
throws IOException, PgpGeneralException, PGPException, SignatureException {
// automatically works with ascii armor input and binary
InputStream in = PGPUtil.getDecoderStream(data.getInputStream());
if (in instanceof ArmoredInputStream) {
@ -207,9 +222,9 @@ public class PgpDecryptVerify {
* @throws PGPException
* @throws SignatureException
*/
private Bundle decryptVerify(InputStream in)
private PgpDecryptVerifyResult decryptVerify(InputStream in)
throws IOException, PgpGeneralException, PGPException, SignatureException {
Bundle returnData = new Bundle();
PgpDecryptVerifyResult returnData = new PgpDecryptVerifyResult();
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc;
@ -277,9 +292,40 @@ public class PgpDecryptVerify {
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, encData.getKeyID());
if (secretKey != null) {
// secret key exists in database
// allow only a specific key for decryption?
if (enforcedKeyId != 0) {
// TODO: improve this code! get master key directly!
PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, encData.getKeyID());
long masterKeyId = PgpKeyHelper.getMasterKey(secretKeyRing).getKeyID();
Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID());
Log.d(Constants.TAG, "enforcedKeyId: " + enforcedKeyId);
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
if (enforcedKeyId != masterKeyId) {
throw new PgpGeneralException(context.getString(R.string.error_no_secret_key_found));
}
}
pbe = encData;
// if no passphrase was explicitly set try to get it from the cache service
if (passphrase == null) {
// returns "" if key has no passphrase
passphrase = PassphraseCacheService.getCachedPassphrase(context, encData.getKeyID());
// if passphrase was not cached, return here indicating that a passphrase is missing!
if (passphrase == null) {
returnData.setKeyPassphraseNeeded(true);
return returnData;
}
}
break;
}
}
}
@ -289,7 +335,7 @@ public class PgpDecryptVerify {
currentProgress += 5;
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
PGPPrivateKey privateKey = null;
PGPPrivateKey privateKey;
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
@ -317,6 +363,7 @@ public class PgpDecryptVerify {
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
Object dataChunk = plainFact.nextObject();
PGPOnePassSignature signature = null;
OpenPgpSignatureResult signatureResult = null;
PGPPublicKey signatureKey = null;
int signatureIndex = -1;
@ -334,7 +381,7 @@ public class PgpDecryptVerify {
if (dataChunk instanceof PGPOnePassSignatureList) {
updateProgress(R.string.progress_processing_signature, currentProgress, 100);
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true);
signatureResult = new OpenPgpSignatureResult();
PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
for (int i = 0; i < sigList.size(); ++i) {
signature = sigList.get(i);
@ -354,12 +401,12 @@ public class PgpDecryptVerify {
if (signKeyRing != null) {
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
}
returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId);
signatureResult.setUserId(userId);
break;
}
}
returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
signatureResult.setKeyId(signatureKeyId);
if (signature != null) {
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
@ -367,7 +414,7 @@ public class PgpDecryptVerify {
signature.init(contentVerifierBuilderProvider, signatureKey);
} else {
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true);
signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY);
}
dataChunk = plainFact.nextObject();
@ -405,8 +452,7 @@ public class PgpDecryptVerify {
try {
signature.update(buffer, 0, n);
} catch (SignatureException e) {
returnData
.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false);
signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR);
signature = null;
}
}
@ -430,17 +476,20 @@ public class PgpDecryptVerify {
PGPSignature messageSignature = signatureList.get(signatureIndex);
// these are not cleartext signatures!
returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false);
// TODO: what about binary signatures?
signatureResult.setSignatureOnly(false);
//Now check binding signatures
boolean keyBinding_isok = verifyKeyBinding(context, messageSignature, signatureKey);
boolean sig_isok = signature.verify(messageSignature);
boolean validKeyBinding = verifyKeyBinding(context, messageSignature, signatureKey);
boolean validSignature = signature.verify(messageSignature);
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, keyBinding_isok & sig_isok);
// TODO: implement CERTIFIED!
if (validKeyBinding & validSignature) {
signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED);
}
}
}
// TODO: test if this integrity really check works!
if (encryptedData.isIntegrityProtected()) {
updateProgress(R.string.progress_verifying_integrity, 95, 100);
@ -455,9 +504,12 @@ public class PgpDecryptVerify {
} else {
// no integrity check
Log.e(Constants.TAG, "Encrypted data was not integrity protected!");
// TODO: inform user?
}
updateProgress(R.string.progress_done, 100, 100);
returnData.setSignatureResult(signatureResult);
return returnData;
}
@ -474,11 +526,12 @@ public class PgpDecryptVerify {
* @throws PGPException
* @throws SignatureException
*/
private Bundle verifyCleartextSignature(ArmoredInputStream aIn)
private PgpDecryptVerifyResult verifyCleartextSignature(ArmoredInputStream aIn)
throws IOException, PgpGeneralException, PGPException, SignatureException {
Bundle returnData = new Bundle();
PgpDecryptVerifyResult returnData = new PgpDecryptVerifyResult();
OpenPgpSignatureResult signatureResult = new OpenPgpSignatureResult();
// cleartext signatures are never encrypted ;)
returnData.putBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, true);
signatureResult.setSignatureOnly(true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
@ -504,8 +557,6 @@ public class PgpDecryptVerify {
byte[] clearText = out.toByteArray();
outStream.write(clearText);
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE, true);
updateProgress(R.string.progress_processing_signature, 60, 100);
PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
@ -533,15 +584,17 @@ public class PgpDecryptVerify {
if (signKeyRing != null) {
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
}
returnData.putString(KeychainIntentService.RESULT_SIGNATURE_USER_ID, userId);
signatureResult.setUserId(userId);
break;
}
}
returnData.putLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, signatureKeyId);
signatureResult.setKeyId(signatureKeyId);
if (signature == null) {
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, true);
signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY);
returnData.setSignatureResult(signatureResult);
updateProgress(R.string.progress_done, 100, 100);
return returnData;
}
@ -569,12 +622,17 @@ public class PgpDecryptVerify {
} while (lookAhead != -1);
}
boolean sig_isok = signature.verify();
//Now check binding signatures
boolean keyBinding_isok = verifyKeyBinding(context, signature, signatureKey);
boolean validKeyBinding = verifyKeyBinding(context, signature, signatureKey);
boolean validSignature = signature.verify();
returnData.putBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, sig_isok & keyBinding_isok);
if (validSignature & validKeyBinding) {
signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED);
}
// TODO: what about SIGNATURE_SUCCESS_CERTIFIED and SIGNATURE_ERROR????
returnData.setSignatureResult(signatureResult);
updateProgress(R.string.progress_done, 100, 100);
return returnData;
@ -582,34 +640,34 @@ public class PgpDecryptVerify {
private static boolean verifyKeyBinding(Context context, PGPSignature signature, PGPPublicKey signatureKey) {
long signatureKeyId = signature.getKeyID();
boolean keyBinding_isok = false;
String userId = null;
boolean validKeyBinding = false;
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context,
signatureKeyId);
PGPPublicKey mKey = null;
if (signKeyRing != null) {
mKey = PgpKeyHelper.getMasterKey(signKeyRing);
}
if (signature.getKeyID() != mKey.getKeyID()) {
keyBinding_isok = verifyKeyBinding(mKey, signatureKey);
validKeyBinding = verifyKeyBinding(mKey, signatureKey);
} else { //if the key used to make the signature was the master key, no need to check binding sigs
keyBinding_isok = true;
validKeyBinding = true;
}
return keyBinding_isok;
return validKeyBinding;
}
private static boolean verifyKeyBinding(PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
boolean subkeyBinding_isok = false;
boolean tmp_subkeyBinding_isok = false;
boolean primkeyBinding_isok = false;
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
boolean validSubkeyBinding = false;
boolean validTempSubkeyBinding = false;
boolean validPrimaryKeyBinding = false;
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
Iterator<PGPSignature> itr = signingPublicKey.getSignatures();
subkeyBinding_isok = false;
tmp_subkeyBinding_isok = false;
primkeyBinding_isok = false;
while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong?
//gpg has an invalid subkey binding error on key import I think, but doesn't shout
//about keys without subkey signing. Can't get it to import a slightly broken one
@ -619,32 +677,36 @@ public class PgpDecryptVerify {
//check and if ok, check primary key binding.
try {
sig.init(contentVerifierBuilderProvider, masterPublicKey);
tmp_subkeyBinding_isok = sig.verifyCertification(masterPublicKey, signingPublicKey);
validTempSubkeyBinding = sig.verifyCertification(masterPublicKey, signingPublicKey);
} catch (PGPException e) {
continue;
} catch (SignatureException e) {
continue;
}
if (tmp_subkeyBinding_isok)
subkeyBinding_isok = true;
if (tmp_subkeyBinding_isok) {
primkeyBinding_isok = verifyPrimaryBinding(sig.getUnhashedSubPackets(), masterPublicKey, signingPublicKey);
if (primkeyBinding_isok)
if (validTempSubkeyBinding)
validSubkeyBinding = true;
if (validTempSubkeyBinding) {
validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getUnhashedSubPackets(),
masterPublicKey, signingPublicKey);
if (validPrimaryKeyBinding)
break;
primkeyBinding_isok = verifyPrimaryBinding(sig.getHashedSubPackets(), masterPublicKey, signingPublicKey);
if (primkeyBinding_isok)
validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getHashedSubPackets(),
masterPublicKey, signingPublicKey);
if (validPrimaryKeyBinding)
break;
}
}
}
return (subkeyBinding_isok & primkeyBinding_isok);
return (validSubkeyBinding & validPrimaryKeyBinding);
}
private static boolean verifyPrimaryBinding(PGPSignatureSubpacketVector Pkts, PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
boolean primkeyBinding_isok = false;
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector Pkts,
PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
boolean validPrimaryKeyBinding = false;
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
new JcaPGPContentVerifierBuilderProvider()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureList eSigList;
if (Pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) {
@ -660,8 +722,8 @@ public class PgpDecryptVerify {
if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
try {
emSig.init(contentVerifierBuilderProvider, signingPublicKey);
primkeyBinding_isok = emSig.verifyCertification(masterPublicKey, signingPublicKey);
if (primkeyBinding_isok)
validPrimaryKeyBinding = emSig.verifyCertification(masterPublicKey, signingPublicKey);
if (validPrimaryKeyBinding)
break;
} catch (PGPException e) {
continue;
@ -671,7 +733,8 @@ public class PgpDecryptVerify {
}
}
}
return primkeyBinding_isok;
return validPrimaryKeyBinding;
}
/**

View File

@ -0,0 +1,88 @@
/*
* 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.pgp;
import android.os.Parcel;
import android.os.Parcelable;
import org.openintents.openpgp.OpenPgpSignatureResult;
public class PgpDecryptVerifyResult implements Parcelable {
boolean symmetricPassphraseNeeded;
boolean keyPassphraseNeeded;
OpenPgpSignatureResult signatureResult;
public boolean isSymmetricPassphraseNeeded() {
return symmetricPassphraseNeeded;
}
public void setSymmetricPassphraseNeeded(boolean symmetricPassphraseNeeded) {
this.symmetricPassphraseNeeded = symmetricPassphraseNeeded;
}
public boolean isKeyPassphraseNeeded() {
return keyPassphraseNeeded;
}
public void setKeyPassphraseNeeded(boolean keyPassphraseNeeded) {
this.keyPassphraseNeeded = keyPassphraseNeeded;
}
public OpenPgpSignatureResult getSignatureResult() {
return signatureResult;
}
public void setSignatureResult(OpenPgpSignatureResult signatureResult) {
this.signatureResult = signatureResult;
}
public PgpDecryptVerifyResult() {
}
public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) {
this.symmetricPassphraseNeeded = b.symmetricPassphraseNeeded;
this.keyPassphraseNeeded = b.keyPassphraseNeeded;
this.signatureResult = b.signatureResult;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (symmetricPassphraseNeeded ? 1 : 0));
dest.writeByte((byte) (keyPassphraseNeeded ? 1 : 0));
dest.writeParcelable(signatureResult, 0);
}
public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() {
public PgpDecryptVerifyResult createFromParcel(final Parcel source) {
PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult();
vr.symmetricPassphraseNeeded = source.readByte() == 1;
vr.keyPassphraseNeeded = source.readByte() == 1;
vr.signatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
return vr;
}
public PgpDecryptVerifyResult[] newArray(final int size) {
return new PgpDecryptVerifyResult[size];
}
};
}

View File

@ -90,8 +90,8 @@ public class PgpImportExport {
aos.write(keyring.getEncoded());
aos.close();
String armouredKey = bos.toString("UTF-8");
server.add(armouredKey);
String armoredKey = bos.toString("UTF-8");
server.add(armoredKey);
return true;
} catch (IOException e) {

View File

@ -105,7 +105,7 @@ public class PgpKeyOperation {
}
}
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passPhrase,
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
PgpGeneralException, InvalidAlgorithmParameterException {
@ -113,8 +113,8 @@ public class PgpKeyOperation {
throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit));
}
if (passPhrase == null) {
passPhrase = "";
if (passphrase == null) {
passphrase = "";
}
int algorithm;
@ -168,7 +168,7 @@ public class PgpKeyOperation {
// Build key encrypter and decrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passPhrase.toCharArray());
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor);

View File

@ -626,7 +626,7 @@ public class ProviderHelper {
String armoredKey = bos.toString("UTF-8");
Log.d(Constants.TAG, "armouredKey:" + armoredKey);
Log.d(Constants.TAG, "armoredKey:" + armoredKey);
output.add(armoredKey);
} catch (IOException e) {

View File

@ -44,6 +44,7 @@ import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpImportExport;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
@ -176,13 +177,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// decrypt/verify
public static final String RESULT_DECRYPTED_STRING = "decrypted_message";
public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
public static final String RESULT_SIGNATURE = "signature";
public static final String RESULT_SIGNATURE_KEY_ID = "signature_key_id";
public static final String RESULT_SIGNATURE_USER_ID = "signature_user_id";
public static final String RESULT_CLEARTEXT_SIGNATURE_ONLY = "signature_only";
public static final String RESULT_SIGNATURE_SUCCESS = "signature_success";
public static final String RESULT_SIGNATURE_UNKNOWN = "signature_unknown";
public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature";
// import
public static final String RESULT_IMPORT_ADDED = "added";
@ -198,8 +193,16 @@ public class KeychainIntentService extends IntentService implements ProgressDial
Messenger mMessenger;
private boolean mIsCanceled;
public KeychainIntentService() {
super("ApgService");
super("KeychainIntentService");
}
@Override
public void onDestroy() {
super.onDestroy();
this.mIsCanceled = true;
}
/**
@ -476,15 +479,17 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// verifyText and decrypt returning additional resultData values for the
// verification of signatures
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
builder.progress(this);
builder.progressDialogUpdater(this);
builder.assumeSymmetric(assumeSymmetricEncryption)
.passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
resultData = builder.build().execute();
PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
outStream.close();
resultData.putParcelable(RESULT_DECRYPT_VERIFY_RESULT, decryptVerifyResult);
/* Output */
switch (target) {
@ -733,7 +738,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
*/
// 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
// armor blocks
BufferedInputStream bufferedInput = new BufferedInputStream(new ByteArrayInputStream(downloadedKey));
try {
@ -805,6 +810,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
}
private void sendErrorToHandler(Exception e) {
// Service was canceled. Do not send error to handler.
if (this.mIsCanceled)
return;
Log.e(Constants.TAG, "ApgService Exception: ", e);
e.printStackTrace();
@ -814,6 +823,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
}
private void sendMessageToHandler(Integer arg1, Integer arg2, Bundle data) {
// Service was canceled. Do not send message to handler.
if (this.mIsCanceled)
return;
Message msg = Message.obtain();
msg.arg1 = arg1;
if (arg2 != null) {
@ -841,10 +854,10 @@ public class KeychainIntentService extends IntentService implements ProgressDial
}
/**
* Set progress of ProgressDialog by sending message to handler on UI thread
* Set progressDialogUpdater of ProgressDialog by sending message to handler on UI thread
*/
public void setProgress(String message, int progress, int max) {
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
Log.d(Constants.TAG, "Send message by setProgress with progressDialogUpdater=" + progress + ", max="
+ max);
Bundle data = new Bundle();

View File

@ -21,6 +21,8 @@ import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.R;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@ -55,9 +57,15 @@ public class KeychainIntentServiceHandler extends Handler {
}
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, int progressDialogStyle) {
this(activity, progressDialogMessageId, progressDialogStyle, false, null);
}
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId,
int progressDialogStyle, boolean cancelable,
OnCancelListener onCancelListener) {
this.mActivity = activity;
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(progressDialogMessageId,
progressDialogStyle);
progressDialogStyle, cancelable, onCancelListener);
}
public void showProgressDialog(FragmentActivity activity) {

View File

@ -21,21 +21,22 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpConstants;
import org.openintents.openpgp.util.OpenPgpApi;
import org.spongycastle.util.Arrays;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
@ -48,7 +49,7 @@ public class OpenPgpService extends RemoteService {
private static final int PRIVATE_REQUEST_CODE_PASSPHRASE = 551;
private static final int PRIVATE_REQUEST_CODE_USER_IDS = 552;
private static final int PRIVATE_REQUEST_CODE_GET_KEYS = 553;
/**
* Search database for key ids based on emails.
@ -56,7 +57,7 @@ public class OpenPgpService extends RemoteService {
* @param encryptionUserIds
* @return
*/
private Bundle getKeyIdsFromEmails(Bundle params, String[] encryptionUserIds) {
private Intent getKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
// find key ids to given emails in database
ArrayList<Long> keyIds = new ArrayList<Long>();
@ -91,21 +92,20 @@ public class OpenPgpService extends RemoteService {
// allow the user to verify pub key selection
if (missingUserIdsCheck || dublicateUserIdsCheck) {
// build PendingIntent for passphrase input
// build PendingIntent
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds);
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0);
// return PendingIntent to be executed by client
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return result;
}
@ -113,44 +113,43 @@ public class OpenPgpService extends RemoteService {
return null;
}
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
result.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS, keyIdsArray);
Intent result = new Intent();
result.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIdsArray);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
}
private Bundle getPassphraseBundleIntent(Bundle params, long keyId) {
private Intent getPassphraseBundleIntent(Intent data, long keyId) {
// build PendingIntent for passphrase input
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_CACHE_PASSPHRASE);
intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
// pass params through to activity that it can be returned again later to repeat pgp operation
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_PASSPHRASE, intent, 0);
// return PendingIntent to be executed by client
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return result;
}
private Bundle signImpl(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output,
AppSettings appSettings) {
private Intent signImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) {
try {
boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
// get passphrase from cache, if key has "no" passphrase, this returns an empty String
String passphrase;
if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
} else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
return passphraseBundle;
}
@ -174,44 +173,43 @@ public class OpenPgpService extends RemoteService {
os.close();
}
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) {
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
}
private Bundle encryptAndSignImpl(Bundle params, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings,
boolean sign) {
private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings, boolean sign) {
try {
boolean asciiArmor = params.getBoolean(OpenPgpConstants.PARAMS_REQUEST_ASCII_ARMOR, true);
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
long[] keyIds;
if (params.containsKey(OpenPgpConstants.PARAMS_KEY_IDS)) {
keyIds = params.getLongArray(OpenPgpConstants.PARAMS_KEY_IDS);
} else if (params.containsKey(OpenPgpConstants.PARAMS_USER_IDS)) {
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
} else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) {
// get key ids based on given user ids
String[] userIds = params.getStringArray(OpenPgpConstants.PARAMS_USER_IDS);
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
// give params through to activity...
Bundle result = getKeyIdsFromEmails(params, userIds);
Intent result = getKeyIdsFromEmails(data, userIds);
if (result.getInt(OpenPgpConstants.RESULT_CODE, 0) == OpenPgpConstants.RESULT_CODE_SUCCESS) {
keyIds = result.getLongArray(OpenPgpConstants.PARAMS_KEY_IDS);
if (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0) == OpenPgpApi.RESULT_CODE_SUCCESS) {
keyIds = result.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
} else {
// if not success -> result contains a PendingIntent for user interaction
return result;
}
} else {
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, "Missing parameter user_ids or key_ids!"));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
@ -235,15 +233,15 @@ public class OpenPgpService extends RemoteService {
if (sign) {
String passphrase;
if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
} else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
appSettings.getKeyId());
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
return passphraseBundle;
}
@ -263,193 +261,123 @@ public class OpenPgpService extends RemoteService {
os.close();
}
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) {
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
}
private Bundle decryptAndVerifyImpl(Bundle params, ParcelFileDescriptor input,
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) {
try {
// Get Input- and OutputStream from ParcelFileDescriptor
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
OpenPgpSignatureResult sigResult = null;
Intent result = new Intent();
try {
// PGPUtil.getDecoderStream(is)
// TODOs API 2.0:
// implement verify-only!
// fix the mess: http://stackoverflow.com/questions/148130/how-do-i-peek-at-the-first-two-bytes-in-an-inputstream
// should we allow to decrypt everything under every key id or only the one set?
// TODO: instead of trying to get the passphrase before
// pause stream when passphrase is missing and then resume
// TODO: this is not really needed
// checked if it is text with BEGIN and END tags
// String message = new String(inputBytes);
// Log.d(Constants.TAG, "in: " + message);
// boolean signedOnly = false;
// Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(message);
// if (matcher.matches()) {
// Log.d(Constants.TAG, "PGP_MESSAGE matched");
// message = matcher.group(1);
// // replace non breakable spaces
// message = message.replaceAll("\\xa0", " ");
//
// // overwrite inputBytes
// inputBytes = message.getBytes();
// } else {
// matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(message);
// if (matcher.matches()) {
// signedOnly = true;
// Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
// message = matcher.group(1);
// // replace non breakable spaces
// message = message.replaceAll("\\xa0", " ");
//
// // overwrite inputBytes
// inputBytes = message.getBytes();
// } else {
// Log.d(Constants.TAG, "Nothing matched! Binary?");
// }
// }
// END TODO
// Log.d(Constants.TAG, "in: " + new String(inputBytes));
// TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
// app, Fix this?
// String passphrase = null;
// if (!signedOnly) {
// // BEGIN Get key
// // TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it
// // better!
// InputStream inputStream2 = new ByteArrayInputStream(inputBytes);
//
// // TODO: duplicates functions from DecryptActivity!
// long secretKeyId;
// try {
// if (inputStream2.markSupported()) {
// // should probably set this to the max size of two
// // pgpF objects, if it even needs to be anything other
// // than 0.
// inputStream2.mark(200);
// }
// secretKeyId = PgpHelper.getDecryptionKeyId(this, inputStream2);
// if (secretKeyId == Id.key.none) {
// throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
// }
// } catch (NoAsymmetricEncryptionException e) {
// if (inputStream2.markSupported()) {
// inputStream2.reset();
// }
// secretKeyId = Id.key.symmetric;
// if (!PgpDecryptVerify.hasSymmetricEncryption(this, inputStream2)) {
// throw new PgpGeneralException(
// getString(R.string.error_no_known_encryption_found));
// }
// // we do not support symmetric decryption from the API!
// throw new Exception("Symmetric decryption is not supported!");
// }
//
// Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
// NOTE: currently this only gets the passphrase for the key set for this client
String passphrase;
if (params.containsKey(OpenPgpConstants.PARAMS_PASSPHRASE)) {
passphrase = params.getString(OpenPgpConstants.PARAMS_PASSPHRASE);
} else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
Bundle passphraseBundle = getPassphraseBundleIntent(params, appSettings.getKeyId());
return passphraseBundle;
}
// }
// build InputData and write into OutputStream
String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength);
Bundle outputBundle;
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
// if (signedOnly) {
// outputBundle = builder.build().verifyText();
// } else {
builder.assumeSymmetric(false)
builder.assumeSymmetric(false) // no support for symmetric encryption
.enforcedKeyId(appSettings.getKeyId()) // allow only the private key for this app for decryption
.passphrase(passphrase);
// Do we want to do this: instead of trying to get the passphrase before
// pause stream when passphrase is missing and then resume???
// TODO: currently does not support binary signed-only content
PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
// TODO: this also decrypts with other secret keys without passphrase!!!
outputBundle = builder.build().execute();
// }
if (decryptVerifyResult.isKeyPassphraseNeeded()) {
// get PendingIntent for passphrase input, add it to given params and return to client
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
return passphraseBundle;
} else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) {
throw new PgpGeneralException("Decryption of symmetric content not supported by API!");
}
// outputStream.close();
OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
if (signatureResult != null) {
if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY) {
// If signature is unknown we return an _additional_ PendingIntent
// to retrieve the missing key
// TODO!!!
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo");
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
// byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0);
// get signature informations from bundle
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE, false);
if (signature) {
long signatureKeyId = outputBundle
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, 0);
String signatureUserId = outputBundle
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
boolean signatureSuccess = outputBundle
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS, false);
boolean signatureUnknown = outputBundle
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN, false);
boolean signatureOnly = outputBundle
.getBoolean(KeychainIntentService.RESULT_CLEARTEXT_SIGNATURE_ONLY, false);
int signatureStatus = OpenPgpSignatureResult.SIGNATURE_ERROR;
if (signatureSuccess) {
signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED;
} else if (signatureUnknown) {
signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY;
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
}
sigResult = new OpenPgpSignatureResult(signatureStatus, signatureUserId,
signatureOnly, signatureKeyId);
result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
}
} finally {
is.close();
os.close();
}
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_SUCCESS);
result.putParcelable(OpenPgpConstants.RESULT_SIGNATURE, sigResult);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
} catch (Exception e) {
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
}
private Bundle getKeyIdsImpl(Bundle params) {
private Intent getKeyImpl(Intent data) {
try {
long keyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
if (ProviderHelper.getPGPPublicKeyByKeyId(this, keyId) == null) {
Intent result = new Intent();
// If keys are not in db we return an additional PendingIntent
// to retrieve the missing key
// TODO!!!
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo");
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return result;
} else {
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result;
}
} catch (Exception e) {
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
}
private Intent getKeyIdsImpl(Intent data) {
// get key ids based on given user ids
String[] userIds = params.getStringArray(OpenPgpConstants.PARAMS_USER_IDS);
Bundle result = getKeyIdsFromEmails(params, userIds);
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
Intent result = getKeyIdsFromEmails(data, userIds);
return result;
}
@ -459,30 +387,30 @@ public class OpenPgpService extends RemoteService {
* - has supported API version
* - is allowed to call the service (access has been granted)
*
* @param params
* @param data
* @return null if everything is okay, or a Bundle with an error/PendingIntent
*/
private Bundle checkRequirements(Bundle params) {
private Intent checkRequirements(Intent data) {
// params Bundle is required!
if (params == null) {
Bundle result = new Bundle();
if (data == null) {
Intent result = new Intent();
OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!");
result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error);
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
// version code is required and needs to correspond to version code of service!
if (params.getInt(OpenPgpConstants.PARAMS_API_VERSION) != OpenPgpConstants.API_VERSION) {
Bundle result = new Bundle();
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) {
Intent result = new Intent();
OpenPgpError error = new OpenPgpError(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!");
result.putParcelable(OpenPgpConstants.RESULT_ERRORS, error);
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
// check if caller is allowed to access openpgp keychain
Bundle result = isAllowed(params);
Intent result = isAllowed(data);
if (result != null) {
return result;
}
@ -494,61 +422,30 @@ public class OpenPgpService extends RemoteService {
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
@Override
public Bundle sign(Bundle params, final ParcelFileDescriptor input, final ParcelFileDescriptor output) {
public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
Intent errorResult = checkRequirements(data);
if (errorResult != null) {
return errorResult;
}
final AppSettings appSettings = getAppSettings();
Bundle errorResult = checkRequirements(params);
if (errorResult != null) {
return errorResult;
String action = data.getAction();
if (OpenPgpApi.ACTION_SIGN.equals(action)) {
return signImpl(data, input, output, appSettings);
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, appSettings, false);
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, appSettings, true);
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
return decryptAndVerifyImpl(data, input, output, appSettings);
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
return getKeyImpl(data);
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
return getKeyIdsImpl(data);
} else {
return null;
}
return signImpl(params, input, output, appSettings);
}
@Override
public Bundle encrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
final AppSettings appSettings = getAppSettings();
Bundle errorResult = checkRequirements(params);
if (errorResult != null) {
return errorResult;
}
return encryptAndSignImpl(params, input, output, appSettings, false);
}
@Override
public Bundle signAndEncrypt(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
final AppSettings appSettings = getAppSettings();
Bundle errorResult = checkRequirements(params);
if (errorResult != null) {
return errorResult;
}
return encryptAndSignImpl(params, input, output, appSettings, true);
}
@Override
public Bundle decryptAndVerify(Bundle params, ParcelFileDescriptor input, ParcelFileDescriptor output) {
final AppSettings appSettings = getAppSettings();
Bundle errorResult = checkRequirements(params);
if (errorResult != null) {
return errorResult;
}
return decryptAndVerifyImpl(params, input, output, appSettings);
}
@Override
public Bundle getKeyIds(Bundle params) {
Bundle errorResult = checkRequirements(params);
if (errorResult != null) {
return errorResult;
}
return getKeyIdsImpl(params);
}
};

View File

@ -21,7 +21,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpConstants;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
@ -38,10 +38,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
/**
* Abstract service class for remote APIs that handle app registration and user input.
@ -57,7 +53,7 @@ public abstract class RemoteService extends Service {
return mContext;
}
protected Bundle isAllowed(Bundle params) {
protected Intent isAllowed(Intent data) {
try {
if (isCallerAllowed(false)) {
@ -74,9 +70,9 @@ public abstract class RemoteService extends Service {
} catch (NameNotFoundException e) {
Log.e(Constants.TAG, "Should not happen, returning!", e);
// return error
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_ERROR);
result.putParcelable(OpenPgpConstants.RESULT_ERRORS,
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
return result;
}
@ -86,14 +82,14 @@ public abstract class RemoteService extends Service {
intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_REGISTER, intent, 0);
// return PendingIntent to be executed by client
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
return result;
}
@ -103,14 +99,14 @@ public abstract class RemoteService extends Service {
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, getString(R.string.api_error_wrong_signature));
intent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), PRIVATE_REQUEST_CODE_ERROR, intent, 0);
// return PendingIntent to be executed by client
Bundle result = new Bundle();
result.putInt(OpenPgpConstants.RESULT_CODE, OpenPgpConstants.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putParcelable(OpenPgpConstants.RESULT_INTENT, pi);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
return result;
}

View File

@ -25,7 +25,7 @@ import android.os.Messenger;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import org.openintents.openpgp.util.OpenPgpConstants;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.htmltextview.HtmlTextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
@ -51,6 +51,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
public static final String EXTRA_MESSENGER = "messenger";
public static final String EXTRA_DATA = "data";
// passphrase action
public static final String EXTRA_SECRET_KEY_ID = "secret_key_id";
// register action
@ -100,12 +102,9 @@ public class RemoteServiceActivity extends ActionBarActivity {
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
mSettingsFragment.getAppSettings());
// give params through for new service call
Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
Intent finishIntent = new Intent();
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, oldParams);
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
// give data through for new service call
Intent resultData = extras.getParcelable(EXTRA_DATA);
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish();
}
}
@ -128,9 +127,9 @@ public class RemoteServiceActivity extends ActionBarActivity {
mSettingsFragment.setAppSettings(settings);
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
Bundle oldParams = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
Intent resultData = extras.getParcelable(EXTRA_DATA);
showPassphraseDialog(oldParams, secretKeyId);
showPassphraseDialog(resultData, secretKeyId);
} else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
ArrayList<String> missingUserIds = intent
@ -167,13 +166,11 @@ public class RemoteServiceActivity extends ActionBarActivity {
@Override
public void onClick(View v) {
// add key ids to params Bundle for new request
Bundle params = extras.getBundle(OpenPgpConstants.PI_RESULT_PARAMS);
params.putLongArray(OpenPgpConstants.PARAMS_KEY_IDS,
Intent resultData = extras.getParcelable(EXTRA_DATA);
resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS,
mSelectFragment.getSelectedMasterKeyIds());
Intent finishIntent = new Intent();
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish();
}
}, R.string.btn_do_not_save, new View.OnClickListener() {
@ -222,7 +219,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
@Override
public void onClick(View v) {
RemoteServiceActivity.this.setResult(RESULT_OK);
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
RemoteServiceActivity.this.finish();
}
});
@ -244,16 +241,14 @@ public class RemoteServiceActivity extends ActionBarActivity {
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
* for a symmetric passphrase
*/
private void showPassphraseDialog(final Bundle params, long secretKeyId) {
private void showPassphraseDialog(final Intent data, 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) {
// return given params again, for calling the service method again
Intent finishIntent = new Intent();
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
RemoteServiceActivity.this.setResult(RESULT_OK, finishIntent);
RemoteServiceActivity.this.setResult(RESULT_OK, data);
} else {
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
}
@ -273,9 +268,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
} catch (PgpGeneralException e) {
Log.d(Constants.TAG, "No passphrase for this secret key, do pgp operation directly!");
// return given params again, for calling the service method again
Intent finishIntent = new Intent();
finishIntent.putExtra(OpenPgpConstants.PI_RESULT_PARAMS, params);
setResult(RESULT_OK, finishIntent);
setResult(RESULT_OK, data);
finish();
}
}

View File

@ -25,6 +25,7 @@ import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.regex.Matcher;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
@ -32,6 +33,7 @@ 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.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
@ -66,6 +68,7 @@ import android.widget.Toast;
import android.widget.ViewFlipper;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
@SuppressLint("NewApi")
public class DecryptActivity extends DrawerActivity {
@ -233,7 +236,7 @@ public class DecryptActivity extends DrawerActivity {
if (matcher.matches()) {
data = matcher.group(1);
mMessage.setText(data);
Toast.makeText(this, R.string.using_clipboard_content, Toast.LENGTH_SHORT)
AppMsg.makeText(this, R.string.using_clipboard_content, AppMsg.STYLE_INFO)
.show();
}
}
@ -420,17 +423,17 @@ public class DecryptActivity extends DrawerActivity {
}
if (mInputFilename.equals("")) {
Toast.makeText(this, R.string.no_file_selected, Toast.LENGTH_SHORT).show();
AppMsg.makeText(this, R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
return;
}
if (mInputFilename.startsWith("file")) {
File file = new File(mInputFilename);
if (!file.exists() || !file.isFile()) {
Toast.makeText(
AppMsg.makeText(
this,
getString(R.string.error_message,
getString(R.string.error_file_not_found)), Toast.LENGTH_SHORT)
getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
.show();
return;
}
@ -510,14 +513,14 @@ public class DecryptActivity extends DrawerActivity {
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();
AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
AppMsg.STYLE_ALERT).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();
AppMsg.makeText(this, getString(R.string.error_external_storage_not_ready),
AppMsg.STYLE_ALERT).show();
return;
}
@ -525,8 +528,8 @@ public class DecryptActivity extends DrawerActivity {
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();
AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
AppMsg.STYLE_ALERT).show();
}
} else {
inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes());
@ -556,8 +559,8 @@ public class DecryptActivity extends DrawerActivity {
mAssumeSymmetricEncryption = true;
}
} catch (Exception e) {
Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
Toast.LENGTH_SHORT).show();
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
AppMsg.STYLE_ALERT).show();
}
}
@ -655,8 +658,8 @@ public class DecryptActivity extends DrawerActivity {
mSignatureKeyId = 0;
mSignatureLayout.setVisibility(View.GONE);
Toast.makeText(DecryptActivity.this, R.string.decryption_successful,
Toast.LENGTH_SHORT).show();
AppMsg.makeText(DecryptActivity.this, R.string.decryption_successful,
AppMsg.STYLE_INFO).show();
if (mReturnResult) {
Intent intent = new Intent();
intent.putExtras(returnData);
@ -689,11 +692,15 @@ public class DecryptActivity extends DrawerActivity {
}
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE)) {
String userId = returnData
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
mSignatureKeyId = returnData
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
PgpDecryptVerifyResult decryptVerifyResult =
returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT);
OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
if (signatureResult != null) {
String userId = signatureResult.getUserId();
mSignatureKeyId = signatureResult.getKeyId();
mUserIdRest.setText("id: "
+ PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId));
if (userId == null) {
@ -706,19 +713,32 @@ public class DecryptActivity extends DrawerActivity {
}
mUserId.setText(userId);
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) {
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
mLookupKey.setVisibility(View.GONE);
} else if (returnData
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
mLookupKey.setVisibility(View.VISIBLE);
Toast.makeText(DecryptActivity.this,
R.string.unknown_signature,
Toast.LENGTH_LONG).show();
} else {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
mLookupKey.setVisibility(View.GONE);
switch (signatureResult.getStatus()) {
case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: {
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
mLookupKey.setVisibility(View.GONE);
break;
}
// TODO!
// case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: {
// break;
// }
case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY: {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
mLookupKey.setVisibility(View.VISIBLE);
AppMsg.makeText(DecryptActivity.this,
R.string.unknown_signature,
AppMsg.STYLE_ALERT).show();
break;
}
default: {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
mLookupKey.setVisibility(View.GONE);
break;
}
}
mSignatureLayout.setVisibility(View.VISIBLE);
}

View File

@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity;
import org.sufficientlysecure.keychain.util.Log;
import android.app.Activity;
import android.content.Context;
@ -52,6 +53,7 @@ public class DrawerActivity extends ActionBarActivity {
private static Class[] mItemsClass = new Class[] { KeyListPublicActivity.class,
EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class,
KeyListSecretActivity.class, RegisteredAppsListActivity.class };
private Class mSelectedItem;
private static final int MENU_ID_PREFERENCE = 222;
private static final int MENU_ID_HELP = 223;
@ -94,6 +96,17 @@ public class DrawerActivity extends ActionBarActivity {
getSupportActionBar().setTitle(mTitle);
// creates call to onPrepareOptionsMenu()
supportInvalidateOptionsMenu();
// call intent activity if selected
if(mSelectedItem != null) {
finish();
overridePendingTransition(0, 0);
Intent intent = new Intent(DrawerActivity.this, mSelectedItem);
startActivity(intent);
// disable animation of activity start
overridePendingTransition(0, 0);
}
}
public void onDrawerOpened(View drawerView) {
@ -182,14 +195,8 @@ public class DrawerActivity extends ActionBarActivity {
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);
// set selected class
mSelectedItem = mItemsClass[position];
}
/**

View File

@ -49,6 +49,7 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import android.app.AlertDialog;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
@ -97,7 +98,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
private SectionView mUserIdsView;
private SectionView mKeysView;
private String mCurrentPassPhrase = null;
private String mCurrentPassphrase = null;
private String mNewPassPhrase = null;
private String mSavedNewPassPhrase = null;
private boolean mIsPassPhraseSet;
@ -105,7 +106,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
private boolean mIsBrandNewKeyring = false;
private MenuItem mSaveButton;
private BootstrapButton mChangePassPhrase;
private BootstrapButton mChangePassphrase;
private CheckBox mNoPassphrase;
@ -174,7 +175,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
private void handleActionCreateKey(Intent intent) {
Bundle extras = intent.getExtras();
mCurrentPassPhrase = "";
mCurrentPassphrase = "";
mIsBrandNewKeyring = true;
if (extras != null) {
@ -190,7 +191,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
if (noPassphrase) {
// check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true);
mChangePassPhrase.setVisibility(View.GONE);
mChangePassphrase.setVisibility(View.GONE);
}
}
@ -200,19 +201,31 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
if (generateDefaultKeys) {
// Send all information needed to service generate keys in other thread
Intent serviceIntent = new Intent(this, KeychainIntentService.class);
final 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);
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) {
this, R.string.progress_generating, ProgressDialog.STYLE_SPINNER, true,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
// Stop key generation on cancel
stopService(serviceIntent);
EditKeyActivity.this.setResult(Activity.RESULT_CANCELED);
EditKeyActivity.this.finish();
}
}) {
@Override
public void handleMessage(Message message) {
// handle messages by standard ApgHandler first
super.handleMessage(message);
@ -286,7 +299,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
@Override
public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
mCurrentPassPhrase = PassphraseCacheService.getCachedPassphrase(
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
EditKeyActivity.this, masterKeyId);
finallySaveClicked();
}
@ -393,14 +406,14 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
}
}
mCurrentPassPhrase = "";
mCurrentPassphrase = "";
buildLayout(false);
mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
if (!mIsPassPhraseSet) {
// check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true);
mChangePassPhrase.setVisibility(View.GONE);
mChangePassphrase.setVisibility(View.GONE);
}
}
@ -431,7 +444,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// set title based on isPassphraseSet()
int title;
if (isPassphraseSet()) {
title = R.string.title_change_pass_phrase;
title = R.string.title_change_passphrase;
} else {
title = R.string.title_set_passphrase;
}
@ -451,7 +464,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
setContentView(R.layout.edit_key_activity);
// find views
mChangePassPhrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_pass_phrase);
mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase);
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
// Build layout based on given userIds and keys
@ -473,7 +486,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
updatePassPhraseButtonText();
mChangePassPhrase.setOnClickListener(new OnClickListener() {
mChangePassphrase.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
showSetPassphraseDialog();
}
@ -488,10 +501,10 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// remove passphrase
mSavedNewPassPhrase = mNewPassPhrase;
mNewPassPhrase = "";
mChangePassPhrase.setVisibility(View.GONE);
mChangePassphrase.setVisibility(View.GONE);
} else {
mNewPassPhrase = mSavedNewPassPhrase;
mChangePassPhrase.setVisibility(View.VISIBLE);
mChangePassphrase.setVisibility(View.VISIBLE);
}
somethingChanged();
}
@ -545,7 +558,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
if (passphrase == null) {
showPassphraseDialog(masterKeyId);
} else {
mCurrentPassPhrase = passphrase;
mCurrentPassphrase = passphrase;
finallySaveClicked();
}
} catch (PgpGeneralException e) {
@ -581,13 +594,14 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
saveParams.keysUsages = getKeysUsages(mKeysView);
saveParams.newPassPhrase = mNewPassPhrase;
saveParams.oldPassPhrase = mCurrentPassPhrase;
saveParams.oldPassPhrase = mCurrentPassphrase;
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
saveParams.keys = getKeys(mKeysView);
// fill values for this action
Bundle data = new Bundle();
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, masterCanSign);
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
@ -766,7 +780,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
}
private void updatePassPhraseButtonText() {
mChangePassPhrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
: getString(R.string.btn_set_passphrase));
}

View File

@ -63,6 +63,7 @@ import android.widget.Toast;
import android.widget.ViewFlipper;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
public class EncryptActivity extends DrawerActivity {
@ -100,8 +101,8 @@ public class EncryptActivity extends DrawerActivity {
private int mEncryptTarget;
private EditText mPassPhrase = null;
private EditText mPassPhraseAgain = null;
private EditText mPassphrase = null;
private EditText mPassphraseAgain = null;
private CheckBox mAsciiArmor = null;
private Spinner mFileCompression = null;
@ -415,17 +416,17 @@ public class EncryptActivity extends DrawerActivity {
mOutputFilename = guessOutputFilename(mInputFilename);
if (mInputFilename.equals("")) {
Toast.makeText(this, R.string.no_file_selected, Toast.LENGTH_SHORT).show();
AppMsg.makeText(this, R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
return;
}
if (!mInputFilename.startsWith("content")) {
File file = new File(mInputFilename);
if (!file.exists() || !file.isFile()) {
Toast.makeText(
AppMsg.makeText(
this,
getString(R.string.error_message,
getString(R.string.error_file_not_found)), Toast.LENGTH_SHORT)
getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
.show();
return;
}
@ -435,16 +436,16 @@ public class EncryptActivity extends DrawerActivity {
// symmetric encryption
if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
boolean gotPassPhrase = false;
String passPhrase = mPassPhrase.getText().toString();
String passPhraseAgain = mPassPhraseAgain.getText().toString();
if (!passPhrase.equals(passPhraseAgain)) {
Toast.makeText(this, R.string.passphrases_do_not_match, Toast.LENGTH_SHORT).show();
String passphrase = mPassphrase.getText().toString();
String passphraseAgain = mPassphraseAgain.getText().toString();
if (!passphrase.equals(passphraseAgain)) {
AppMsg.makeText(this, R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show();
return;
}
gotPassPhrase = (passPhrase.length() != 0);
gotPassPhrase = (passphrase.length() != 0);
if (!gotPassPhrase) {
Toast.makeText(this, R.string.passphrase_must_not_be_empty, Toast.LENGTH_SHORT)
AppMsg.makeText(this, R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT)
.show();
return;
}
@ -452,13 +453,13 @@ public class EncryptActivity extends DrawerActivity {
boolean encryptIt = (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0);
// for now require at least one form of encryption for files
if (!encryptIt && mEncryptTarget == Id.target.file) {
Toast.makeText(this, R.string.select_encryption_key, Toast.LENGTH_SHORT).show();
AppMsg.makeText(this, R.string.select_encryption_key, AppMsg.STYLE_ALERT).show();
return;
}
if (!encryptIt && mSecretKeyId == 0) {
Toast.makeText(this, R.string.select_encryption_or_signature_key,
Toast.LENGTH_SHORT).show();
AppMsg.makeText(this, R.string.select_encryption_or_signature_key,
AppMsg.STYLE_ALERT).show();
return;
}
@ -549,11 +550,11 @@ public class EncryptActivity extends DrawerActivity {
if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
Log.d(Constants.TAG, "Symmetric encryption enabled!");
String passPhrase = mPassPhrase.getText().toString();
if (passPhrase.length() == 0) {
passPhrase = null;
String passphrase = mPassphrase.getText().toString();
if (passphrase.length() == 0) {
passphrase = null;
}
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passPhrase);
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase);
} else {
mSecretKeyIdToPass = mSecretKeyId;
encryptionKeyIds = mEncryptionKeyIds;
@ -618,8 +619,8 @@ public class EncryptActivity extends DrawerActivity {
output = data.getString(KeychainIntentService.RESULT_ENCRYPTED_STRING);
Log.d(Constants.TAG, "output: " + output);
ClipboardReflection.copyToClipboard(EncryptActivity.this, output);
Toast.makeText(EncryptActivity.this,
R.string.encryption_to_clipboard_successful, Toast.LENGTH_SHORT)
AppMsg.makeText(EncryptActivity.this,
R.string.encryption_to_clipboard_successful, AppMsg.STYLE_INFO)
.show();
break;
@ -640,8 +641,8 @@ public class EncryptActivity extends DrawerActivity {
break;
case Id.target.file:
Toast.makeText(EncryptActivity.this, R.string.encryption_successful,
Toast.LENGTH_SHORT).show();
AppMsg.makeText(EncryptActivity.this, R.string.encryption_successful,
AppMsg.STYLE_INFO).show();
if (mDeleteAfter.isChecked()) {
// Create and show dialog to delete original file
@ -765,8 +766,8 @@ public class EncryptActivity extends DrawerActivity {
mMainUserId = (TextView) findViewById(R.id.mainUserId);
mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
mPassPhrase = (EditText) findViewById(R.id.passPhrase);
mPassPhraseAgain = (EditText) findViewById(R.id.passPhraseAgain);
mPassphrase = (EditText) findViewById(R.id.passphrase);
mPassphraseAgain = (EditText) findViewById(R.id.passphraseAgain);
// 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 File

@ -17,22 +17,9 @@
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.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
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.net.Uri;
import android.nfc.NdefMessage;
@ -51,6 +38,18 @@ import android.widget.ArrayAdapter;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
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.BadImportKeyDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.Locale;
public class ImportKeysActivity extends DrawerActivity implements ActionBar.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
@ -236,10 +235,10 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
* inside your Activity."
* <p/>
* from http://stackoverflow.com/questions/10983396/fragment-oncreateview-and-onactivitycreated-called-twice/14295474#14295474
*
* <p/>
* In our case, if we start ImportKeysActivity with parameters to directly search using a fingerprint,
* the fragment would be loaded twice resulting in the query being empty after the second load.
*
* <p/>
* Our solution:
* To prevent that a fragment will be loaded again even if it was already loaded loadNavFragment
* checks against mCurrentNavPostition.
@ -395,23 +394,8 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO)
.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();
BadImportKeyDialogFragment badImportKeyDialogFragment = BadImportKeyDialogFragment.newInstance(bad);
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
}
}
}

View File

@ -27,11 +27,13 @@ import java.util.List;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
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.ui.adapter.ImportKeysListServerLoader;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.KeyServer;
import org.sufficientlysecure.keychain.util.Log;
import android.app.Activity;
@ -42,10 +44,11 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import com.devspark.appmsg.AppMsg;
public class ImportKeysListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<List<ImportKeysListEntry>> {
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
private static final String ARG_DATA_URI = "uri";
private static final String ARG_BYTES = "bytes";
private static final String ARG_SERVER_QUERY = "query";
@ -181,11 +184,10 @@ public class ImportKeysListFragment extends ListFragment implements
}
@Override
public Loader<List<ImportKeysListEntry>> onCreateLoader(int id, Bundle args) {
public Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_BYTES: {
InputData inputData = getInputData(mKeyBytes, mDataUri);
return new ImportKeysListLoader(mActivity, inputData);
}
case LOADER_ID_SERVER_QUERY: {
@ -198,15 +200,15 @@ public class ImportKeysListFragment extends ListFragment implements
}
@Override
public void onLoadFinished(Loader<List<ImportKeysListEntry>> loader,
List<ImportKeysListEntry> data) {
public void onLoadFinished(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader,
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
Log.d(Constants.TAG, "data: " + data);
Log.d(Constants.TAG, "data: " + data.getResult());
// swap in the real data!
mAdapter.setData(data);
mAdapter.setData(data.getResult());
mAdapter.notifyDataSetChanged();
setListAdapter(mAdapter);
@ -222,11 +224,25 @@ public class ImportKeysListFragment extends ListFragment implements
break;
case LOADER_ID_SERVER_QUERY:
Toast.makeText(
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
mAdapter.getCount(), mAdapter.getCount()),
Toast.LENGTH_SHORT
).show();
Exception error = data.getError();
if(error == null){
AppMsg.makeText(
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
mAdapter.getCount(), mAdapter.getCount()),
AppMsg.STYLE_INFO
).show();
} else if(error instanceof KeyServer.InsufficientQuery){
AppMsg.makeText(getActivity(), R.string.error_keyserver_insufficient_query,
AppMsg.STYLE_ALERT).show();
}else if(error instanceof KeyServer.QueryException){
AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
AppMsg.STYLE_ALERT).show();
}else if(error instanceof KeyServer.TooManyResponses){
AppMsg.makeText(getActivity(), R.string.error_keyserver_too_many_responses,
AppMsg.STYLE_ALERT).show();
}
break;
default:
@ -235,7 +251,7 @@ public class ImportKeysListFragment extends ListFragment implements
}
@Override
public void onLoaderReset(Loader<List<ImportKeysListEntry>> loader) {
public void onLoaderReset(Loader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> loader) {
switch (loader.getId()) {
case LOADER_ID_BYTES:
// Clear the data in the adapter.

View File

@ -33,6 +33,7 @@ import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
@ -45,9 +46,13 @@ 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.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
@ -63,23 +68,25 @@ 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,
public class KeyListPublicFragment extends Fragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor> {
private KeyListPublicAdapter mAdapter;
private StickyListHeadersListView mStickyList;
private String mCurQuery;
private SearchView mSearchView;
// empty list 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);
setHasOptionsMenu(true);
mButtonEmptyCreate = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_create);
mButtonEmptyCreate.setOnClickListener(new OnClickListener() {
@ -137,8 +144,6 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
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();
@ -172,13 +177,20 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
showDeleteKeyDialog(mode, ids);
break;
}
case R.id.menu_key_list_public_multi_select_all: {
//Select all
int localCount = mStickyList.getCount();
for(int k = 0; k < localCount; k++) {
mStickyList.setItemChecked(k, true);
}
break;
}
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
count = 0;
mAdapter.clearSelection();
}
@ -186,13 +198,11 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
if (checked) {
count++;
mAdapter.setNewSelection(position, checked);
} else {
count--;
mAdapter.removeSelection(position);
}
int count = mAdapter.getCurrentCheckedPosition().size();
String keysSelected = getResources().getQuantityString(
R.plurals.key_list_selected_keys, count, count);
mode.setTitle(keysSelected);
@ -231,10 +241,15 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
// 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();
String where = null;
String whereArgs[] = null;
if(mCurQuery != null){
where = KeychainContract.UserIds.USER_ID + " LIKE ?";
whereArgs = new String[]{mCurQuery+"%"};
}
// 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);
return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, SORT_ORDER);
}
@Override
@ -276,7 +291,8 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
viewIntent.setData(KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(id)));
startActivity(viewIntent);
}
@TargetApi(11)
public void encrypt(ActionMode mode, long[] keyRingRowIds) {
// get master key ids from row ids
long[] keyRingIds = new long[keyRingRowIds.length];
@ -298,6 +314,7 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
*
* @param keyRingRowIds
*/
@TargetApi(11)
public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) {
// Message is received after key is deleted
Handler returnHandler = new Handler() {
@ -332,4 +349,34 @@ public class KeyListPublicFragment extends Fragment implements AdapterView.OnIte
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
}
}
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
// Get the searchview
MenuItem searchItem = menu.findItem(R.id.menu_key_list_public_search);
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
// Execute this when searching
mSearchView.setOnQueryTextListener(this);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onQueryTextSubmit(String s) {
return true;
}
@Override
public boolean onQueryTextChange(String s) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
String newQuery = !TextUtils.isEmpty(s) ? s : null;
mCurQuery = newQuery;
getLoaderManager().restartLoader(0, null, this);
return true;
}
}

View File

@ -29,6 +29,7 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyListSecretAdapter;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
@ -81,8 +82,6 @@ public class KeyListSecretFragment extends ListFragment implements
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();
@ -112,13 +111,20 @@ public class KeyListSecretFragment extends ListFragment implements
showDeleteKeyDialog(mode, ids);
break;
}
case R.id.menu_key_list_public_multi_select_all: {
//Select all
int localCount = getListView().getCount();
for(int k = 0; k < localCount; k++) {
getListView().setItemChecked(k, true);
}
break;
}
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
count = 0;
mAdapter.clearSelection();
}
@ -126,13 +132,12 @@ public class KeyListSecretFragment extends ListFragment implements
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
if (checked) {
count++;
mAdapter.setNewSelection(position, checked);
} else {
count--;
mAdapter.removeSelection(position);
}
int count = getListView().getCheckedItemCount();
String keysSelected = getResources().getQuantityString(
R.plurals.key_list_selected_keys, count, count);
mode.setTitle(keysSelected);
@ -209,6 +214,7 @@ public class KeyListSecretFragment extends ListFragment implements
*
* @param keyRingRowIds
*/
@TargetApi(11)
public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) {
// Message is received after key is deleted
Handler returnHandler = new Handler() {

View File

@ -24,24 +24,28 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.support.v7.app.ActionBarActivity;
import java.util.List;
@SuppressLint("NewApi")
public class PreferencesActivity extends PreferenceActivity {
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;
public final static String ACTION_PREFS_GEN = "org.sufficientlysecure.keychain.ui.PREFS_GEN";
public final static String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
private PreferenceScreen mKeyServerPreference = null;
private Preferences mPreferences;
private static Preferences mPreferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -53,22 +57,219 @@ public class PreferencesActivity extends PreferenceActivity {
// actionBar.setDisplayHomeAsUpEnabled(false);
// actionBar.setHomeButtonEnabled(false);
addPreferencesFromResource(R.xml.preferences);
//addPreferencesFromResource(R.xml.preferences);
String action = getIntent().getAction();
mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL);
mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl());
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry());
mPassPhraseCacheTtl
if (action != null && action.equals(ACTION_PREFS_GEN)) {
addPreferencesFromResource(R.xml.gen_preferences);
initializePassPassPhraceCacheTtl(
(IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL));
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;
}
});
} else if (action != null && action.equals(ACTION_PREFS_ADV)) {
addPreferencesFromResource(R.xml.adv_preferences);
initializeEncryptionAlgorithm(
(IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM));
int[] valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip,
Id.choice.compression.zlib, Id.choice.compression.bzip2, };
String[] 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) + ")", };
String[] values = new String[valueIds.length];
for (int i = 0; i < values.length; ++i) {
values[i] = "" + valueIds[i];
}
initializeHashAlgorithm(
(IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM),
valueIds, entries, values);
initializeMessageCompression(
(IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION),
valueIds, entries, values);
initializeFileCompression(
(IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION),
entries, values);
initializeAsciiArmour((CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR));
initializeForceV3Signatures((CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES));
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
addPreferencesFromResource(R.xml.preference_headers_legacy);
}
}
@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;
}
}
}
/* Called only on Honeycomb and later */
@Override
public void onBuildHeaders(List<Header> target) {
super.onBuildHeaders(target);
loadHeadersFromResource(R.xml.preference_headers, target);
}
/** This fragment shows the general preferences in android 3.0+ */
public static class GeneralPrefsFragment extends PreferenceFragment {
private PreferenceScreen mKeyServerPreference = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.gen_preferences);
initializePassPassPhraceCacheTtl(
(IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL));
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(getActivity(),
PreferencesKeyServerActivity.class);
intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
mPreferences.getKeyServers());
startActivityForResult(intent, Id.request.key_server_preference);
return false;
}
});
}
@Override
public 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;
}
}
}
}
/** This fragment shows the advanced preferences in android 3.0+ */
public static class AdvancedPrefsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.adv_preferences);
initializeEncryptionAlgorithm(
(IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM));
int[] valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip,
Id.choice.compression.zlib, Id.choice.compression.bzip2, };
String[] 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) + ")", };
String[] values = new String[valueIds.length];
for (int i = 0; i < values.length; ++i) {
values[i] = "" + valueIds[i];
}
initializeHashAlgorithm(
(IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM),
valueIds, entries, values);
initializeMessageCompression(
(IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION),
valueIds, entries, values);
initializeFileCompression(
(IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION),
entries, values);
initializeAsciiArmour((CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR));
initializeForceV3Signatures((CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES));
}
}
protected boolean isValidFragment (String fragmentName) {
return AdvancedPrefsFragment.class.getName().equals(fragmentName)
|| GeneralPrefsFragment.class.getName().equals(fragmentName)
|| super.isValidFragment(fragmentName);
}
private static void initializePassPassPhraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) {
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());
mPassphraseCacheTtl.setValue(newValue.toString());
mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
return false;
}
});
}
mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM);
private static void initializeEncryptionAlgorithm(final IntegerListPreference mEncryptionAlgorithm) {
int valueIds[] = { PGPEncryptedData.AES_128, PGPEncryptedData.AES_192,
PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH,
PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES,
@ -93,8 +294,10 @@ public class PreferencesActivity extends PreferenceActivity {
return false;
}
});
}
mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM);
private static void initializeHashAlgorithm
(final IntegerListPreference mHashAlgorithm, int[] valueIds, String[] entries, String[] values) {
valueIds = new int[] { HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160,
HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256,
HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, };
@ -116,19 +319,10 @@ public class PreferencesActivity extends PreferenceActivity {
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];
}
private static void initializeMessageCompression
(final IntegerListPreference mMessageCompression, int[] valueIds, String[] entries, String[] values) {
mMessageCompression.setEntries(entries);
mMessageCompression.setEntryValues(values);
mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression());
@ -143,8 +337,10 @@ public class PreferencesActivity extends PreferenceActivity {
return false;
}
});
}
mFileCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION);
private static void initializeFileCompression
(final IntegerListPreference mFileCompression, String[] entries, String[] values) {
mFileCompression.setEntries(entries);
mFileCompression.setEntryValues(values);
mFileCompression.setValue("" + mPreferences.getDefaultFileCompression());
@ -157,8 +353,9 @@ public class PreferencesActivity extends PreferenceActivity {
return false;
}
});
}
mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR);
private static void initializeAsciiArmour(final CheckBoxPreference mAsciiArmour) {
mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
@ -167,8 +364,9 @@ public class PreferencesActivity extends PreferenceActivity {
return false;
}
});
}
mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES);
private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) {
mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures());
mForceV3Signatures
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@ -178,43 +376,5 @@ public class PreferencesActivity extends PreferenceActivity {
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

@ -38,17 +38,22 @@ import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.ListView;
public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
public class SelectPublicKeyFragment extends ListFragmentWorkaround implements TextWatcher,
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids";
private Activity mActivity;
private SelectKeyCursorAdapter mAdapter;
private ListView mListView;
private EditText mSearchView;
private long mSelectedMasterKeyIds[];
private String mCurQuery;
/**
* Creates new instance of this fragment
@ -67,7 +72,8 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSearchView = (EditText)getActivity().findViewById(R.id.select_public_key_search);
mSearchView.addTextChangedListener(this);
mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS);
}
@ -82,7 +88,6 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
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));
@ -220,10 +225,16 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
// sort by selected master keys
orderBy = inMasterKeyList + " DESC, " + orderBy;
}
String where = null;
String whereArgs[] = null;
if(mCurQuery != null){
where = UserIds.USER_ID + " LIKE ?";
whereArgs = new String[]{mCurQuery+"%"};
}
// 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);
return new CursorLoader(getActivity(), baseUri, projection, where, whereArgs, orderBy);
}
@Override
@ -250,4 +261,21 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
// longer using it.
mAdapter.swapCursor(null);
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void afterTextChanged(Editable editable) {
String newQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null;
mCurQuery = newQuery;
getLoaderManager().restartLoader(0, null, this);
}
}

View File

@ -0,0 +1,45 @@
/*
* 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;
/**
* The AsyncTaskResultWrapper is used to wrap a result from a AsyncTask (for example: Loader).
* You can pass the result and an exception in it if an error occurred.
* Concept found at:
* https://stackoverflow.com/questions/19593577/how-to-handle-errors-in-custom-asynctaskloader
* @param <T> - Typ of the result which is wrapped
*/
public class AsyncTaskResultWrapper <T>{
private final T result;
private final Exception error;
public AsyncTaskResultWrapper(T result, Exception error){
this.result = result;
this.error = error;
}
public T getResult() {
return result;
}
public Exception getError() {
return error;
}
}

View File

@ -33,12 +33,13 @@ import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public class ImportKeysListLoader extends AsyncTaskLoader<List<ImportKeysListEntry>> {
public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
Context mContext;
InputData mInputData;
ArrayList<ImportKeysListEntry> data = new ArrayList<ImportKeysListEntry>();
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> entryListWrapper;
public ImportKeysListLoader(Context context, InputData inputData) {
super(context);
@ -47,15 +48,18 @@ public class ImportKeysListLoader extends AsyncTaskLoader<List<ImportKeysListEnt
}
@Override
public List<ImportKeysListEntry> loadInBackground() {
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(data, null);
if (mInputData == null) {
Log.e(Constants.TAG, "Input data is null!");
return data;
return entryListWrapper;
}
generateListOfKeyrings(mInputData);
return data;
return entryListWrapper;
}
@Override
@ -77,7 +81,7 @@ public class ImportKeysListLoader extends AsyncTaskLoader<List<ImportKeysListEnt
}
@Override
public void deliverResult(List<ImportKeysListEntry> data) {
public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
super.deliverResult(data);
}
@ -93,7 +97,7 @@ public class ImportKeysListLoader extends AsyncTaskLoader<List<ImportKeysListEnt
// 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
// armor blocks
BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
try {

View File

@ -26,15 +26,15 @@ import org.sufficientlysecure.keychain.util.KeyServer;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.List;
public class ImportKeysListServerLoader extends AsyncTaskLoader<List<ImportKeysListEntry>> {
public class ImportKeysListServerLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
Context mContext;
String mServerQuery;
String mKeyServer;
ArrayList<ImportKeysListEntry> data = new ArrayList<ImportKeysListEntry>();
private ArrayList<ImportKeysListEntry> entryList = new ArrayList<ImportKeysListEntry>();
private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> entryListWrapper;
public ImportKeysListServerLoader(Context context, String serverQuery, String keyServer) {
super(context);
@ -44,15 +44,18 @@ public class ImportKeysListServerLoader extends AsyncTaskLoader<List<ImportKeysL
}
@Override
public List<ImportKeysListEntry> loadInBackground() {
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(entryList, null);
if (mServerQuery == null) {
Log.e(Constants.TAG, "mServerQuery is null!");
return data;
return entryListWrapper;
}
queryServer(mServerQuery, mKeyServer);
return data;
return entryListWrapper;
}
@Override
@ -74,7 +77,7 @@ public class ImportKeysListServerLoader extends AsyncTaskLoader<List<ImportKeysL
}
@Override
public void deliverResult(List<ImportKeysListEntry> data) {
public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
super.deliverResult(data);
}
@ -87,13 +90,17 @@ public class ImportKeysListServerLoader extends AsyncTaskLoader<List<ImportKeysL
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
// add result to data
data.addAll(searchResult);
entryList.addAll(searchResult);
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(entryList, null);
} catch (KeyServer.InsufficientQuery e) {
Log.e(Constants.TAG, "InsufficientQuery", e);
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(entryList, e);
} catch (KeyServer.QueryException e) {
Log.e(Constants.TAG, "QueryException", e);
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(entryList, e);
} catch (KeyServer.TooManyResponses e) {
Log.e(Constants.TAG, "TooManyResponses", e);
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(entryList, e);
}
}

View File

@ -223,7 +223,7 @@ public class KeyListPublicAdapter extends CursorAdapter implements StickyListHea
*/
// default color
v.setBackgroundColor(Color.TRANSPARENT);
if (mSelection.get(position) != null) {
if (mSelection.get(position) != null && mSelection.get(position).booleanValue()) {
// this is a selected position, change color!
v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis));
}

View File

@ -0,0 +1,68 @@
/*
* 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.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import org.sufficientlysecure.keychain.R;
public class BadImportKeyDialogFragment extends DialogFragment {
private static final String ARG_BAD_IMPORT = "bad_import";
/**
* Creates a new instance of this Bad Import Key DialogFragment
*
* @param bad
* @return
*/
public static BadImportKeyDialogFragment newInstance(int bad) {
BadImportKeyDialogFragment frag = new BadImportKeyDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_BAD_IMPORT, bad);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = getActivity();
final int badImport = getArguments().getInt(ARG_BAD_IMPORT);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
alert.setTitle(R.string.warning);
alert.setMessage(activity.getResources()
.getQuantityString(R.plurals.bad_keys_encountered, badImport, badImport));
alert.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
alert.setCancelable(true);
return alert.create();
}
}

View File

@ -0,0 +1,152 @@
/*
* 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.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Choice;
import java.util.Vector;
public class CreateKeyDialogFragment extends DialogFragment {
public interface OnAlgorithmSelectedListener {
public void onAlgorithmSelected(Choice algorithmChoice, int keySize);
}
private static final String ARG_EDITOR_CHILD_COUNT = "child_count";
private int mNewKeySize;
private Choice mNewKeyAlgorithmChoice;
private OnAlgorithmSelectedListener mAlgorithmSelectedListener;
public void setOnAlgorithmSelectedListener(OnAlgorithmSelectedListener listener) {
mAlgorithmSelectedListener = listener;
}
public static CreateKeyDialogFragment newInstance(int mEditorChildCount) {
CreateKeyDialogFragment frag = new CreateKeyDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_EDITOR_CHILD_COUNT, mEditorChildCount);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity context = getActivity();
final LayoutInflater mInflater;
final int childCount = getArguments().getInt(ARG_EDITOR_CHILD_COUNT);
mInflater = context.getLayoutInflater();
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
View view = mInflater.inflate(R.layout.create_key_dialog, null);
dialog.setView(view);
dialog.setTitle(R.string.title_create_key);
boolean wouldBeMasterKey = (childCount == 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>(context,
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(
context, 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();
mAlgorithmSelectedListener.onAlgorithmSelected(mNewKeyAlgorithmChoice, mNewKeySize);
}
});
dialog.setCancelable(true);
dialog.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface di, int id) {
di.dismiss();
}
});
return dialog.create();
}
}

View File

@ -61,7 +61,8 @@ public class DeleteFileDialogFragment extends DialogFragment {
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setIcon(android.R.drawable.ic_dialog_alert);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
alert.setTitle(R.string.warning);
alert.setMessage(this.getString(R.string.file_delete_confirmation, deleteFile));
@ -82,7 +83,7 @@ public class DeleteFileDialogFragment extends DialogFragment {
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance(
R.string.progress_deleting_securely, ProgressDialog.STYLE_HORIZONTAL);
R.string.progress_deleting_securely, ProgressDialog.STYLE_HORIZONTAL, false, null);
// Message is received after deleting is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(activity, deletingDialog) {

View File

@ -17,17 +17,6 @@
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.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
@ -40,6 +29,14 @@ import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
public class DeleteKeyDialogFragment extends DialogFragment {
@ -100,7 +97,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
builder.setMessage(R.string.key_deletion_confirmation_multi);
}
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setIcon(R.drawable.ic_dialog_alert_holo_light);
builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
@Override

View File

@ -153,7 +153,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
dismiss();
long curKeyIndex = 1;
boolean keyOK = true;
String passPhrase = mPassphraseEditText.getText().toString();
String passphrase = mPassphraseEditText.getText().toString();
long keyId;
PGPSecretKey clickSecretKey = secretKey;
@ -163,7 +163,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passPhrase.toCharArray());
passphrase.toCharArray());
PGPPrivateKey testKey = clickSecretKey
.extractPrivateKey(keyDecryptor);
if (testKey == null) {
@ -206,10 +206,10 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
// cache the new passphrase
Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
PassphraseCacheService.addCachedPassphrase(activity, keyId, passPhrase);
PassphraseCacheService.addCachedPassphrase(activity, keyId, passphrase);
if (keyOK == false && clickSecretKey.getKeyID() != keyId) {
PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(),
passPhrase);
passphrase);
}
sendMessageToHandler(MESSAGE_OKAY);

View File

@ -21,28 +21,40 @@ import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnKeyListener;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.KeyEvent;
import org.sufficientlysecure.keychain.R;
public class ProgressDialogFragment extends DialogFragment {
private static final String ARG_MESSAGE_ID = "message_id";
private static final String ARG_STYLE = "style";
private static final String ARG_CANCELABLE = "cancelable";
private OnCancelListener mOnCancelListener;
/**
* Creates new instance of this fragment
*
* @param id
* @param messageId
* @param style
* @param cancelable
* @return
*/
public static ProgressDialogFragment newInstance(int messageId, int style) {
public static ProgressDialogFragment newInstance(int messageId, int style, boolean cancelable,
OnCancelListener onCancelListener) {
ProgressDialogFragment frag = new ProgressDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_MESSAGE_ID, messageId);
args.putInt(ARG_STYLE, style);
args.putBoolean(ARG_CANCELABLE, cancelable);
frag.setArguments(args);
frag.mOnCancelListener = onCancelListener;
return frag;
}
@ -60,7 +72,6 @@ public class ProgressDialogFragment extends DialogFragment {
/**
* Updates progress of dialog
*
* @param messageId
* @param progress
* @param max
*/
@ -74,7 +85,7 @@ public class ProgressDialogFragment extends DialogFragment {
/**
* Updates progress of dialog
*
* @param messageId
* @param message
* @param progress
* @param max
*/
@ -86,12 +97,20 @@ public class ProgressDialogFragment extends DialogFragment {
dialog.setMax(max);
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
if (this.mOnCancelListener != null)
this.mOnCancelListener.onCancel(dialog);
}
/**
* Creates dialog
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Activity activity = getActivity();
final Activity activity = getActivity();
ProgressDialog dialog = new ProgressDialog(activity);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
@ -100,10 +119,22 @@ public class ProgressDialogFragment extends DialogFragment {
int messageId = getArguments().getInt(ARG_MESSAGE_ID);
int style = getArguments().getInt(ARG_STYLE);
boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE);
dialog.setMessage(getString(messageId));
dialog.setProgressStyle(style);
if (cancelable) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE,
activity.getString(R.string.progress_cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
}
// Disable the back button
OnKeyListener keyListener = new OnKeyListener() {

View File

@ -101,9 +101,9 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
public void onClick(DialogInterface dialog, int id) {
dismiss();
String passPhrase1 = mPassphraseEditText.getText().toString();
String passPhrase2 = mPassphraseAgainEditText.getText().toString();
if (!passPhrase1.equals(passPhrase2)) {
String passphrase1 = mPassphraseEditText.getText().toString();
String passphrase2 = mPassphraseAgainEditText.getText().toString();
if (!passphrase1.equals(passphrase2)) {
Toast.makeText(
activity,
getString(R.string.error_message,
@ -112,7 +112,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
return;
}
if (passPhrase1.equals("")) {
if (passphrase1.equals("")) {
Toast.makeText(
activity,
getString(R.string.error_message,
@ -123,7 +123,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
// return resulting data back to activity
Bundle data = new Bundle();
data.putString(MESSAGE_NEW_PASSPHRASE, passPhrase1);
data.putString(MESSAGE_NEW_PASSPHRASE, passphrase1);
sendMessageToHandler(MESSAGE_OKAY, data);
}

View File

@ -53,7 +53,6 @@ public class ShareNfcDialogFragment extends DialogFragment {
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);

View File

@ -17,28 +17,26 @@
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.support.v4.app.DialogFragment;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
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.QrCodeUtils;
import java.util.ArrayList;
public class ShareQrCodeDialogFragment extends DialogFragment {
private static final String ARG_KEY_URI = "uri";
private static final String ARG_FINGERPRINT_ONLY = "fingerprint_only";

View File

@ -35,6 +35,7 @@ import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
@ -60,6 +61,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
TextView mKeyId;
TextView mCreationDate;
BootstrapButton mExpiryDateButton;
GregorianCalendar mCreatedDate;
GregorianCalendar mExpiryDate;
GregorianCalendar mOriginalExpiryDate = null;
CheckBox mChkCertify;
@ -142,8 +144,12 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
if (date == null) {
date = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
}
DatePickerDialog dialog = new DatePickerDialog(getContext(),
/*
* Using custom DatePickerDialog which overrides the setTitle because
* the DatePickerDialog title is buggy (unix warparound bug).
* See: https://code.google.com/p/android/issues/detail?id=49066
*/
DatePickerDialog dialog = new ExpiryDatePickerDialog(getContext(),
mExpiryDateSetListener, date.get(Calendar.YEAR), date.get(Calendar.MONTH),
date.get(Calendar.DAY_OF_MONTH));
mDatePickerResultCount = 0;
@ -161,6 +167,21 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
}
}
});
// setCalendarViewShown() is supported from API 11 onwards.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB)
// Hide calendarView in tablets because of the unix warparound bug.
dialog.getDatePicker().setCalendarViewShown(false);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
if ( dialog != null && mCreatedDate != null ) {
dialog.getDatePicker().setMinDate(mCreatedDate.getTime().getTime()+ DateUtils.DAY_IN_MILLIS);
} else {
//When created date isn't available
dialog.getDatePicker().setMinDate(date.getTime().getTime()+ DateUtils.DAY_IN_MILLIS);
}
}
dialog.show();
}
});
@ -237,7 +258,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
cal.setTime(PgpKeyHelper.getCreationDate(key));
mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime()));
setCreatedDate(cal);
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
Date expiryDate = PgpKeyHelper.getExpiryDate(key);
if (expiryDate == null) {
@ -268,6 +289,15 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
mEditorListener = listener;
}
private void setCreatedDate(GregorianCalendar date) {
mCreatedDate = date;
if (date == null) {
mCreationDate.setText(getContext().getString(R.string.none));
} else {
mCreationDate.setText(DateFormat.getDateInstance().format(date.getTime()));
}
}
private void setExpiryDate(GregorianCalendar date) {
mExpiryDate = date;
if (date == null) {
@ -320,3 +350,14 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
}
}
class ExpiryDatePickerDialog extends DatePickerDialog {
public ExpiryDatePickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
super(context, callBack, year, monthOfYear, dayOfMonth);
}
//Set permanent title.
public void setTitle(CharSequence title) {
super.setTitle(getContext().getString(R.string.expiry_date_dialog_title));
}
}

View File

@ -46,13 +46,25 @@ 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.beardedhen.androidbootstrap.BootstrapButton;
import org.spongycastle.openpgp.PGPSecretKey;
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.CreateKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.util.Choice;
public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Editor {
private LayoutInflater mInflater;
private BootstrapButton mPlusButton;
@ -252,84 +264,16 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
}
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;
CreateKeyDialogFragment mCreateKeyDialogFragment = CreateKeyDialogFragment.newInstance(mEditors.getChildCount());
mCreateKeyDialogFragment.setOnAlgorithmSelectedListener(new CreateKeyDialogFragment.OnAlgorithmSelectedListener() {
@Override
public void onAlgorithmSelected(Choice algorithmChoice, int keySize) {
mNewKeyAlgorithmChoice = algorithmChoice;
mNewKeySize = keySize;
createKey();
}
}
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();
});
mCreateKeyDialogFragment.show(mActivity.getSupportFragmentManager(), "createKeyDialog");
break;
}
@ -382,7 +326,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
private void createKey() {
// Send all information needed to service to edit key in other thread
Intent intent = new Intent(mActivity, KeychainIntentService.class);
final Intent intent = new Intent(mActivity, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_GENERATE_KEY);
@ -390,18 +334,18 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
Bundle data = new Bundle();
Boolean isMasterKey;
String passPhrase;
String passphrase;
if (mEditors.getChildCount() > 0) {
PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
passPhrase = PassphraseCacheService
passphrase = PassphraseCacheService
.getCachedPassphrase(mActivity, masterKey.getKeyID());
isMasterKey = false;
} else {
passPhrase = "";
passphrase = "";
isMasterKey = true;
}
data.putBoolean(KeychainIntentService.GENERATE_KEY_MASTER_KEY, isMasterKey);
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, 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);
@ -409,7 +353,12 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
// show progress dialog
mGeneratingDialog = ProgressDialogFragment.newInstance(R.string.progress_generating,
ProgressDialog.STYLE_SPINNER);
ProgressDialog.STYLE_SPINNER, true, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
mActivity.stopService(intent);
}
});
// Message is received after generating is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity,

View File

@ -1,6 +1,8 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2011 Thialfihar <thi@thialfihar.org>
* 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
@ -32,6 +34,7 @@ import java.util.List;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
@ -172,11 +175,11 @@ public class HkpKeyServer extends KeyServer {
if (e.getCode() == 404) {
return results;
} else {
if (e.getData().toLowerCase().contains("no keys found")) {
if (e.getData().toLowerCase(Locale.US).contains("no keys found")) {
return results;
} else if (e.getData().toLowerCase().contains("too many")) {
} else if (e.getData().toLowerCase(Locale.US).contains("too many")) {
throw new TooManyResponses();
} else if (e.getData().toLowerCase().contains("insufficient")) {
} else if (e.getData().toLowerCase(Locale.US).contains("insufficient")) {
throw new InsufficientQuery();
}
}

View File

@ -1,6 +1,8 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2011 Thialfihar <thi@thialfihar.org>
* 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
@ -16,14 +18,8 @@
package org.sufficientlysecure.keychain.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.os.Parcel;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
public abstract class KeyServer {
@ -52,5 +48,5 @@ public abstract class KeyServer {
abstract String get(long keyId) throws QueryException;
abstract void add(String armouredText) throws AddKeyException;
abstract void add(String armoredText) throws AddKeyException;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -6,7 +6,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
@ -26,4 +26,4 @@
tools:layout="@layout/api_app_settings_fragment" />
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@ -6,7 +6,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
@ -18,4 +18,4 @@
tools:layout="@layout/api_app_settings_fragment" />
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@ -27,7 +27,7 @@
android:text="@string/label_no_passphrase" />
<com.beardedhen.androidbootstrap.BootstrapButton
android:id="@+id/edit_key_btn_change_pass_phrase"
android:id="@+id/edit_key_btn_change_passphrase"
android:layout_width="match_parent"
android:layout_height="60dp"
android:padding="4dp"

View File

@ -135,7 +135,7 @@
<TableRow>
<TextView
android:id="@+id/label_passPhrase"
android:id="@+id/label_passphrase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@ -144,7 +144,7 @@
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/passPhrase"
android:id="@+id/passphrase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
@ -153,7 +153,7 @@
<TableRow>
<TextView
android:id="@+id/label_passPhraseAgain"
android:id="@+id/label_passphraseAgain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@ -162,7 +162,7 @@
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/passPhraseAgain"
android:id="@+id/passphraseAgain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />

View File

@ -71,8 +71,8 @@
<LinearLayout
android:id="@+id/editors"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>
</LinearLayout>
</LinearLayout>

View File

@ -4,8 +4,16 @@
android:layout_height="match_parent"
android:layout_centerHorizontal="true" >
<EditText
android:id="@+id/select_public_key_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/menu_search"
android:drawableLeft="@drawable/ic_action_search"/>
<FrameLayout
android:id="@+id/select_public_key_fragment_container"
android:layout_below="@id/select_public_key_search"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@ -11,5 +11,10 @@
android:id="@+id/menu_key_list_public_export"
app:showAsAction="never"
android:title="@string/menu_export_keys" />
<item
android:id="@+id/menu_key_list_public_search"
android:title="@string/menu_search"
android:icon="@drawable/ic_action_search"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="ifRoom" />
</menu>

View File

@ -1,8 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_key_list_public_multi_select_all"
android:icon="@drawable/ic_action_select_all"
android:title="@string/menu_select_all" />
<item
android:id="@+id/menu_key_list_public_multi_encrypt"
android:icon="@drawable/ic_action_secure"
android:title="@string/menu_encrypt_to" />
<item
android:id="@+id/menu_key_list_public_multi_delete"

View File

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_key_list_public_multi_select_all"
android:icon="@drawable/ic_action_select_all"
android:title="@string/menu_select_all" />
<item
android:id="@+id/menu_key_list_public_multi_delete"
android:icon="@drawable/ic_action_discard"

View File

@ -1,11 +1,11 @@
<html>
<head></head>
<body>
<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> ist eine OpenPGP-Implementierung für Android. Die Entwicklung begann als ein Fork von Android Privacy Guard (APG).</p>
<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
<p><a href="http://www.openkeychain.org">OpenKeychain</a> ist eine OpenPGP implementation für Android.</p>
<p>Lizenz: GPLv3+</p>
<h2>Entwickler OpenPGP Keychain</h2>
<h2>Entwickler OpenKeychain</h2>
<ul>
<li>Dominik Schürmann (Leitender Entwickler)</li>
<li>Ash Hughes (crypto patches)</li>
@ -23,9 +23,9 @@
<h2>Bibliotheken</h2>
<ul>
<li>
<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
<a href="http://developer.android.com/tools/support-library/index.html">Android Support Bibliothek v4</a> (Apache Lizenz v2)</li>
<li>
<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
<a href="http://developer.android.com/tools/support-library/index.html">Android Support Bibliothek v7 'appcompat'</a> (Apache Lizenz v2)</li>
<li>
<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache Lizenz v2)</li>
<li>
@ -36,6 +36,8 @@
<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 Lizenz)</li>
<li>
<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache Lizenz v2)</li>
<li>
<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Bibliothek</a> (Apache Lizenz v2)</li>
<li>Icons von <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike Lizenz 3.0)</li>
<li>Icons von <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
</ul>

View File

@ -16,12 +16,12 @@
<h2>2.2</h2>
<ul>
<li>new design with navigation drawer</li>
<li>new public key list design</li>
<li>Neus Design für die Liste der öffentlichen Schlüssel</li>
<li>new public key view</li>
<li>bug fixes for importing of keys</li>
<li>Fehler beim Schlüsselimport behoben</li>
<li>key cross-certification (thanks to Ash Hughes)</li>
<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
<li>first version with new languages (thanks to the contributors on Transifex)</li>
<li>Erste Version mit neuen Sprachen (Danke an die Mitwirkenden bei Transifex)</li>
<li>sharing of keys via QR Codes fixed and improved</li>
<li>package signature verification for API</li>
</ul>
@ -31,16 +31,16 @@
</ul>
<h2>2.1</h2>
<ul>
<li>lots of bug fixes</li>
<li>new API for developers</li>
<li>PRNG bug fix by Google</li>
<li>Viele Fehler behoben</li>
<li>Neue API für Entwickler</li>
<li>PRNG Bugfix von Google</li>
</ul>
<h2>2.0</h2>
<ul>
<li>complete redesign</li>
<li>Komlett neu designd</li>
<li>share public keys via qr codes, nfc beam</li>
<li>sign keys</li>
<li>upload keys to server</li>
<li>Schlüssel signieren</li>
<li>Schlüssel auf den Server hochladen</li>
<li>fixes import issues</li>
<li>new AIDL API</li>
</ul>
@ -56,25 +56,25 @@
<h2>1.0.7</h2>
<ul>
<li>fixed problem with signature verification of texts with trailing newline</li>
<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
<li>weitere Optionen für die Time-to-live des Passphrasencaches (20, 40, 60 mins)</li>
</ul>
<h2>1.0.6</h2>
<ul>
<li>account adding crash on Froyo fixed</li>
<li>crash beim Hinzufügen eines Kontos auf Froyo repariert</li>
<li>sichere Dateilöschung</li>
<li>option to delete key file after import</li>
<li>Option, um Schlüsseldatei nach dem Import zu löschen</li>
<li>Streamverschlüsselung/-entschlüsselung (Galerie, etc.)</li>
<li>new options (language, force v3 signatures)</li>
<li>interface changes</li>
<li>neue Optionen (Sprache, v3-Unterschriften erzwingen)</li>
<li>Interfaceänderungen</li>
<li>Fehlerbehebungen</li>
</ul>
<h2>1.0.5</h2>
<ul>
<li>Deutsche und Italienische Übersetzung</li>
<li>much smaller package, due to reduced BC sources</li>
<li>new preferences GUI</li>
<li>layout adjustment for localization</li>
<li>signature bugfix</li>
<li>viel kleineres Paket, dank reduzierter BC Quellen</li>
<li>Neues Einstellungs-GUI</li>
<li>Lay-Out-Anpassung für die Lokalisierung</li>
<li>Fehler bei Signatur behoben</li>
</ul>
<h2>1.0.4</h2>
<ul>
@ -82,11 +82,11 @@
</ul>
<h2>1.0.3</h2>
<ul>
<li>fixed crashes during encryption/signing and possibly key export</li>
<li>Absturz während der Verschlüsselung/Signierung und möglicherweise Schlüsselexport behoben.</li>
</ul>
<h2>1.0.2</h2>
<ul>
<li>filterable key lists</li>
<li>Filterbare Schlüsselliste</li>
<li>smarter pre-selection of encryption keys</li>
<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
@ -100,7 +100,7 @@
<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
<li>support of more file managers (including ASTRO)</li>
<li>Slovenian translation</li>
<li>new database, much faster, less memory usage</li>
<li>Neue Datenbank, viel schneller, weniger Speicherbedarf</li>
<li>defined Intents and content provider for other apps</li>
<li>Fehlerbehebungen</li>
</ul>

View File

@ -6,14 +6,14 @@
<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
<h2>I found a bug in OpenPGP Keychain!</h2>
<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
<h2>Ich habe einen Fehler in OpenKeychain gefunden!</h2>
<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
<h2>Contribute</h2>
<p>If you want to help us developing OpenPGP Keychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
<h2>Translations</h2>
<p>Help translating OpenPGP Keychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenPGP Keychain on Transifex</a>.</p>
<h2>Übersetzungen</h2>
<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
</body>
</html>

View File

@ -1,11 +1,11 @@
<html>
<head></head>
<body>
<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
<p><a href="http://www.openkeychain.org">OpenKeychain</a> is an OpenPGP implementation for Android.</p>
<p>License: GPLv3+</p>
<h2>Developers OpenPGP Keychain</h2>
<h2>Developers OpenKeychain</h2>
<ul>
<li>Dominik Schürmann (Lead developer)</li>
<li>Ash Hughes (crypto patches)</li>
@ -36,6 +36,8 @@
<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
<li>
<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
<li>
<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Apache License v2)</li>
<li>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
</ul>

View File

@ -6,14 +6,14 @@
<p>It is recommended that you install <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> for enhanced file selection and <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> to scan generated QR Codes. Clicking on the links will open Google Play Store or F-Droid for installation.</p>
<h2>I found a bug in OpenPGP Keychain!</h2>
<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenPGP Keychain</a>.</p>
<h2>I found a bug in OpenKeychain!</h2>
<p>Please report the bug using the <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">issue tracker of OpenKeychain</a>.</p>
<h2>Contribute</h2>
<p>If you want to help us developing OpenPGP Keychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
<p>If you want to help us developing OpenKeychain by contributing code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">follow our small guide on Github</a>.</p>
<h2>Translations</h2>
<p>Help translating OpenPGP Keychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenPGP Keychain on Transifex</a>.</p>
<p>Help translating OpenKeychain! Everybody can participate at <a href="https://www.transifex.com/projects/p/openpgp-keychain/">OpenKeychain on Transifex</a>.</p>
</body>
</html>

View File

@ -1,11 +1,11 @@
<html>
<head></head>
<body>
<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> es una implementación de OpenPGP para Android. Su desarrollo comenzó como un fork de Android Privacy Guard (APG).</p>
<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
<p><a href="http://www.openkeychain.org">OpenKeychain</a> es una implementación de OpenPGP para Android.</p>
<p>Licencia: GPLv3+</p>
<h2>Desarrolladores de OpenPGP Keychain</h2>
<h2>Desarrolladores OpenKeychain</h2>
<ul>
<li>Dominik Schürmann (Desarrollador principal)</li>
<li>Ash Hughes (Parches cryptográficos)</li>
@ -36,6 +36,8 @@
<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (Licencia MIT X11)</li>
<li>
<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Licencia Apache v2)</li>
<li>
<a href="https://github.com/johnkil/Android-AppMsg">Librería Android AppMsg</a> (Licencia Apache v2)</li>
<li>Icons de <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Compartir-Igual licencia 3.0)</li>
<li>Iconos de <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Dominio Público)</li>
</ul>

View File

@ -6,14 +6,14 @@
<p>Es recomendable que instales <a href="market://details?id=org.openintents.filemanager">OI File Manager</a> para una mejor selección de archivos y <a href="market://details?id=com.google.zxing.client.android">Barcode Scanner</a> para escanear los códigos QR generados. Pulsando en los enlaces se abrirá Google Play o F-Droid.</p>
<h2>¡He encontrado un bug en OpenPGP Keychain!</h2>
<p>Por favor, informa de errores usando el <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">seguimiento de incidencias de OpenPGP Keychain</a>.</p>
<h2>¡He encontrado un bug en OpenKeychain!</h2>
<p>Por favor, informa de errores usando el <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">seguimiento de incidencias de OpenKeychain</a>.</p>
<h2>Aportar</h2>
<p>Si quieres ayudarnos con el desarrollo de OpenPGP Keychain aportando código <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">sigue nuestra pequeña guía en Github</a>.</p>
<p>Si quieres ayudarnos con el desarrollo de OpenKeychain aportando código <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">sigue nuestra pequeña guía en Github</a>.</p>
<h2>Traducciones</h2>
<p>¡Ayúdanos a traducir OpenPGP Keychain! Todo el mundo es bienvenido en <a href="https://www.transifex.com/projects/p/openpgp-keychain/">Transifex - OpenPGP Keychain</a>.</p>
<p>¡Ayúdanos a traducir OpenKeychain! Todo el mundo es bienvenido en <a href="https://www.transifex.com/projects/p/openpgp-keychain/">Transifex - OpenKeychain</a>.</p>
</body>
</html>

View File

@ -1,11 +1,11 @@
<html>
<head></head>
<body>
<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
<p><a href="http://sufficientlysecure.org/keychain">Le porte-clefs OpenPGP</a> est une implémentation d'OpenPGP pour Android. Le développement a commencé comme bifurcation d'Android Privacy Guard (APG).</p>
<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
<p><a href="http://www.openkeychain.org">OpenKeychain</a> est une implémentation d'OpenPGP pour Android.</p>
<p>Licence : GPLv3+</p>
<h2>Les développeurs du Porte-clefs OpenPGP</h2>
<h2>Les développeurs d'OpenKeychain</h2>
<ul>
<li>Dominik Schürmann (développeur principal)</li>
<li>Ash Hughes (correctif crypto)</li>
@ -36,6 +36,8 @@
<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (Licence MIT X11)</li>
<li>
<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Licence Apache v2)</li>
<li>
<a href="https://github.com/johnkil/Android-AppMsg">Bibliothèque Android AppMsg</a> (Licence Apache v2)</li>
<li>Icônes du <a href="http://rrze-icon-set.berlios.de/">jeu d'icônes RRZE</a> (Licence Creative Commons Paternité - Partage des Conditions Initiales à l'Identique 3.0)</li>
<li>Icônes du <a href="http://tango.freedesktop.org/">jeu d'icônes Tango</a> (domaine public)</li>
</ul>

View File

@ -6,14 +6,14 @@
<p>Il vous est recommendé d'installer le <a href="market://details?id=org.openintents.filemanager">gestionnaire de fichiers OI</a> pour sa fonction améliorée de séléction des fichiers et le <a href="market://details?id=com.google.zxing.client.android">lecteur de codes à barres</a> pour balayer les codes QR générés. Cliquer sur les liens ouvrira Google Play Store ou F-Droid pour l'installation.</p>
<h2>J'ai trouvé un bogue dans le Porte-clefs OpenPGP !</h2>
<p>Veuillez rapporter le bogue en utilisant le <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">gestionnaire de bogues du Porte-clefs OpenPGP</a>.</p>
<h2>J'ai trouvé un bogue dans OpenKeychain !</h2>
<p>Veuillez rapporter le bogue en utilisant le <a href="https://github.com/openpgp-keychain/openpgp-keychain/issues">gestionnaire de bogue d'OpenKeychain</a>.</p>
<h2>Contribuer</h2>
<p>Si vous voulez nous aider à développer le Porte-clefs OpenPGP en y contribuant par du code, <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">veuillez suivre notre petit guide sur Github</a>.</p>
<p>Si vous voulez nous aider à développer OpenKeychain en y contribuant par du code <a href="https://github.com/openpgp-keychain/openpgp-keychain#contribute-code">veuillez suivre notre petit guide sur Github</a>.</p>
<h2>Traductions</h2>
<p>Aidez-nous à traduire le Porte-clefs OpenPGP ! Tout le monde peut y participer sur la <a href="https://www.transifex.com/projects/p/openpgp-keychain/">page Transifex du Porte-clefs OpenPGP Keychain</a>.</p>
<p>Aidez-nous à traduire le OpenKeychain ! Tout le monde peut y participer sur la <a href="https://www.transifex.com/projects/p/openpgp-keychain/">page d'OpenKeychain sur Transifex</a>.</p>
</body>
</html>

View File

@ -1,43 +1,45 @@
<html>
<head></head>
<body>
<p><a href="http://sufficientlysecure.org/keychain">http://sufficientlysecure.org/keychain</a></p>
<p><a href="http://sufficientlysecure.org/keychain">OpenPGP Keychain</a> is an OpenPGP implementation for Android. The development began as a fork of Android Privacy Guard (APG).</p>
<p>License: GPLv3+</p>
<p><a href="http://www.openkeychain.org">http://www.openkeychain.org</a></p>
<p><a href="http://www.openkeychain.org">OpenKeychain</a> un implementazione OpenPGP per Android.</p>
<p>Licenza: GPLv3+</p>
<h2>Developers OpenPGP Keychain</h2>
<h2>Sviluppatori OpenKeychain</h2>
<ul>
<li>Dominik Schürmann (Lead developer)</li>
<li>Ash Hughes (crypto patches)</li>
<li>Dominik Schürmann (Capo Sviluppatore)</li>
<li>Ash Hughes (Patch crittografia)</li>
<li>Brian C. Barnes</li>
<li>Bahtiar 'kalkin' Gadimov (UI)</li>
<li>Bahtiar 'kalkin' Gadimov (Interfaccia Utente)</li>
</ul>
<h2>Developers APG 1.x</h2>
<h2>Sviluppatori APG 1.x</h2>
<ul>
<li>'Thialfihar' (Lead developer)</li>
<li>'Senecaso' (QRCode, sign key, upload key)</li>
<li>'Thialfihar' (Capo Sviluppatore)</li>
<li>'Senecaso' (QRCode, firma chiavi, caricamento chiavi)</li>
<li>Oliver Runge</li>
<li>Markus Doits</li>
</ul>
<h2>Libraries</h2>
<h2>Librerie</h2>
<ul>
<li>
<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Apache License v2)</li>
<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v4</a> (Licenza Apache v2)</li>
<li>
<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Apache License v2)</li>
<a href="http://developer.android.com/tools/support-library/index.html">Android Support Library v7 'appcompat'</a> (Licenza Apache v2)</li>
<li>
<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Apache License v2)</li>
<a href="https://github.com/emilsjolander/StickyListHeaders">StickyListHeaders</a> (Licenza Apache v2)</li>
<li>
<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (MIT License)</li>
<a href="https://github.com/Bearded-Hen/Android-Bootstrap">Android-Bootstrap</a> (Licenza MIT)</li>
<li>
<a href="http://code.google.com/p/zxing/">ZXing</a> (Apache License v2)</li>
<a href="http://code.google.com/p/zxing/">ZXing</a> (Licenza Apache v2)</li>
<li>
<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (MIT X11 License)</li>
<a href="http://rtyley.github.com/spongycastle/">SpongyCastle</a> (Licenza MIT X11)</li>
<li>
<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Apache License v2)</li>
<li>Icons from <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Creative Commons Attribution Share-Alike licence 3.0)</li>
<li>Icons from <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Public Domain)</li>
<a href="https://github.com/dschuermann/html-textview">HtmlTextView</a> (Licenza Apache v2)</li>
<li>
<a href="https://github.com/johnkil/Android-AppMsg">Android AppMsg Library</a> (Licenza Apache v2)</li>
<li>Icone da <a href="http://rrze-icon-set.berlios.de/">RRZE Icon Set</a> (Licenza Creative Commons Attribution Share-Alike 3.0)</li>
<li>Icone da <a href="http://tango.freedesktop.org/">Tango Icon Set</a> (Pubblico Dominio)</li>
</ul>
</body>
</html>

View File

@ -3,106 +3,106 @@
<body>
<h2>2.3</h2>
<ul>
<li>remove unnecessary export of public keys when exporting secret key (thanks to Ash Hughes)</li>
<li>fix setting expiry dates on keys (thanks to Ash Hughes)</li>
<li>more internal fixes when editing keys (thanks to Ash Hughes)</li>
<li>querying keyservers directly from the import screen</li>
<li>fix layout and dialog style on Android 2.2-3.0</li>
<li>fix crash on keys with empty user ids</li>
<li>fix crash and empty lists when coming back from signing screen</li>
<li>Bouncy Castle (cryptography library) updated from 1.47 to 1.50 and build from source</li>
<li>fix upload of key from signing screen</li>
<li>rimossa esportazione non necessaria delle chiavi pubbliche quando si esportano le chiavi private (grazie a Ash Hughes)</li>
<li>corretto impostazione data di scadenza delle chiavi (grazie a Ash Hughes)</li>
<li>altre correzioni interne quando si modificano le chiavi (grazie a Ash Hughes)</li>
<li>interrogazione server delle chiavi direttamente dalla schermata di importazione</li>
<li>corretto layout e dialog style su Android 2.2-3.0</li>
<li>corretto crash su chiavi con id utente vuoto</li>
<li>corretto crash e liste vuote quando si torna dalla schermata di firma</li>
<li>Bouncy Castle (libreria crittografica) aggiornata da 1.47 a 1.50 e compilata da sorgente</li>
<li>corretto upload delle chiavi dalla schermata di firma</li>
</ul>
<h2>2.2</h2>
<ul>
<li>new design with navigation drawer</li>
<li>new public key list design</li>
<li>new public key view</li>
<li>bug fixes for importing of keys</li>
<li>key cross-certification (thanks to Ash Hughes)</li>
<li>handle UTF-8 passwords properly (thanks to Ash Hughes)</li>
<li>first version with new languages (thanks to the contributors on Transifex)</li>
<li>sharing of keys via QR Codes fixed and improved</li>
<li>package signature verification for API</li>
<li>nuovo design con drawer di navigazione</li>
<li>nuovo design per la lista chiavi pubbliche</li>
<li>nuova visuale chiavi pubbliche</li>
<li>correzione bug per importazione chiavi</li>
<li>Chiave certificazione incrociata (grazie a Ash Hughes)</li>
<li>password UTF-8 gestite correttamente (grazie a Ash Hughes)</li>
<li>Prima versione con nuove lingue (grazie ai contributori su Transifex)</li>
<li>condivisione di chiavi via Codici QR corretta e migliorata</li>
<li>Verifica firma pacchetto per API</li>
</ul>
<h2>2.1.1</h2>
<ul>
<li>API Updates, preparation for K-9 Mail integration</li>
<li>Aggiornamenti API, preparazione per integrazione con K-9 Mail</li>
</ul>
<h2>2.1</h2>
<ul>
<li>lots of bug fixes</li>
<li>new API for developers</li>
<li>molte correzioni di bug</li>
<li>nuove API per sviluppatori</li>
<li>PRNG bug fix by Google</li>
</ul>
<h2>2.0</h2>
<ul>
<li>complete redesign</li>
<li>share public keys via qr codes, nfc beam</li>
<li>sign keys</li>
<li>upload keys to server</li>
<li>fixes import issues</li>
<li>new AIDL API</li>
<li>completo restyle</li>
<li>condivisione chiavi pubbliche via codici qr, nfc beam</li>
<li>firma chiavi</li>
<li>Carica chiavi sul server</li>
<li>corrette caratteristiche di importazione</li>
<li>nuova AIDL API</li>
</ul>
<h2>1.0.8</h2>
<ul>
<li>basic keyserver support</li>
<li>supporto base per server delle chiavi</li>
<li>app2sd</li>
<li>more choices for pass phrase cache: 1, 2, 4, 8, hours</li>
<li>translations: Norwegian (thanks, Sander Danielsen), Chinese (thanks, Zhang Fredrick)</li>
<li>bugfixes</li>
<li>optimizations</li>
<li>Aggiunte opzioni per la cache della frase di accesso: 1, 2, 4, 8 ore</li>
<li>Traduzioni: Norvegese (grazie, Sander Danielsen), Cinese (grazie, Zhang Fredrick)</li>
<li>correzione bug</li>
<li>ottimizzazioni</li>
</ul>
<h2>1.0.7</h2>
<ul>
<li>fixed problem with signature verification of texts with trailing newline</li>
<li>more options for pass phrase cache time to live (20, 40, 60 mins)</li>
<li>corretto problema con la verifica firma di testi con capo finale</li>
<li>maggiori opzioni per il tempo di mantenimento della cache della frase di accesso (20, 40, 60 minuti)</li>
</ul>
<h2>1.0.6</h2>
<ul>
<li>account adding crash on Froyo fixed</li>
<li>secure file deletion</li>
<li>option to delete key file after import</li>
<li>stream encryption/decryption (gallery, etc.)</li>
<li>new options (language, force v3 signatures)</li>
<li>interface changes</li>
<li>bugfixes</li>
<li>crash della aggiunta degli account risolto su Froyo</li>
<li>Cancellazione sicura dei file</li>
<li>opzione per cancellare file delle chiavi dopo l'importazione</li>
<li>flusso codifica/decodifica (galleria, ecc.)</li>
<li>nuove opzioni (lingua, forza firme v3)</li>
<li>cambiamenti interfaccia</li>
<li>correzione bug</li>
</ul>
<h2>1.0.5</h2>
<ul>
<li>German and Italian translation</li>
<li>much smaller package, due to reduced BC sources</li>
<li>new preferences GUI</li>
<li>layout adjustment for localization</li>
<li>signature bugfix</li>
<li>Traduzione Italiana e Tedesca</li>
<li>dimensioni pacchetto ridotte, a causa della riduzione dei sorgenti BC</li>
<li>Nuove preferenze GUI</li>
<li>Regolazione layout per la localizzazione</li>
<li>correzione bug firma</li>
</ul>
<h2>1.0.4</h2>
<ul>
<li>fixed another crash caused by some SDK bug with query builder</li>
<li>corretto altro crash causato da alcuni bug SDK con query builder</li>
</ul>
<h2>1.0.3</h2>
<ul>
<li>fixed crashes during encryption/signing and possibly key export</li>
<li>corretto crash durante codifica/firma e possibilita' di esportare chiave</li>
</ul>
<h2>1.0.2</h2>
<ul>
<li>filterable key lists</li>
<li>smarter pre-selection of encryption keys</li>
<li>new Intent handling for VIEW and SEND, allows files to be encrypted/decrypted out of file managers</li>
<li>fixes and additional features (key preselection) for K-9 Mail, new beta build available</li>
<li>liste chiavi filtrabili</li>
<li>preselezione di chiavi di codifica intelligente</li>
<li>nuovo gestore intent per VIEW e SEND, permette la codifica/decodifica file all'infuori di file manager</li>
<li>caratteristiche corrette e aggiunte (preselezione chiavi) per K-9 Mail. nuova build beta disponibile</li>
</ul>
<h2>1.0.1</h2>
<ul>
<li>GMail account listing was broken in 1.0.0, fixed again</li>
<li>elencazione account GMail corrotta in 1.0.0, corretta nuovamente</li>
</ul>
<h2>1.0.0</h2>
<ul>
<li>K-9 Mail integration, APG supporting beta build of K-9 Mail</li>
<li>support of more file managers (including ASTRO)</li>
<li>Slovenian translation</li>
<li>new database, much faster, less memory usage</li>
<li>defined Intents and content provider for other apps</li>
<li>bugfixes</li>
<li>integrazione K-9 Mail, APG supporto beta build di K-9 Mail</li>
<li>supporto per altri file manager (incluso ASTRO)</li>
<li>traduzione Slovena</li>
<li>Nuovo database, piu' veloce, utilizzo memoria ridotto</li>
<li>Definiti Intent e ContentProvider per le altre app</li>
<li>correzione bug</li>
</ul>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More