Merge branch 'master' into certs

Conflicts:
	OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java
	OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
This commit is contained in:
Vincent Breitmoser 2014-03-12 23:45:21 +01:00
commit a9c9b6132c
178 changed files with 3771 additions and 1191 deletions

View File

@ -10,7 +10,7 @@ before_install:
- export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
# Install required Android components. # Install required Android components.
- echo "y" | android update sdk -a --filter build-tools-19.0.1,android-19,platform-tools,extra-android-support,extra-android-m2repository,android-17 --no-ui --force - 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" install: echo "Installation done"
script: gradle assemble -S -q script: gradle assemble -S -q

View File

@ -5,7 +5,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:0.8.3' classpath 'com.android.tools.build:gradle:0.9.0'
} }
} }
@ -13,12 +13,13 @@ apply plugin: 'android'
dependencies { dependencies {
compile 'com.android.support:support-v4:19.0.1' compile 'com.android.support:support-v4:19.0.1'
compile project(':libraries:keychain-api-library') compile project(':libraries:openpgp-api-library')
compile project(':libraries:openkeychain-api-library')
} }
android { android {
compileSdkVersion 19 compileSdkVersion 19
buildToolsVersion "19.0.1" buildToolsVersion "19.0.3"
defaultConfig { defaultConfig {
minSdkVersion 9 minSdkVersion 9

View File

@ -202,7 +202,7 @@ public class OpenPgpProviderActivity extends Activity {
break; break;
} }
case OpenPgpApi.RESULT_CODE_ERROR: { case OpenPgpApi.RESULT_CODE_ERROR: {
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERRORS); OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
handleError(error); handleError(error);
break; break;
} }
@ -234,7 +234,7 @@ public class OpenPgpProviderActivity extends Activity {
} }
public void signAndEncrypt(Intent data) { public void signAndEncrypt(Intent data) {
data.setAction(OpenPgpApi.ACTION_SIGN_AND_ENCTYPT); data.setAction(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(",")); data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);

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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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 { 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 { android {
compileSdkVersion 19 compileSdkVersion 19
buildToolsVersion '19.0.1' buildToolsVersion '19.0.3'
// NOTE: We are using the old folder structure to also support Eclipse // NOTE: We are using the old folder structure to also support Eclipse
sourceSets { 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 *;
#}

View File

@ -0,0 +1,29 @@
#Android specific
bin
gen
obj
lint.xml
local.properties
release.properties
ant.properties
*.class
*.apk
#Gradle
.gradle
build
gradle.properties
#Maven
target
pom.xml.*
#Eclipse
.project
.classpath
.settings
.metadata
#IntelliJ IDEA
.idea
*.iml

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.openintents.openpgp"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="19" />
<application/>
</manifest>

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,35 @@
// please leave this here, so this library builds on its own
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.9.0'
}
}
apply plugin: 'android-library'
android {
compileSdkVersion 19
buildToolsVersion '19.0.3'
// NOTE: We are using the old folder structure to also support Eclipse
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
// Do not abort build if lint finds errors
lintOptions {
abortOnError false
}
}

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 *;
#}

View File

@ -0,0 +1,15 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19
android.library=true

View File

@ -38,18 +38,34 @@ public class OpenPgpSignatureResult implements Parcelable {
return status; return status;
} }
public void setStatus(int status) {
this.status = status;
}
public boolean isSignatureOnly() { public boolean isSignatureOnly() {
return signatureOnly; return signatureOnly;
} }
public void setSignatureOnly(boolean signatureOnly) {
this.signatureOnly = signatureOnly;
}
public String getUserId() { public String getUserId() {
return userId; return userId;
} }
public void setUserId(String userId) {
this.userId = userId;
}
public long getKeyId() { public long getKeyId() {
return keyId; return keyId;
} }
public void setKeyId(long keyId) {
this.keyId = keyId;
}
public OpenPgpSignatureResult() { public OpenPgpSignatureResult() {
} }

View File

@ -16,119 +16,146 @@
package org.openintents.openpgp.util; package org.openintents.openpgp.util;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.util.Log; import android.util.Log;
import org.openintents.openpgp.IOpenPgpService; import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
public class OpenPgpApi { public class OpenPgpApi {
//TODO: fix this documentation
/**
* General extras
* --------------
*
* Intent extras:
* int api_version (required)
* boolean ascii_armor (request ascii armor for ouput)
*
* returned Bundle:
* int result_code (0, 1, or 2 (see OpenPgpApi))
* OpenPgpError error (if result_code == 0)
* Intent intent (if result_code == 2)
*/
/**
* Sign only
*
* optional params:
* String passphrase (for key passphrase)
*/
/**
* Encrypt
*
* Intent extras:
* long[] key_ids
* or
* String[] user_ids (= emails of recipients) (if more than one key has this user_id, a PendingIntent is returned)
*
* optional extras:
* String passphrase (for key passphrase)
*/
/**
* Sign and encrypt
*
* Intent extras:
* same as in encrypt()
*/
/**
* Decrypts and verifies given input bytes. This methods handles encrypted-only, signed-and-encrypted,
* and also signed-only input.
*
* returned Bundle:
* OpenPgpSignatureResult signature_result
*/
/**
* Retrieves key ids based on given user ids (=emails)
*
* Intent extras:
* String[] user_ids
*
* returned Bundle:
* long[] key_ids
*/
public static final String TAG = "OpenPgp API"; public static final String TAG = "OpenPgp API";
public static final int API_VERSION = 2; public static final int API_VERSION = 2;
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService"; 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"; 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"; public static final String ACTION_ENCRYPT = "org.openintents.openpgp.action.ENCRYPT";
public static final String ACTION_SIGN_AND_ENCTYPT = "org.openintents.openpgp.action.SIGN_AND_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"; public static final String ACTION_DECRYPT_VERIFY = "org.openintents.openpgp.action.DECRYPT_VERIFY";
public static final String ACTION_DOWNLOAD_KEYS = "org.openintents.openpgp.action.DOWNLOAD_KEYS";
/**
* 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"; public static final String ACTION_GET_KEY_IDS = "org.openintents.openpgp.action.GET_KEY_IDS";
/* Bundle params */ /**
* 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"; public static final String EXTRA_API_VERSION = "api_version";
// SIGN, ENCRYPT, SIGN_ENCRYPT, DECRYPT_VERIFY // SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
// request ASCII Armor for output // request ASCII Armor for output
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53) // 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"; public static final String EXTRA_REQUEST_ASCII_ARMOR = "ascii_armor";
// ENCRYPT, SIGN_ENCRYPT // ENCRYPT, SIGN_AND_ENCRYPT
public static final String EXTRA_USER_IDS = "user_ids"; public static final String EXTRA_USER_IDS = "user_ids";
public static final String EXTRA_KEY_IDS = "key_ids"; public static final String EXTRA_KEY_IDS = "key_ids";
// optional parameter: // optional extras:
public static final String EXTRA_PASSPHRASE = "passphrase"; public static final String EXTRA_PASSPHRASE = "passphrase";
/* Service Bundle returns */ // GET_KEY
public static final String RESULT_CODE = "result_code"; public static final String EXTRA_KEY_ID = "key_id";
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 /* 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; public static final int RESULT_CODE_ERROR = 0;
// success! // success!
public static final int RESULT_CODE_SUCCESS = 1; public static final int RESULT_CODE_SUCCESS = 1;
// executeServiceMethod intent and do it again with intent // 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 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; IOpenPgpService mService;
Context mContext; Context mContext;
@ -166,6 +193,7 @@ public class OpenPgpApi {
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void executeApiAsync(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) { public void executeApiAsync(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) {
OpenPgpAsyncTask task = new OpenPgpAsyncTask(data, is, os, callback); OpenPgpAsyncTask task = new OpenPgpAsyncTask(data, is, os, callback);
@ -188,13 +216,13 @@ public class OpenPgpApi {
result = mService.execute(data, null, null); result = mService.execute(data, null, null);
return result; return result;
} else { } else {
// send the input and output pfds // pipe the input and output
ParcelFileDescriptor input = ParcelFileDescriptorUtil.pipeFrom(is, ParcelFileDescriptor input = ParcelFileDescriptorUtil.pipeFrom(is,
new ParcelFileDescriptorUtil.IThreadListener() { new ParcelFileDescriptorUtil.IThreadListener() {
@Override @Override
public void onThreadFinished(Thread thread) { public void onThreadFinished(Thread thread) {
Log.d(OpenPgpApi.TAG, "Copy to service finished"); //Log.d(OpenPgpApi.TAG, "Copy to service finished");
} }
}); });
ParcelFileDescriptor output = ParcelFileDescriptorUtil.pipeTo(os, ParcelFileDescriptor output = ParcelFileDescriptorUtil.pipeTo(os,
@ -202,7 +230,7 @@ public class OpenPgpApi {
@Override @Override
public void onThreadFinished(Thread thread) { public void onThreadFinished(Thread thread) {
Log.d(OpenPgpApi.TAG, "Service finished writing!"); //Log.d(OpenPgpApi.TAG, "Service finished writing!");
} }
}); });
@ -222,7 +250,7 @@ public class OpenPgpApi {
Log.e(OpenPgpApi.TAG, "Exception", e); Log.e(OpenPgpApi.TAG, "Exception", e);
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(RESULT_CODE, RESULT_CODE_ERROR); result.putExtra(RESULT_CODE, RESULT_CODE_ERROR);
result.putExtra(RESULT_ERRORS, result.putExtra(RESULT_ERROR,
new OpenPgpError(OpenPgpError.CLIENT_SIDE_ERROR, e.getMessage())); new OpenPgpError(OpenPgpError.CLIENT_SIDE_ERROR, e.getMessage()));
return result; return result;
} }

View File

@ -31,7 +31,7 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListAdapter; import android.widget.ListAdapter;
import android.widget.TextView; import android.widget.TextView;
import org.sufficientlysecure.keychain.api.R; import org.openintents.openpgp.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -1,2 +1,3 @@
include ':example-app' include ':example-app'
include ':libraries:keychain-api-library' include ':libraries:openpgp-api-library'
include ':libraries:openkeychain-api-library'

View File

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

View File

@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sufficientlysecure.keychain" package="org.sufficientlysecure.keychain"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="23101" android:versionCode="23104"
android:versionName="2.3.1 beta1"> android:versionName="2.3.1 beta4">
<!-- <!--
General remarks General remarks
@ -22,7 +22,8 @@
Remarks about the ugly android:pathPattern: Remarks about the ugly android:pathPattern:
- We are matching all files with a specific file ending. - We are matching all files with a specific file ending.
This is done in an ugly way because of Android limitations. 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. for more information.
- Do _not_ set mimeType for gpg! - Do _not_ set mimeType for gpg!
Cyanogenmod's file manager will only show Keychain for gpg files if no mimeType is set! Cyanogenmod's file manager will only show Keychain for gpg files if no mimeType is set!
@ -49,6 +50,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! --> <!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
<application <application
@ -106,30 +108,12 @@
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_recipients" android:label="@string/title_select_recipients"
android:launchMode="singleTop"> 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>
<activity <activity
android:name=".ui.SelectSecretKeyActivity" android:name=".ui.SelectSecretKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_select_secret_key" android:label="@string/title_select_secret_key"
android:launchMode="singleTop"> 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>
<activity <activity
android:name=".ui.EncryptActivity" android:name=".ui.EncryptActivity"
@ -261,7 +245,16 @@
<activity <activity
android:name=".ui.PreferencesActivity" android:name=".ui.PreferencesActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" 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 <activity
android:name=".ui.PreferencesKeyServerActivity" android:name=".ui.PreferencesKeyServerActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
@ -439,10 +432,6 @@
<!--<intent-filter>--> <!--<intent-filter>-->
<!--<action android:name="org.sufficientlysecure.keychain.service.remote.IExtendedApiService" />--> <!--<action android:name="org.sufficientlysecure.keychain.service.remote.IExtendedApiService" />-->
<!--</intent-filter>--> <!--</intent-filter>-->
<!--<meta-data-->
<!--android:name="api_version"-->
<!--android:value="1" />-->
<!--</service>--> <!--</service>-->
<!-- TODO: authority! Make this API with content provider uris --> <!-- TODO: authority! Make this API with content provider uris -->

View File

@ -43,6 +43,8 @@ public final class Constants {
public static final class path { public static final class path {
public static final String APP_DIR = Environment.getExternalStorageDirectory() public static final String APP_DIR = Environment.getExternalStorageDirectory()
+ "/OpenPGP-Keychain"; + "/OpenPGP-Keychain";
public static final String APP_DIR_FILE_SEC = APP_DIR + "/secexport.asc";
public static final String APP_DIR_FILE_PUB = APP_DIR + "/pubexport.asc";
} }
public static final class pref { public static final class pref {
@ -51,7 +53,7 @@ public final class Constants {
public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour"; public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour";
public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression"; public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression";
public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression"; 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 LANGUAGE = "language";
public static final String FORCE_V3_SIGNATURES = "forceV3Signatures"; public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
public static final String KEY_SERVERS = "keyServers"; 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 menu {
public static final class option { 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 create = 0x21070002;
public static final int about = 0x21070003; public static final int about = 0x21070003;
public static final int manage_public_keys = 0x21070004; public static final int manage_public_keys = 0x21070004;
@ -85,12 +85,12 @@ public final class Id {
} }
public static final class dialog { 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 encrypting = 0x21070002;
public static final int decrypting = 0x21070003; public static final int decrypting = 0x21070003;
public static final int new_pass_phrase = 0x21070004; public static final int new_passphrase = 0x21070004;
public static final int pass_phrases_do_not_match = 0x21070005; public static final int passphrases_do_not_match = 0x21070005;
public static final int no_pass_phrase = 0x21070006; public static final int no_passphrase = 0x21070006;
public static final int saving = 0x21070007; public static final int saving = 0x21070007;
public static final int delete_key = 0x21070008; public static final int delete_key = 0x21070008;
public static final int import_keys = 0x21070009; public static final int import_keys = 0x21070009;

View File

@ -59,7 +59,6 @@ public class ClipboardReflection {
* Wrapper around ClipboardManager based on Android version using Reflection API * Wrapper around ClipboardManager based on Android version using Reflection API
* *
* @param context * @param context
* @param text
*/ */
public static CharSequence getClipboardText(Context context) { public static CharSequence getClipboardText(Context context) {
Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE);

View File

@ -56,28 +56,33 @@ public class ActionBarHelper {
* Sets custom view on ActionBar for Done/Cancel activities * Sets custom view on ActionBar for Done/Cancel activities
* *
* @param actionBar * @param actionBar
* @param doneText * @param firstText
* @param doneOnClickListener * @param firstDrawableId
* @param cancelText * @param firstOnClickListener
* @param cancelOnClickListener * @param secondText
* @param secondDrawableId
* @param secondOnClickListener
*/ */
public static void setDoneCancelView(ActionBar actionBar, int doneText, public static void setTwoButtonView(ActionBar actionBar, int firstText, int firstDrawableId,
OnClickListener doneOnClickListener, int cancelText, OnClickListener firstOnClickListener, int secondText, int secondDrawableId,
OnClickListener cancelOnClickListener) { OnClickListener secondOnClickListener) {
// Inflate a "Done"/"Cancel" custom action bar view // Inflate the custom action bar view
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater.inflate( final View customActionBarView = inflater.inflate(
R.layout.actionbar_custom_view_done_cancel, null); R.layout.actionbar_custom_view_done_cancel, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText); TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text));
firstTextView.setText(firstText);
firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0);
customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
doneOnClickListener); firstOnClickListener);
((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text)) TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text));
.setText(cancelText); secondTextView.setText(secondText);
secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0);
customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener( customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
cancelOnClickListener); secondOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title. // Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
@ -91,20 +96,22 @@ public class ActionBarHelper {
* Sets custom view on ActionBar for Done activities * Sets custom view on ActionBar for Done activities
* *
* @param actionBar * @param actionBar
* @param doneText * @param firstText
* @param doneOnClickListener * @param firstOnClickListener
*/ */
public static void setDoneView(ActionBar actionBar, int doneText, public static void setOneButtonView(ActionBar actionBar, int firstText, int firstDrawableId,
OnClickListener doneOnClickListener) { OnClickListener firstOnClickListener) {
// Inflate a "Done" custom action bar view to serve as the "Up" affordance. // Inflate a "Done" custom action bar view to serve as the "Up" affordance.
final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext() final LayoutInflater inflater = (LayoutInflater) actionBar.getThemedContext()
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); .getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
final View customActionBarView = inflater final View customActionBarView = inflater
.inflate(R.layout.actionbar_custom_view_done, null); .inflate(R.layout.actionbar_custom_view_done, null);
((TextView) customActionBarView.findViewById(R.id.actionbar_done_text)).setText(doneText); TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text));
firstTextView.setText(firstText);
firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0);
customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener( customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
doneOnClickListener); firstOnClickListener);
// Show the custom action bar view and hide the normal Home icon and title. // Show the custom action bar view and hide the normal Home icon and title.
actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowTitleEnabled(false);
@ -112,5 +119,4 @@ public class ActionBarHelper {
actionBar.setDisplayShowCustomEnabled(true); actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(customActionBarView); actionBar.setCustomView(customActionBarView);
} }
} }

View File

@ -0,0 +1,41 @@
/*
* 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.sufficientlysecure.keychain.helper;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.util.Patterns;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ContactHelper {
public static final List<String> getMailAccounts(Context context) {
final Account[] accounts = AccountManager.get(context).getAccounts();
final Set<String> emailSet = new HashSet<String>();
for (Account account : accounts) {
if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
emailSet.add(account.name);
}
}
return new ArrayList<String>(emailSet);
}
}

View File

@ -20,7 +20,6 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
@ -63,7 +62,7 @@ public class ExportHelper {
/** /**
* Show dialog where to export keys * Show dialog where to export keys
*/ */
public void showExportKeysDialog(final Uri dataUri, final int keyType, public void showExportKeysDialog(final long[] rowIds, final int keyType,
final String exportFilename) { final String exportFilename) {
mExportFilename = exportFilename; mExportFilename = exportFilename;
@ -75,7 +74,7 @@ public class ExportHelper {
Bundle data = message.getData(); Bundle data = message.getData();
mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
exportKeys(dataUri, keyType); exportKeys(rowIds, keyType);
} }
} }
}; };
@ -86,7 +85,7 @@ public class ExportHelper {
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() { public void run() {
String title = null; String title = null;
if (dataUri == null) { if (rowIds == null) {
// export all keys // export all keys
title = activity.getString(R.string.title_export_keys); title = activity.getString(R.string.title_export_keys);
} else { } else {
@ -112,7 +111,7 @@ public class ExportHelper {
/** /**
* Export keys * Export keys
*/ */
public void exportKeys(Uri dataUri, int keyType) { public void exportKeys(long[] rowIds, int keyType) {
Log.d(Constants.TAG, "exportKeys started"); Log.d(Constants.TAG, "exportKeys started");
// Send all information needed to service to export key in other thread // Send all information needed to service to export key in other thread
@ -126,20 +125,17 @@ public class ExportHelper {
data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType); data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType);
if (dataUri == null) { if (rowIds == null) {
data.putBoolean(KeychainIntentService.EXPORT_ALL, true); data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
} else { } else {
// TODO: put data uri into service??? data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_ROW_ID, rowIds);
long keyRingMasterKeyId = ProviderHelper.getMasterKeyId(activity, dataUri);
data.putLong(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, keyRingMasterKeyId);
} }
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after exporting is done in ApgService // Message is received after exporting is done in ApgService
KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(activity, KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(activity,
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { activity.getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard ApgHandler first
super.handleMessage(message); super.handleMessage(message);
@ -160,7 +156,7 @@ public class ExportHelper {
Toast.makeText(activity, toastMessage, Toast.LENGTH_SHORT).show(); Toast.makeText(activity, toastMessage, Toast.LENGTH_SHORT).show();
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back

View File

@ -17,8 +17,6 @@
package org.sufficientlysecure.keychain.helper; package org.sufficientlysecure.keychain.helper;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator; import java.util.Iterator;
import java.util.Set; import java.util.Set;

View File

@ -144,8 +144,8 @@ public class Preferences {
Constants.defaults.KEY_SERVERS); Constants.defaults.KEY_SERVERS);
Vector<String> servers = new Vector<String>(); Vector<String> servers = new Vector<String>();
String chunks[] = rawData.split(","); String chunks[] = rawData.split(",");
for (int i = 0; i < chunks.length; ++i) { for (String c : chunks) {
String tmp = chunks[i].trim(); String tmp = c.trim();
if (tmp.length() > 0) { if (tmp.length() > 0) {
servers.add(tmp); servers.add(tmp);
} }
@ -156,8 +156,8 @@ public class Preferences {
public void setKeyServers(String[] value) { public void setKeyServers(String[] value) {
SharedPreferences.Editor editor = mSharedPreferences.edit(); SharedPreferences.Editor editor = mSharedPreferences.edit();
String rawData = ""; String rawData = "";
for (int i = 0; i < value.length; ++i) { for (String v : value) {
String tmp = value[i].trim(); String tmp = v.trim();
if (tmp.length() == 0) { if (tmp.length() == 0) {
continue; continue;
} }

View File

@ -78,7 +78,7 @@ public class PgpConversionHelper {
* *
* Singles keys are encoded as keyRings with one single key in it by Bouncy Castle * Singles keys are encoded as keyRings with one single key in it by Bouncy Castle
* *
* @param keysBytes * @param keyBytes
* @return * @return
*/ */
public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) { public static PGPSecretKey BytesToPGPSecretKey(byte[] keyBytes) {
@ -149,7 +149,7 @@ public class PgpConversionHelper {
/** /**
* Convert from PGPSecretKey to byte[] * Convert from PGPSecretKey to byte[]
* *
* @param keysBytes * @param key
* @return * @return
*/ */
public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) { public static byte[] PGPSecretKeyToBytes(PGPSecretKey key) {
@ -165,7 +165,7 @@ public class PgpConversionHelper {
/** /**
* Convert from PGPSecretKeyRing to byte[] * Convert from PGPSecretKeyRing to byte[]
* *
* @param keysBytes * @param keyRing
* @return * @return
*/ */
public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) { public static byte[] PGPSecretKeyRingToBytes(PGPSecretKeyRing keyRing) {

View File

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

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

@ -193,11 +193,10 @@ public class PgpHelper {
* @param context * @param context
* @param progress * @param progress
* @param file * @param file
* @throws FileNotFoundException
* @throws IOException * @throws IOException
*/ */
public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file) public static void deleteFileSecurely(Context context, ProgressDialogUpdater progress, File file)
throws FileNotFoundException, IOException { throws IOException {
long length = file.length(); long length = file.length();
SecureRandom random = new SecureRandom(); SecureRandom random = new SecureRandom();
RandomAccessFile raf = new RandomAccessFile(file, "rws"); RandomAccessFile raf = new RandomAccessFile(file, "rws");

View File

@ -17,11 +17,9 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -29,12 +27,10 @@ import java.util.List;
import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
@ -44,11 +40,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
import org.sufficientlysecure.keychain.util.HkpKeyServer; import org.sufficientlysecure.keychain.util.HkpKeyServer;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException; import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater; import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
import android.content.Context; import android.content.Context;
@ -85,13 +79,14 @@ public class PgpImportExport {
public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) { public boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ArmoredOutputStream aos = new ArmoredOutputStream(bos); ArmoredOutputStream aos = null;
try { try {
aos = new ArmoredOutputStream(bos);
aos.write(keyring.getEncoded()); aos.write(keyring.getEncoded());
aos.close(); aos.close();
String armouredKey = bos.toString("UTF-8"); String armoredKey = bos.toString("UTF-8");
server.add(armouredKey); server.add(armoredKey);
return true; return true;
} catch (IOException e) { } catch (IOException e) {
@ -101,7 +96,8 @@ public class PgpImportExport {
return false; return false;
} finally { } finally {
try { try {
bos.close(); if (aos != null) aos.close();
if (bos != null) bos.close();
} catch (IOException e) { } catch (IOException e) {
} }
} }
@ -161,59 +157,53 @@ public class PgpImportExport {
return returnData; return returnData;
} }
public Bundle exportKeyRings(ArrayList<Long> keyRingMasterKeyIds, int keyType, public Bundle exportKeyRings(ArrayList<Long> keyRingRowIds, int keyType,
OutputStream outStream) throws PgpGeneralException, FileNotFoundException, OutputStream outStream) throws PgpGeneralException,
PGPException, IOException { PGPException, IOException {
Bundle returnData = new Bundle(); Bundle returnData = new Bundle();
int rowIdsSize = keyRingRowIds.size();
updateProgress( updateProgress(
mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
keyRingMasterKeyIds.size()), 0, 100); rowIdsSize), 0, 100);
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
throw new PgpGeneralException( throw new PgpGeneralException(
mContext.getString(R.string.error_external_storage_not_ready)); mContext.getString(R.string.error_external_storage_not_ready));
} }
// For each row id
for (int i = 0; i < rowIdsSize; ++i) {
// Create an output stream
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
// If the keyType is secret get the PGPSecretKeyRing
// based on the row id and encode it to the output
if (keyType == Id.type.secret_key) { if (keyType == Id.type.secret_key) {
ArmoredOutputStream outSec = new ArmoredOutputStream(outStream); updateProgress(i * 100 / rowIdsSize / 2, 100);
outSec.setHeader("Version", PgpHelper.getFullVersion(mContext)); PGPSecretKeyRing secretKeyRing =
ProviderHelper.getPGPSecretKeyRingByRowId(mContext, keyRingRowIds.get(i));
for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) {
updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
mContext, keyRingMasterKeyIds.get(i));
if (secretKeyRing != null) { if (secretKeyRing != null) {
secretKeyRing.encode(outSec); secretKeyRing.encode(arOutStream);
} }
} // Else if it's a public key get the PGPPublicKeyRing
outSec.close(); // and encode that to the output
} else { } else {
// export public keyrings... updateProgress(i * 100 / rowIdsSize, 100);
ArmoredOutputStream outPub = new ArmoredOutputStream(outStream); PGPPublicKeyRing publicKeyRing =
outPub.setHeader("Version", PgpHelper.getFullVersion(mContext)); ProviderHelper.getPGPPublicKeyRingByRowId(mContext, keyRingRowIds.get(i));
for (int i = 0; i < keyRingMasterKeyIds.size(); ++i) {
// double the needed time if exporting both public and secret parts
if (keyType == Id.type.secret_key) {
updateProgress(i * 100 / keyRingMasterKeyIds.size() / 2, 100);
} else {
updateProgress(i * 100 / keyRingMasterKeyIds.size(), 100);
}
PGPPublicKeyRing publicKeyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(
mContext, keyRingMasterKeyIds.get(i));
if (publicKeyRing != null) { if (publicKeyRing != null) {
publicKeyRing.encode(outPub); publicKeyRing.encode(arOutStream);
} }
} }
outPub.close();
}
returnData.putInt(KeychainIntentService.RESULT_EXPORT, keyRingMasterKeyIds.size()); arOutStream.close();
}
returnData.putInt(KeychainIntentService.RESULT_EXPORT, rowIdsSize);
updateProgress(R.string.progress_done, 100, 100); updateProgress(R.string.progress_done, 100, 100);
@ -234,7 +224,7 @@ public class PgpImportExport {
for (PGPSecretKey testSecretKey : new IterableIterator<PGPSecretKey>( for (PGPSecretKey testSecretKey : new IterableIterator<PGPSecretKey>(
secretKeyRing.getSecretKeys())) { secretKeyRing.getSecretKeys())) {
if (!testSecretKey.isMasterKey()) { if (!testSecretKey.isMasterKey()) {
if (PgpKeyHelper.isSecretKeyPrivateEmpty(testSecretKey)) { if (testSecretKey.isPrivateKeyEmpty()) {
// this is bad, something is very wrong... // this is bad, something is very wrong...
save = false; save = false;
status = Id.return_value.bad; status = Id.return_value.bad;

View File

@ -32,6 +32,7 @@ import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature; import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector; import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
@ -421,7 +422,6 @@ public class PgpKeyHelper {
algorithmStr = "RSA"; algorithmStr = "RSA";
break; break;
} }
case PGPPublicKey.DSA: { case PGPPublicKey.DSA: {
algorithmStr = "DSA"; algorithmStr = "DSA";
break; break;
@ -444,31 +444,6 @@ public class PgpKeyHelper {
return algorithmStr; return algorithmStr;
} }
/**
* Converts fingerprint to hex with whitespaces after 4 characters
*
* @param fp
* @return
*/
public static String convertFingerprintToHex(byte[] fp, boolean chunked) {
String fingerPrint = "";
for (int i = 0; i < fp.length; ++i) {
if (chunked && i != 0 && i % 10 == 0) {
fingerPrint += " ";
} else if (chunked && i != 0 && i % 2 == 0) {
fingerPrint += " ";
}
String chunk = Integer.toHexString((fp[i] + 256) % 256).toUpperCase(Locale.US);
while (chunk.length() < 2) {
chunk = "0" + chunk;
}
fingerPrint += chunk;
}
return fingerPrint;
}
public static String getFingerPrint(Context context, long keyId) { public static String getFingerPrint(Context context, long keyId) {
PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId); PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId);
// if it is no public key get it from your own keys... // if it is no public key get it from your own keys...
@ -484,41 +459,57 @@ public class PgpKeyHelper {
return convertFingerprintToHex(key.getFingerprint(), true); return convertFingerprintToHex(key.getFingerprint(), true);
} }
public static boolean isSecretKeyPrivateEmpty(PGPSecretKey secretKey) { /**
return secretKey.isPrivateKeyEmpty(); * Converts fingerprint to hex (optional: with whitespaces after 4 characters)
* <p/>
* Fingerprint is shown using lowercase characters. Studies have shown that humans can
* better differentiate between numbers and letters when letters are lowercase.
*
* @param fingerprint
* @param split split into 4 character chunks
* @return
*/
public static String convertFingerprintToHex(byte[] fingerprint, boolean split) {
String hexString = Hex.toHexString(fingerprint);
if (split) {
hexString = hexString.replaceAll("(.{4})(?!$)", "$1 ");
} }
// public static boolean isSecretKeyPrivateEmpty(Context context, long keyId) { return hexString;
// PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
// if (secretKey == null) {
// Log.e(Constants.TAG, "Key could not be found!");
// return false; // could be a public key, assume it is not empty
// }
// return isSecretKeyPrivateEmpty(secretKey);
// }
public static String convertKeyIdToHex(long keyId) {
String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(Locale.US);
while (fingerPrint.length() < 8) {
fingerPrint = "0" + fingerPrint;
}
return fingerPrint;
} }
/** /**
* TODO: documentation * Convert key id from long to 64 bit hex string
* <p/>
* V4: "The Key ID is the low-order 64 bits of the fingerprint"
* <p/>
* see http://tools.ietf.org/html/rfc4880#section-12.2
* *
* @param keyId * @param keyId
* @return * @return
*/ */
public static String convertKeyToHex(long keyId) { public static String convertKeyIdToHex(long keyId) {
return convertKeyIdToHex(keyId >> 32) + convertKeyIdToHex(keyId); return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId);
} }
public static long convertHexToKeyId(String data) { private static String convertKeyIdToHex32bit(long keyId) {
int len = data.length(); String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US);
String s2 = data.substring(len - 8); while (hexString.length() < 8) {
String s1 = data.substring(0, len - 8); hexString = "0" + hexString;
}
return hexString;
}
/**
* Used in HkpKeyServer to convert hex encoded key ids back to long.
*
* @param hexString
* @return
*/
public static long convertHexToKeyId(String hexString) {
int len = hexString.length();
String s2 = hexString.substring(len - 8);
String s1 = hexString.substring(0, len - 8);
return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16);
} }
@ -550,7 +541,6 @@ public class PgpKeyHelper {
result[0] = matcher.group(1); result[0] = matcher.group(1);
result[1] = matcher.group(3); result[1] = matcher.group(3);
result[2] = matcher.group(2); result[2] = matcher.group(2);
return result;
} }
return result; return result;

View File

@ -107,7 +107,7 @@ public class PgpKeyOperation {
* *
* @param algorithmChoice * @param algorithmChoice
* @param keySize * @param keySize
* @param passPhrase * @param passphrase
* @param isMasterKey * @param isMasterKey
* @return * @return
* @throws NoSuchAlgorithmException * @throws NoSuchAlgorithmException
@ -118,7 +118,7 @@ public class PgpKeyOperation {
*/ */
// TODO: key flags? // TODO: key flags?
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passPhrase, public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException, boolean isMasterKey) throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
PgpGeneralException, InvalidAlgorithmParameterException { PgpGeneralException, InvalidAlgorithmParameterException {
@ -126,8 +126,8 @@ public class PgpKeyOperation {
throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit)); throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit));
} }
if (passPhrase == null) { if (passphrase == null) {
passPhrase = ""; passphrase = "";
} }
int algorithm = 0; int algorithm = 0;
@ -181,7 +181,7 @@ public class PgpKeyOperation {
// Build key encrypter and decrypter based on passphrase // Build key encrypter and decrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc) PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passPhrase.toCharArray()); .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
PGPSecretKey secKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), PGPSecretKey secKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor); sha1Calc, isMasterKey, keyEncryptor);
@ -190,7 +190,7 @@ public class PgpKeyOperation {
} }
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase, public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
String newPassPhrase) throws IOException, PGPException, PGPException, String newPassPhrase) throws IOException, PGPException,
NoSuchProviderException { NoSuchProviderException {
updateProgress(R.string.progress_building_key, 0, 100); updateProgress(R.string.progress_building_key, 0, 100);

View File

@ -342,7 +342,7 @@ public class KeychainProvider extends ContentProvider {
/** /**
* Returns type of the query (secret/public) * Returns type of the query (secret/public)
* *
* @param uri * @param match
* @return * @return
*/ */
private int getKeyType(int match) { private int getKeyType(int match) {
@ -1039,7 +1039,8 @@ public class KeychainProvider extends ContentProvider {
* Build default selection statement for KeyRings. If no extra selection is specified only build * Build default selection statement for KeyRings. If no extra selection is specified only build
* where clause with rowId * where clause with rowId
* *
* @param uri * @param defaultSelection
* @param keyType
* @param selection * @param selection
* @return * @return
*/ */

View File

@ -100,7 +100,7 @@ public class ProviderHelper {
} }
/** /**
* Retrieves the actual PGPPublicKeyRing object from the database blob based on the maserKeyId * Retrieves the actual PGPPublicKeyRing object from the database blob based on the masterKeyId
*/ */
public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context, public static PGPPublicKeyRing getPGPPublicKeyRingByMasterKeyId(Context context,
long masterKeyId) { long masterKeyId) {
@ -123,11 +123,8 @@ public class ProviderHelper {
*/ */
public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) { public static PGPPublicKey getPGPPublicKeyByKeyId(Context context, long keyId) {
PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId); PGPPublicKeyRing keyRing = getPGPPublicKeyRingByKeyId(context, keyId);
if (keyRing == null) {
return null;
}
return keyRing.getPublicKey(keyId); return (keyRing == null)? null : keyRing.getPublicKey(keyId);
} }
/** /**
@ -162,11 +159,8 @@ public class ProviderHelper {
*/ */
public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) { public static PGPSecretKey getPGPSecretKeyByKeyId(Context context, long keyId) {
PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId); PGPSecretKeyRing keyRing = getPGPSecretKeyRingByKeyId(context, keyId);
if (keyRing == null) {
return null;
}
return keyRing.getSecretKey(keyId); return (keyRing == null) ? null : keyRing.getSecretKey(keyId);
} }
/** /**
@ -411,10 +405,10 @@ public class ProviderHelper {
long keyRingRowId, PGPSecretKey key, int rank) throws IOException { long keyRingRowId, PGPSecretKey key, int rank) throws IOException {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
boolean has_private = true; boolean hasPrivate = true;
if (key.isMasterKey()) { if (key.isMasterKey()) {
if (PgpKeyHelper.isSecretKeyPrivateEmpty(key)) { if (key.isPrivateKeyEmpty()) {
has_private = false; hasPrivate = false;
} }
} }
@ -422,8 +416,8 @@ public class ProviderHelper {
values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); values.put(Keys.IS_MASTER_KEY, key.isMasterKey());
values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm()); values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm());
values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength()); values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength());
values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && has_private)); values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key) && hasPrivate));
values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && has_private)); values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key) && hasPrivate));
values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key)); values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key));
values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked());
values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000); values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000);
@ -480,6 +474,30 @@ public class ProviderHelper {
return masterKeyIds; return masterKeyIds;
} }
/**
* Private helper method
*/
private static ArrayList<Long> getKeyRingsRowIds(Context context, Uri queryUri) {
Cursor cursor = context.getContentResolver().query(queryUri,
new String[]{KeyRings._ID}, null, null, null);
ArrayList<Long> rowIds = new ArrayList<Long>();
if (cursor != null) {
int IdCol = cursor.getColumnIndex(KeyRings._ID);
if (cursor.moveToFirst()) {
do {
rowIds.add(cursor.getLong(IdCol));
} while (cursor.moveToNext());
}
}
if (cursor != null) {
cursor.close();
}
return rowIds;
}
/** /**
* Retrieves ids of all SecretKeyRings * Retrieves ids of all SecretKeyRings
*/ */
@ -496,6 +514,22 @@ public class ProviderHelper {
return getKeyRingsMasterKeyIds(context, queryUri); return getKeyRingsMasterKeyIds(context, queryUri);
} }
/**
* Retrieves ids of all SecretKeyRings
*/
public static ArrayList<Long> getSecretKeyRingsRowIds(Context context) {
Uri queryUri = KeyRings.buildSecretKeyRingsUri();
return getKeyRingsRowIds(context, queryUri);
}
/**
* Retrieves ids of all PublicKeyRings
*/
public static ArrayList<Long> getPublicKeyRingsRowIds(Context context) {
Uri queryUri = KeyRings.buildPublicKeyRingsUri();
return getKeyRingsRowIds(context, queryUri);
}
public static void deletePublicKeyRing(Context context, long rowId) { public static void deletePublicKeyRing(Context context, long rowId) {
ContentResolver cr = context.getContentResolver(); ContentResolver cr = context.getContentResolver();
cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null); cr.delete(KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)), null, null);
@ -722,7 +756,7 @@ public class ProviderHelper {
String armoredKey = bos.toString("UTF-8"); String armoredKey = bos.toString("UTF-8");
Log.d(Constants.TAG, "armouredKey:" + armoredKey); Log.d(Constants.TAG, "armoredKey:" + armoredKey);
output.add(armoredKey); output.add(armoredKey);
} catch (IOException e) { } catch (IOException e) {

View File

@ -44,11 +44,13 @@ import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpImportExport; import org.sufficientlysecure.keychain.pgp.PgpImportExport;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream; import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
@ -152,6 +154,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
public static final String EXPORT_KEY_TYPE = "export_key_type"; public static final String EXPORT_KEY_TYPE = "export_key_type";
public static final String EXPORT_ALL = "export_all"; public static final String EXPORT_ALL = "export_all";
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
public static final String EXPORT_KEY_RING_ROW_ID = "export_key_rind_row_id";
// upload key // upload key
public static final String UPLOAD_KEY_SERVER = "upload_key_server"; public static final String UPLOAD_KEY_SERVER = "upload_key_server";
@ -181,13 +184,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// decrypt/verify // decrypt/verify
public static final String RESULT_DECRYPTED_STRING = "decrypted_message"; public static final String RESULT_DECRYPTED_STRING = "decrypted_message";
public static final String RESULT_DECRYPTED_BYTES = "decrypted_data"; public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
public static final String RESULT_SIGNATURE = "signature"; public static final String RESULT_DECRYPT_VERIFY_RESULT = "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";
// import // import
public static final String RESULT_IMPORT_ADDED = "added"; public static final String RESULT_IMPORT_ADDED = "added";
@ -206,7 +203,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
private boolean mIsCanceled; private boolean mIsCanceled;
public KeychainIntentService() { public KeychainIntentService() {
super("ApgService"); super("KeychainIntentService");
} }
@Override @Override
@ -489,15 +486,17 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// verifyText and decrypt returning additional resultData values for the // verifyText and decrypt returning additional resultData values for the
// verification of signatures // verification of signatures
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream); PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
builder.progress(this); builder.progressDialogUpdater(this);
builder.assumeSymmetric(assumeSymmetricEncryption) builder.assumeSymmetric(assumeSymmetricEncryption)
.passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId)); .passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
resultData = builder.build().execute(); PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
outStream.close(); outStream.close();
resultData.putParcelable(RESULT_DECRYPT_VERIFY_RESULT, decryptVerifyResult);
/* Output */ /* Output */
switch (target) { switch (target) {
@ -599,13 +598,23 @@ public class KeychainIntentService extends IntentService implements ProgressDial
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE); String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
/* Operation */ /* Operation */
int keysTotal = 2;
int keysCreated = 0;
setProgress(
getApplicationContext().getResources().getQuantityString(R.plurals.progress_generating, keysTotal),
keysCreated,
keysTotal);
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this); PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa, PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa,
4096, passphrase, true); 4096, passphrase, true);
keysCreated++;
setProgress(keysCreated, keysTotal);
PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa, PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
4096, passphrase, false); 4096, passphrase, false);
keysCreated++;
setProgress(keysCreated, keysTotal);
// TODO: default to one master for cert, one sub for encrypt and one sub // TODO: default to one master for cert, one sub for encrypt and one sub
// for sign // for sign
@ -668,10 +677,12 @@ public class KeychainIntentService extends IntentService implements ProgressDial
String outputFile = data.getString(EXPORT_FILENAME); String outputFile = data.getString(EXPORT_FILENAME);
long[] rowIds = new long[0];
// If not exporting all keys get the rowIds of the keys to export from the intent
boolean exportAll = data.getBoolean(EXPORT_ALL); boolean exportAll = data.getBoolean(EXPORT_ALL);
long keyRingMasterKeyId = -1;
if (!exportAll) { if (!exportAll) {
keyRingMasterKeyId = data.getLong(EXPORT_KEY_RING_MASTER_KEY_ID); rowIds = data.getLongArray(EXPORT_KEY_RING_ROW_ID);
} }
/* Operation */ /* Operation */
@ -684,24 +695,26 @@ public class KeychainIntentService extends IntentService implements ProgressDial
// OutputStream // OutputStream
FileOutputStream outStream = new FileOutputStream(outputFile); FileOutputStream outStream = new FileOutputStream(outputFile);
ArrayList<Long> keyRingMasterKeyIds = new ArrayList<Long>(); ArrayList<Long> keyRingRowIds = new ArrayList<Long>();
if (exportAll) { if (exportAll) {
// get all key ring row ids based on export type // get all key ring row ids based on export type
if (keyType == Id.type.public_key) { if (keyType == Id.type.public_key) {
keyRingMasterKeyIds = ProviderHelper.getPublicKeyRingsMasterKeyIds(this); keyRingRowIds = ProviderHelper.getPublicKeyRingsRowIds(this);
} else { } else {
keyRingMasterKeyIds = ProviderHelper.getSecretKeyRingsMasterKeyIds(this); keyRingRowIds = ProviderHelper.getSecretKeyRingsRowIds(this);
} }
} else { } else {
keyRingMasterKeyIds.add(keyRingMasterKeyId); for(long rowId : rowIds) {
keyRingRowIds.add(rowId);
}
} }
Bundle resultData = new Bundle(); Bundle resultData;
PgpImportExport pgpImportExport = new PgpImportExport(this, this); PgpImportExport pgpImportExport = new PgpImportExport(this, this);
resultData = pgpImportExport resultData = pgpImportExport
.exportKeyRings(keyRingMasterKeyIds, keyType, outStream); .exportKeyRings(keyRingRowIds, keyType, outStream);
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData); sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) { } catch (Exception e) {
@ -751,7 +764,7 @@ public class KeychainIntentService extends IntentService implements ProgressDial
*/ */
// need to have access to the bufferedInput, so we can reuse it for the possible // 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 // PGPObject chunks after the first one, e.g. files with several consecutive ASCII
// armour blocks // armor blocks
BufferedInputStream bufferedInput = new BufferedInputStream(new ByteArrayInputStream(downloadedKey)); BufferedInputStream bufferedInput = new BufferedInputStream(new ByteArrayInputStream(downloadedKey));
try { try {
@ -867,10 +880,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) { 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); + max);
Bundle data = new Bundle(); Bundle data = new Bundle();

View File

@ -21,7 +21,6 @@ import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import android.app.Activity; import android.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnCancelListener;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -51,21 +50,26 @@ public class KeychainIntentServiceHandler extends Handler {
this.mActivity = activity; this.mActivity = activity;
} }
public KeychainIntentServiceHandler(Activity activity, ProgressDialogFragment progressDialogFragment) { public KeychainIntentServiceHandler(Activity activity,
ProgressDialogFragment progressDialogFragment) {
this.mActivity = activity; this.mActivity = activity;
this.mProgressDialogFragment = progressDialogFragment; this.mProgressDialogFragment = progressDialogFragment;
} }
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, int progressDialogStyle) { public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage,
this(activity, progressDialogMessageId, progressDialogStyle, false, null); int progressDialogStyle) {
this(activity, progressDialogMessage, progressDialogStyle, false, null);
} }
public KeychainIntentServiceHandler(Activity activity, int progressDialogMessageId, public KeychainIntentServiceHandler(Activity activity, String progressDialogMessage,
int progressDialogStyle, boolean cancelable, int progressDialogStyle, boolean cancelable,
OnCancelListener onCancelListener) { OnCancelListener onCancelListener) {
this.mActivity = activity; this.mActivity = activity;
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(progressDialogMessageId, this.mProgressDialogFragment = ProgressDialogFragment.newInstance(
progressDialogStyle, cancelable, onCancelListener); progressDialogMessage,
progressDialogStyle,
cancelable,
onCancelListener);
} }
public void showProgressDialog(FragmentActivity activity) { public void showProgressDialog(FragmentActivity activity) {

View File

@ -21,6 +21,7 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import android.util.LongSparseArray;
import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey; import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKey;
@ -77,7 +78,7 @@ public class PassphraseCacheService extends Service {
private BroadcastReceiver mIntentReceiver; private BroadcastReceiver mIntentReceiver;
private HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>(); private LongSparseArray<String> mPassphraseCache = new LongSparseArray<String>();
Context mContext; Context mContext;
@ -347,7 +348,7 @@ public class PassphraseCacheService extends Service {
Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!"); Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!");
// stop whole service if no cached passphrases remaining // stop whole service if no cached passphrases remaining
if (mPassphraseCache.isEmpty()) { if (mPassphraseCache.size() == 0) {
Log.d(TAG, "No passphrases remaining in memory, stopping service!"); Log.d(TAG, "No passphrases remaining in memory, stopping service!");
stopSelf(); stopSelf();
} }

View File

@ -41,7 +41,7 @@ public class AppSettingsActivity extends ActionBarActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Inflate a "Done" custom action bar // Inflate a "Done" custom action bar
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.api_settings_save, ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.api_settings_save, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {

View File

@ -21,7 +21,6 @@ import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
@ -33,9 +32,11 @@ import org.spongycastle.util.Arrays;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract; 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.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
@ -103,9 +104,8 @@ public class OpenPgpService extends RemoteService {
// return PendingIntent to be executed by client // return PendingIntent to be executed by client
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return result; return result;
} }
@ -114,8 +114,8 @@ public class OpenPgpService extends RemoteService {
} }
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
result.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIdsArray); result.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIdsArray);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
return result; return result;
} }
@ -130,9 +130,8 @@ public class OpenPgpService extends RemoteService {
// return PendingIntent to be executed by client // return PendingIntent to be executed by client
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return result; return result;
} }
@ -179,9 +178,9 @@ public class OpenPgpService extends RemoteService {
return result; return result;
} catch (Exception e) { } catch (Exception e) {
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_ERROR,
result.putExtra(OpenPgpApi.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} }
} }
@ -208,9 +207,9 @@ public class OpenPgpService extends RemoteService {
} }
} else { } else {
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_ERROR,
result.putExtra(OpenPgpApi.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, "Missing parameter user_ids or key_ids!")); new OpenPgpError(OpenPgpError.GENERIC_ERROR, "Missing parameter user_ids or key_ids!"));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} }
@ -267,9 +266,9 @@ public class OpenPgpService extends RemoteService {
return result; return result;
} catch (Exception e) { } catch (Exception e) {
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_ERROR,
result.putExtra(OpenPgpApi.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} }
} }
@ -284,98 +283,30 @@ public class OpenPgpService extends RemoteService {
Intent result = new Intent(); Intent result = new Intent();
try { try {
// TODO: String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
// 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: put this code into PgpDecryptVerify class
// 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 (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
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
return passphraseBundle;
}
long inputLength = is.available(); long inputLength = is.available();
InputData inputData = new InputData(is, inputLength); InputData inputData = new InputData(is, inputLength);
Bundle outputBundle;
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os); PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
builder.assumeSymmetric(false) // no support for symmetric encryption
builder.assumeSymmetric(false) .enforcedKeyId(appSettings.getKeyId()) // allow only the private key for this app for decryption
.passphrase(passphrase); .passphrase(passphrase);
// TODO: this also decrypts with other secret keys that have no passphrase!!! // TODO: currently does not support binary signed-only content
outputBundle = builder.build().execute(); PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
//TODO: instead of using all these wrapping use OpenPgpSignatureResult directly if (decryptVerifyResult.isKeyPassphraseNeeded()) {
// in DecryptVerify class and then in DecryptActivity // get PendingIntent for passphrase input, add it to given params and return to client
boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE, false); Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
if (signature) { return passphraseBundle;
long signatureKeyId = outputBundle } else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) {
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID, 0); throw new PgpGeneralException("Decryption of symmetric content not supported by API!");
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; OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
if (signatureSuccess) { if (signatureResult != null) {
signatureStatus = OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED; if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY) {
} else if (signatureUnknown) { // If signature is unknown we return an _additional_ PendingIntent
signatureStatus = OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY;
// If signature is unknown we return an additional PendingIntent
// to retrieve the missing key // to retrieve the missing key
// TODO!!! // TODO!!!
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class); Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
@ -389,11 +320,9 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
} }
result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
OpenPgpSignatureResult sigResult = new OpenPgpSignatureResult(signatureStatus,
signatureUserId, signatureOnly, signatureKeyId);
result.putExtra(OpenPgpApi.RESULT_SIGNATURE, sigResult);
} }
} finally { } finally {
is.close(); is.close();
os.close(); os.close();
@ -403,9 +332,44 @@ public class OpenPgpService extends RemoteService {
return result; return result;
} catch (Exception e) { } catch (Exception e) {
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_ERROR,
result.putExtra(OpenPgpApi.RESULT_ERRORS,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
}
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; return result;
} }
} }
@ -431,7 +395,7 @@ public class OpenPgpService extends RemoteService {
if (data == null) { if (data == null) {
Intent result = new Intent(); Intent result = new Intent();
OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!"); OpenPgpError error = new OpenPgpError(OpenPgpError.GENERIC_ERROR, "params Bundle required!");
result.putExtra(OpenPgpApi.RESULT_ERRORS, error); result.putExtra(OpenPgpApi.RESULT_ERROR, error);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} }
@ -440,7 +404,7 @@ public class OpenPgpService extends RemoteService {
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) { if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) {
Intent result = new Intent(); Intent result = new Intent();
OpenPgpError error = new OpenPgpError(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!"); OpenPgpError error = new OpenPgpError(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!");
result.putExtra(OpenPgpApi.RESULT_ERRORS, error); result.putExtra(OpenPgpApi.RESULT_ERROR, error);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result; return result;
} }
@ -471,13 +435,12 @@ public class OpenPgpService extends RemoteService {
return signImpl(data, input, output, appSettings); return signImpl(data, input, output, appSettings);
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) { } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, appSettings, false); return encryptAndSignImpl(data, input, output, appSettings, false);
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCTYPT.equals(action)) { } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, appSettings, true); return encryptAndSignImpl(data, input, output, appSettings, true);
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) { } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
return decryptAndVerifyImpl(data, input, output, appSettings); return decryptAndVerifyImpl(data, input, output, appSettings);
} else if (OpenPgpApi.ACTION_DOWNLOAD_KEYS.equals(action)) { } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
// TODO! return getKeyImpl(data);
return null;
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) { } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
return getKeyIdsImpl(data); return getKeyIdsImpl(data);
} else { } else {

View File

@ -72,7 +72,7 @@ public abstract class RemoteService extends Service {
// return error // return error
Intent result = new Intent(); Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
result.putExtra(OpenPgpApi.RESULT_ERRORS, result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage())); new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
return result; return result;
} }

View File

@ -88,7 +88,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.api_register_allow, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -108,7 +108,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
RemoteServiceActivity.this.finish(); RemoteServiceActivity.this.finish();
} }
} }
}, R.string.api_register_disallow, new View.OnClickListener() { }, R.string.api_register_disallow, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// Disallow // Disallow
@ -161,7 +161,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
} }
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -173,7 +173,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
RemoteServiceActivity.this.setResult(RESULT_OK, resultData); RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish(); RemoteServiceActivity.this.finish();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// cancel // cancel
@ -214,7 +214,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
String text = "<font color=\"red\">" + errorMessage + "</font>"; String text = "<font color=\"red\">" + errorMessage + "</font>";
// Inflate a "Done" custom action bar view // Inflate a "Done" custom action bar view
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override

View File

@ -230,7 +230,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
// Message is received after signing is done in ApgService // Message is received after signing is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_signing, ProgressDialog.STYLE_SPINNER) { getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard ApgHandler first
super.handleMessage(message); super.handleMessage(message);
@ -249,7 +249,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
finish(); finish();
} }
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
@ -283,7 +283,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
// Message is received after uploading is done in ApgService // Message is received after uploading is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard ApgHandler first
super.handleMessage(message); super.handleMessage(message);
@ -295,7 +295,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
setResult(RESULT_OK); setResult(RESULT_OK);
finish(); finish();
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back

View File

@ -25,6 +25,7 @@ import java.io.FileNotFoundException;
import java.io.InputStream; import java.io.InputStream;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.openpgp.PGPPublicKeyRing; import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
@ -32,6 +33,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
@ -528,6 +530,10 @@ public class DecryptActivity extends DrawerActivity {
Log.e(Constants.TAG, "File not found!", e); Log.e(Constants.TAG, "File not found!", e);
AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()), AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
AppMsg.STYLE_ALERT).show(); AppMsg.STYLE_ALERT).show();
} finally {
try {
if (inStream != null) inStream.close();
} catch (Exception e){ }
} }
} else { } else {
inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes()); inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes());
@ -644,7 +650,7 @@ public class DecryptActivity extends DrawerActivity {
// Message is received after encrypting is done in ApgService // Message is received after encrypting is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_decrypting, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard ApgHandler first
super.handleMessage(message); super.handleMessage(message);
@ -690,11 +696,15 @@ public class DecryptActivity extends DrawerActivity {
} }
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE)) { PgpDecryptVerifyResult decryptVerifyResult =
String userId = returnData returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT);
.getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
mSignatureKeyId = returnData OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
.getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
if (signatureResult != null) {
String userId = signatureResult.getUserId();
mSignatureKeyId = signatureResult.getKeyId();
mUserIdRest.setText("id: " mUserIdRest.setText("id: "
+ PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId)); + PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId));
if (userId == null) { if (userId == null) {
@ -707,26 +717,37 @@ public class DecryptActivity extends DrawerActivity {
} }
mUserId.setText(userId); mUserId.setText(userId);
if (returnData.getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS)) { switch (signatureResult.getStatus()) {
case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: {
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
mLookupKey.setVisibility(View.GONE); mLookupKey.setVisibility(View.GONE);
} else if (returnData break;
.getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) { }
// TODO!
// case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: {
// break;
// }
case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY: {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error); mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
mLookupKey.setVisibility(View.VISIBLE); mLookupKey.setVisibility(View.VISIBLE);
AppMsg.makeText(DecryptActivity.this, AppMsg.makeText(DecryptActivity.this,
R.string.unknown_signature, R.string.unknown_signature,
AppMsg.STYLE_ALERT).show(); AppMsg.STYLE_ALERT).show();
} else { break;
}
default: {
mSignatureStatusImage.setImageResource(R.drawable.overlay_error); mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
mLookupKey.setVisibility(View.GONE); mLookupKey.setVisibility(View.GONE);
break;
}
} }
mSignatureLayout.setVisibility(View.VISIBLE); mSignatureLayout.setVisibility(View.VISIBLE);
} }
} }
} }
;
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back

View File

@ -92,12 +92,12 @@ public class EditKeyActivity extends ActionBarActivity {
private SectionView mUserIdsView; private SectionView mUserIdsView;
private SectionView mKeysView; private SectionView mKeysView;
private String mCurrentPassPhrase = null; private String mCurrentPassphrase = null;
private String mNewPassPhrase = null; private String mNewPassPhrase = null;
private String mSavedNewPassPhrase = null; private String mSavedNewPassPhrase = null;
private boolean mIsPassPhraseSet; private boolean mIsPassPhraseSet;
private BootstrapButton mChangePassPhrase; private BootstrapButton mChangePassphrase;
private CheckBox mNoPassphrase; private CheckBox mNoPassphrase;
@ -134,14 +134,14 @@ public class EditKeyActivity extends ActionBarActivity {
* @param intent * @param intent
*/ */
private void handleActionCreateKey(Intent intent) { private void handleActionCreateKey(Intent intent) {
// Inflate a "Done"/"Cancel" custom action bar // Inflate a "Save"/"Cancel" custom action bar
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_save, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
saveClicked(); saveClicked();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
cancelClicked(); cancelClicked();
@ -150,7 +150,7 @@ public class EditKeyActivity extends ActionBarActivity {
Bundle extras = intent.getExtras(); Bundle extras = intent.getExtras();
mCurrentPassPhrase = ""; mCurrentPassphrase = "";
if (extras != null) { if (extras != null) {
// if userId is given, prefill the fields // if userId is given, prefill the fields
@ -165,7 +165,7 @@ public class EditKeyActivity extends ActionBarActivity {
if (noPassphrase) { if (noPassphrase) {
// check "no passphrase" checkbox and remove button // check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true); mNoPassphrase.setChecked(true);
mChangePassPhrase.setVisibility(View.GONE); mChangePassphrase.setVisibility(View.GONE);
} }
} }
@ -181,13 +181,14 @@ public class EditKeyActivity extends ActionBarActivity {
// fill values for this action // fill values for this action
Bundle data = new Bundle(); Bundle data = new Bundle();
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE,
mCurrentPassPhrase); mCurrentPassphrase);
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data); serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after generating is done in ApgService // Message is received after generating is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler( KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
this, R.string.progress_generating, ProgressDialog.STYLE_SPINNER, true, this, getResources().getQuantityString(R.plurals.progress_generating, 1),
ProgressDialog.STYLE_HORIZONTAL, true,
new DialogInterface.OnCancelListener() { new DialogInterface.OnCancelListener() {
@Override @Override
@ -224,7 +225,7 @@ public class EditKeyActivity extends ActionBarActivity {
buildLayout(); buildLayout();
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
@ -248,8 +249,8 @@ public class EditKeyActivity extends ActionBarActivity {
* @param intent * @param intent
*/ */
private void handleActionEditKey(Intent intent) { private void handleActionEditKey(Intent intent) {
// Inflate a "Done"/"Cancel" custom action bar // Inflate a "Save"/"Cancel" custom action bar
ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_save, ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -279,9 +280,9 @@ public class EditKeyActivity extends ActionBarActivity {
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
String passPhrase = PassphraseCacheService.getCachedPassphrase( String passphrase = PassphraseCacheService.getCachedPassphrase(
EditKeyActivity.this, masterKeyId); EditKeyActivity.this, masterKeyId);
mCurrentPassPhrase = passPhrase; mCurrentPassphrase = passphrase;
finallySaveClicked(); finallySaveClicked();
} }
} }
@ -326,8 +327,8 @@ public class EditKeyActivity extends ActionBarActivity {
cancelClicked(); cancelClicked();
return true; return true;
case R.id.menu_key_edit_export_file: case R.id.menu_key_edit_export_file:
mExportHelper.showExportKeysDialog(mDataUri, Id.type.secret_key, Constants.path.APP_DIR long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())};
+ "/secexport.asc"); mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.path.APP_DIR_FILE_SEC);
return true; return true;
case R.id.menu_key_edit_delete: { case R.id.menu_key_edit_delete: {
// Message is received after key is deleted // Message is received after key is deleted
@ -371,14 +372,14 @@ public class EditKeyActivity extends ActionBarActivity {
} }
} }
mCurrentPassPhrase = ""; mCurrentPassphrase = "";
buildLayout(); buildLayout();
mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId); mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
if (!mIsPassPhraseSet) { if (!mIsPassPhraseSet) {
// check "no passphrase" checkbox and remove button // check "no passphrase" checkbox and remove button
mNoPassphrase.setChecked(true); mNoPassphrase.setChecked(true);
mChangePassPhrase.setVisibility(View.GONE); mChangePassphrase.setVisibility(View.GONE);
} }
} }
@ -408,7 +409,7 @@ public class EditKeyActivity extends ActionBarActivity {
// set title based on isPassphraseSet() // set title based on isPassphraseSet()
int title = -1; int title = -1;
if (isPassphraseSet()) { if (isPassphraseSet()) {
title = R.string.title_change_pass_phrase; title = R.string.title_change_passphrase;
} else { } else {
title = R.string.title_set_passphrase; title = R.string.title_set_passphrase;
} }
@ -427,7 +428,7 @@ public class EditKeyActivity extends ActionBarActivity {
setContentView(R.layout.edit_key_activity); setContentView(R.layout.edit_key_activity);
// find views // 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); mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
// Build layout based on given userIds and keys // Build layout based on given userIds and keys
@ -447,7 +448,7 @@ public class EditKeyActivity extends ActionBarActivity {
updatePassPhraseButtonText(); updatePassPhraseButtonText();
mChangePassPhrase.setOnClickListener(new OnClickListener() { mChangePassphrase.setOnClickListener(new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
showSetPassphraseDialog(); showSetPassphraseDialog();
} }
@ -462,10 +463,10 @@ public class EditKeyActivity extends ActionBarActivity {
// remove passphrase // remove passphrase
mSavedNewPassPhrase = mNewPassPhrase; mSavedNewPassPhrase = mNewPassPhrase;
mNewPassPhrase = ""; mNewPassPhrase = "";
mChangePassPhrase.setVisibility(View.GONE); mChangePassphrase.setVisibility(View.GONE);
} else { } else {
mNewPassPhrase = mSavedNewPassPhrase; mNewPassPhrase = mSavedNewPassPhrase;
mChangePassPhrase.setVisibility(View.VISIBLE); mChangePassphrase.setVisibility(View.VISIBLE);
} }
} }
}); });
@ -504,7 +505,7 @@ public class EditKeyActivity extends ActionBarActivity {
if (passphrase == null) { if (passphrase == null) {
showPassphraseDialog(masterKeyId, masterCanSign); showPassphraseDialog(masterKeyId, masterCanSign);
} else { } else {
mCurrentPassPhrase = passphrase; mCurrentPassphrase = passphrase;
finallySaveClicked(); finallySaveClicked();
} }
} catch (PgpGeneralException e) { } catch (PgpGeneralException e) {
@ -523,7 +524,7 @@ public class EditKeyActivity extends ActionBarActivity {
// fill values for this action // fill values for this action
Bundle data = new Bundle(); Bundle data = new Bundle();
data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE, data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE,
mCurrentPassPhrase); mCurrentPassphrase);
data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase); data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase);
data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS, data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS,
getUserIds(mUserIdsView)); getUserIds(mUserIdsView));
@ -541,7 +542,7 @@ public class EditKeyActivity extends ActionBarActivity {
// Message is received after saving is done in ApgService // Message is received after saving is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_saving, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard ApgHandler first
super.handleMessage(message); super.handleMessage(message);
@ -559,7 +560,7 @@ public class EditKeyActivity extends ActionBarActivity {
setResult(RESULT_OK, data); setResult(RESULT_OK, data);
finish(); finish();
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
@ -694,7 +695,7 @@ public class EditKeyActivity extends ActionBarActivity {
} }
private void updatePassPhraseButtonText() { 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)); : getString(R.string.btn_set_passphrase));
} }

View File

@ -52,17 +52,21 @@ import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.ViewFlipper; import android.widget.ViewFlipper;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import com.beardedhen.androidbootstrap.FontAwesomeText;
import com.devspark.appmsg.AppMsg; import com.devspark.appmsg.AppMsg;
public class EncryptActivity extends DrawerActivity { public class EncryptActivity extends DrawerActivity {
@ -101,18 +105,24 @@ public class EncryptActivity extends DrawerActivity {
private int mEncryptTarget; private int mEncryptTarget;
private EditText mPassPhrase = null; private EditText mPassphrase = null;
private EditText mPassPhraseAgain = null; private EditText mPassphraseAgain = null;
private CheckBox mAsciiArmor = null; private CheckBox mAsciiArmor = null;
private Spinner mFileCompression = null; private Spinner mFileCompression = null;
private EditText mFilename = null; private EditText mFilename = null;
private CheckBox mDeleteAfter = null; private CheckBox mDeleteAfter = null;
private CheckBox mShareAfter = null;
private BootstrapButton mBrowse = null; private BootstrapButton mBrowse = null;
private String mInputFilename = null; private String mInputFilename = null;
private String mOutputFilename = null; private String mOutputFilename = null;
private Integer mShortAnimationDuration = null;
private boolean mFileAdvancedSettingsVisible = false;
private TextView mFileAdvancedSettings = null;
private LinearLayout mFileAdvancedSettingsContainer = null;
private FontAwesomeText mAdvancedSettingsIcon;
private boolean mAsciiArmorDemand = false; private boolean mAsciiArmorDemand = false;
private boolean mOverrideAsciiArmor = false; private boolean mOverrideAsciiArmor = false;
@ -147,6 +157,9 @@ public class EncryptActivity extends DrawerActivity {
updateMode(); updateMode();
updateActionBarButtons(); updateActionBarButtons();
// retrieve and cache the system's short animation time
mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
} }
/** /**
@ -436,14 +449,14 @@ public class EncryptActivity extends DrawerActivity {
// symmetric encryption // symmetric encryption
if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
boolean gotPassPhrase = false; boolean gotPassPhrase = false;
String passPhrase = mPassPhrase.getText().toString(); String passphrase = mPassphrase.getText().toString();
String passPhraseAgain = mPassPhraseAgain.getText().toString(); String passphraseAgain = mPassphraseAgain.getText().toString();
if (!passPhrase.equals(passPhraseAgain)) { if (!passphrase.equals(passphraseAgain)) {
AppMsg.makeText(this, R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show(); AppMsg.makeText(this, R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show();
return; return;
} }
gotPassPhrase = (passPhrase.length() != 0); gotPassPhrase = (passphrase.length() != 0);
if (!gotPassPhrase) { if (!gotPassPhrase) {
AppMsg.makeText(this, R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT) AppMsg.makeText(this, R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT)
.show(); .show();
@ -550,11 +563,11 @@ public class EncryptActivity extends DrawerActivity {
if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { if (mMode.getCurrentView().getId() == R.id.modeSymmetric) {
Log.d(Constants.TAG, "Symmetric encryption enabled!"); Log.d(Constants.TAG, "Symmetric encryption enabled!");
String passPhrase = mPassPhrase.getText().toString(); String passphrase = mPassphrase.getText().toString();
if (passPhrase.length() == 0) { if (passphrase.length() == 0) {
passPhrase = null; passphrase = null;
} }
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passPhrase); data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase);
} else { } else {
mSecretKeyIdToPass = mSecretKeyId; mSecretKeyIdToPass = mSecretKeyId;
encryptionKeyIds = mEncryptionKeyIds; encryptionKeyIds = mEncryptionKeyIds;
@ -604,7 +617,7 @@ public class EncryptActivity extends DrawerActivity {
// Message is received after encrypting is done in ApgService // Message is received after encrypting is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_encrypting, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard ApgHandler first
super.handleMessage(message); super.handleMessage(message);
@ -650,6 +663,15 @@ public class EncryptActivity extends DrawerActivity {
.newInstance(mInputFilename); .newInstance(mInputFilename);
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog"); deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
} }
if (mShareAfter.isChecked()) {
// Share encrypted file
Intent sendFileIntent = new Intent(Intent.ACTION_SEND);
sendFileIntent.setType("*/*");
sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename));
startActivity(Intent.createChooser(sendFileIntent,
getString(R.string.title_send_file)));
}
break; break;
default: default:
@ -659,8 +681,6 @@ public class EncryptActivity extends DrawerActivity {
} }
} }
} }
;
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back
@ -766,8 +786,8 @@ public class EncryptActivity extends DrawerActivity {
mMainUserId = (TextView) findViewById(R.id.mainUserId); mMainUserId = (TextView) findViewById(R.id.mainUserId);
mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
mPassPhrase = (EditText) findViewById(R.id.passPhrase); mPassphrase = (EditText) findViewById(R.id.passphrase);
mPassPhraseAgain = (EditText) findViewById(R.id.passPhraseAgain); mPassphraseAgain = (EditText) findViewById(R.id.passphraseAgain);
// measure the height of the source_file view and set the message view's min height to that, // 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. // so it fills mSource fully... bit of a hack.
@ -785,6 +805,50 @@ public class EncryptActivity extends DrawerActivity {
} }
}); });
mAdvancedSettingsIcon = (FontAwesomeText) findViewById(R.id.advancedSettingsIcon);
mFileAdvancedSettingsContainer = (LinearLayout) findViewById(R.id.fileAdvancedSettingsContainer);
mFileAdvancedSettings = (TextView) findViewById(R.id.advancedSettings);
LinearLayout advancedSettingsControl = (LinearLayout) findViewById(R.id.advancedSettingsControl);
advancedSettingsControl.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mFileAdvancedSettingsVisible = !mFileAdvancedSettingsVisible;
if (mFileAdvancedSettingsVisible) {
mAdvancedSettingsIcon.setIcon("fa-chevron-down");
mFileAdvancedSettingsContainer.setVisibility(View.VISIBLE);
AlphaAnimation animation = new AlphaAnimation(0f, 1f);
animation.setDuration(mShortAnimationDuration);
mFileAdvancedSettingsContainer.startAnimation(animation);
mFileAdvancedSettings.setText(R.string.btn_encryption_advanced_settings_hide);
} else {
mAdvancedSettingsIcon.setIcon("fa-chevron-right");
AlphaAnimation animation = new AlphaAnimation(1f, 0f);
animation.setDuration(mShortAnimationDuration);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// do nothing
}
@Override
public void onAnimationEnd(Animation animation) {
// making sure that at the end the container is completely removed from view
mFileAdvancedSettingsContainer.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
// do nothing
}
});
mFileAdvancedSettingsContainer.startAnimation(animation);
mFileAdvancedSettings.setText(R.string.btn_encryption_advanced_settings_show);
}
}
});
mFileCompression = (Spinner) findViewById(R.id.fileCompression); mFileCompression = (Spinner) findViewById(R.id.fileCompression);
Choice[] choices = new Choice[]{ Choice[] choices = new Choice[]{
new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " ("
@ -809,6 +873,7 @@ public class EncryptActivity extends DrawerActivity {
} }
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption); mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption);
mShareAfter = (CheckBox) findViewById(R.id.shareAfterEncryption);
mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour); mAsciiArmor = (CheckBox) findViewById(R.id.asciiArmour);
mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour()); mAsciiArmor.setChecked(Preferences.getPreferences(this).getDefaultAsciiArmour());

View File

@ -22,16 +22,11 @@ import java.util.ArrayList;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter; import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.widget.TextView;
public class HelpActivity extends ActionBarActivity { public class HelpActivity extends ActionBarActivity {
public static final String EXTRA_SELECTED_TAB = "selectedTab"; public static final String EXTRA_SELECTED_TAB = "selectedTab";
@ -64,19 +59,19 @@ public class HelpActivity extends ActionBarActivity {
Bundle startBundle = new Bundle(); Bundle startBundle = new Bundle();
startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start); startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)),
HelpHtmlFragment.class, startBundle, (selectedTab == 0 ? true : false)); HelpHtmlFragment.class, startBundle, (selectedTab == 0) );
Bundle nfcBundle = new Bundle(); Bundle nfcBundle = new Bundle();
nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam); nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)),
HelpHtmlFragment.class, nfcBundle, (selectedTab == 1 ? true : false)); HelpHtmlFragment.class, nfcBundle, (selectedTab == 1) );
Bundle changelogBundle = new Bundle(); Bundle changelogBundle = new Bundle();
changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog); changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)),
HelpHtmlFragment.class, changelogBundle, (selectedTab == 2 ? true : false)); HelpHtmlFragment.class, changelogBundle, (selectedTab == 2) );
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)),
HelpAboutFragment.class, null, (selectedTab == 3 ? true : false)); HelpAboutFragment.class, null, (selectedTab == 3) );
} }
} }

View File

@ -161,7 +161,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
} else if (extras.containsKey(EXTRA_KEY_ID)) { } else if (extras.containsKey(EXTRA_KEY_ID)) {
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0); long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
if (keyId != 0) { if (keyId != 0) {
query = "0x" + PgpKeyHelper.convertKeyToHex(keyId); query = PgpKeyHelper.convertKeyIdToHex(keyId);
} }
} else if (extras.containsKey(EXTRA_FINGERPRINT)) { } else if (extras.containsKey(EXTRA_FINGERPRINT)) {
String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT); String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
@ -360,11 +360,17 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
// } // }
/**
* Import keys with mImportData
*/
public void importKeys() {
// Message is received after importing is done in ApgService // Message is received after importing is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
R.string.progress_importing, ProgressDialog.STYLE_HORIZONTAL) { this,
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message); super.handleMessage(message);
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
@ -401,10 +407,6 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
} }
}; };
/**
* Import keys with mImportData
*/
public void importKeys() {
if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) { if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) {
Log.d(Constants.TAG, "importKeys started"); Log.d(Constants.TAG, "importKeys started");

View File

@ -219,14 +219,31 @@ public class ImportKeysListFragment extends ListFragment implements
} else { } else {
setListShownNoAnimation(true); setListShownNoAnimation(true);
} }
Exception error = data.getError();
switch (loader.getId()) { switch (loader.getId()) {
case LOADER_ID_BYTES: case LOADER_ID_BYTES:
if(error == null){
// No error
} else if(error instanceof ImportKeysListLoader.FileHasNoContent) {
AppMsg.makeText(getActivity(), R.string.error_import_file_no_content,
AppMsg.STYLE_ALERT).show();
} else if(error instanceof ImportKeysListLoader.NonPgpPart) {
AppMsg.makeText(getActivity(),
((ImportKeysListLoader.NonPgpPart) error).getCount() + " " + getResources().
getQuantityString(R.plurals.error_import_non_pgp_part,
((ImportKeysListLoader.NonPgpPart) error).getCount()),
new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.confirm)).show();
} else {
AppMsg.makeText(getActivity(), R.string.error_generic_report_bug,
new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.alert)).show();
}
break; break;
case LOADER_ID_SERVER_QUERY: case LOADER_ID_SERVER_QUERY:
Exception error = data.getError();
if(error == null) { if(error == null) {
AppMsg.makeText( AppMsg.makeText(
getActivity(), getResources().getQuantityString(R.plurals.keys_found, getActivity(), getResources().getQuantityString(R.plurals.keys_found,

View File

@ -59,8 +59,8 @@ public class KeyListActivity extends DrawerActivity {
return true; return true;
case R.id.menu_key_list_export: case R.id.menu_key_list_export:
mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR // TODO fix this for unified keylist
+ "/pubexport.asc"); mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB);
return true; return true;
default: default:

View File

@ -19,11 +19,14 @@ package org.sufficientlysecure.keychain.ui;
import java.util.HashMap; import java.util.HashMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
@ -53,13 +56,21 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button; import android.widget.Button;
@ -73,24 +84,37 @@ import com.beardedhen.androidbootstrap.BootstrapButton;
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
* StickyListHeaders library which does not extend upon ListView. * StickyListHeaders library which does not extend upon ListView.
*/ */
public class KeyListFragment extends Fragment implements AdapterView.OnItemClickListener, public class KeyListFragment extends Fragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor> { LoaderManager.LoaderCallbacks<Cursor> {
private KeyListAdapter mAdapter; private KeyListAdapter mAdapter;
private StickyListHeadersListView mStickyList; private StickyListHeadersListView mStickyList;
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
boolean mListShown;
View mProgressContainer;
View mListContainer;
private String mCurQuery;
private SearchView mSearchView;
// empty list layout // empty list layout
private BootstrapButton mButtonEmptyCreate; private BootstrapButton mButtonEmptyCreate;
private BootstrapButton mButtonEmptyImport; private BootstrapButton mButtonEmptyImport;
/** /**
* Load custom layout with StickyListView from library * Load custom layout with StickyListView from library
*/ */
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.key_list_fragment, container, false); View root = inflater.inflate(R.layout.key_list_fragment, container, false);
mButtonEmptyCreate = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_create); mStickyList = (StickyListHeadersListView) root.findViewById(R.id.key_list_list);
mStickyList.setOnItemClickListener(this);
// empty view
mButtonEmptyCreate = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_create);
mButtonEmptyCreate.setOnClickListener(new OnClickListener() { mButtonEmptyCreate.setOnClickListener(new OnClickListener() {
@Override @Override
@ -102,8 +126,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
startActivityForResult(intent, 0); startActivityForResult(intent, 0);
} }
}); });
mButtonEmptyImport = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_import);
mButtonEmptyImport = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_import);
mButtonEmptyImport.setOnClickListener(new OnClickListener() { mButtonEmptyImport.setOnClickListener(new OnClickListener() {
@Override @Override
@ -114,7 +137,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
} }
}); });
return view; // rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
mListContainer = root.findViewById(R.id.key_list_list_container);
mProgressContainer = root.findViewById(R.id.key_list_progress_container);
mListShown = true;
return root;
} }
/** /**
@ -125,8 +153,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
mStickyList.setOnItemClickListener(this); mStickyList.setOnItemClickListener(this);
mStickyList.setAreHeadersSticky(true); mStickyList.setAreHeadersSticky(true);
mStickyList.setDrawingListUnderStickyHeader(false); mStickyList.setDrawingListUnderStickyHeader(false);
@ -137,7 +163,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
} }
// this view is made visible if no data is available // this view is made visible if no data is available
mStickyList.setEmptyView(getActivity().findViewById(R.id.empty)); mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
/* /*
* ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
@ -147,8 +173,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() {
private int count = 0;
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
android.view.MenuInflater inflater = getActivity().getMenuInflater(); android.view.MenuInflater inflater = getActivity().getMenuInflater();
@ -174,7 +198,7 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
break; break;
} }
case R.id.menu_key_list_multi_delete: { case R.id.menu_key_list_multi_delete: {
ids = mAdapter.getCurrentSelectedItemIds(); ids = mStickyList.getWrappedList().getCheckedItemIds();
showDeleteKeyDialog(mode, ids); showDeleteKeyDialog(mode, ids);
break; break;
} }
@ -184,7 +208,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
@Override @Override
public void onDestroyActionMode(ActionMode mode) { public void onDestroyActionMode(ActionMode mode) {
count = 0;
mAdapter.clearSelection(); mAdapter.clearSelection();
} }
@ -192,13 +215,11 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) { boolean checked) {
if (checked) { if (checked) {
count++;
mAdapter.setNewSelection(position, checked); mAdapter.setNewSelection(position, checked);
} else { } else {
count--;
mAdapter.removeSelection(position); mAdapter.removeSelection(position);
} }
int count = mStickyList.getCheckedItemCount();
String keysSelected = getResources().getQuantityString( String keysSelected = getResources().getQuantityString(
R.plurals.key_list_selected_keys, count, count); R.plurals.key_list_selected_keys, count, count);
mode.setTitle(keysSelected); mode.setTitle(keysSelected);
@ -207,9 +228,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
}); });
} }
// NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading // We have a menu item to show in action bar.
setHasOptionsMenu(true);
// NOTE: Not supported by StickyListHeader, but reimplemented here
// Start out with a progress indicator. // Start out with a progress indicator.
// setListShown(false); setListShown(false);
// Create an empty adapter we will use to display the loaded data. // Create an empty adapter we will use to display the loaded data.
mAdapter = new KeyListAdapter(getActivity(), null, Id.type.public_key); mAdapter = new KeyListAdapter(getActivity(), null, Id.type.public_key);
@ -238,27 +262,33 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
// This is called when a new Loader needs to be created. This // 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. // sample only has one Loader, so we don't care about the ID.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
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 // Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed. // 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 @Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the // Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.) // old cursor once we return.)
mAdapter.setSearchQuery(mCurQuery);
mAdapter.swapCursor(data); mAdapter.swapCursor(data);
mStickyList.setAdapter(mAdapter); mStickyList.setAdapter(mAdapter);
// NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading // NOTE: Not supported by StickyListHeader, but reimplemented here
// The list should now be shown. // The list should now be shown.
// if (isResumed()) { if (isResumed()) {
// setListShown(true); setListShown(true);
// } else { } else {
// setListShownNoAnimation(true); setListShownNoAnimation(true);
// } }
} }
@Override @Override
@ -338,6 +368,87 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); 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_search);
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
// Execute this when searching
mSearchView.setOnQueryTextListener(this);
// Erase search result without focus
MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
mCurQuery = null;
mSearchView.setQuery("", true);
getLoaderManager().restartLoader(0, null, KeyListFragment.this);
return true;
}
});
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.
mCurQuery = !TextUtils.isEmpty(s) ? s : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
public void setListShown(boolean shown, boolean animate) {
if (mListShown == shown) {
return;
}
mListShown = shown;
if (shown) {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
mListContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
}
mProgressContainer.setVisibility(View.GONE);
mListContainer.setVisibility(View.VISIBLE);
} else {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
mListContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
}
mProgressContainer.setVisibility(View.VISIBLE);
mListContainer.setVisibility(View.INVISIBLE);
}
}
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
public void setListShown(boolean shown) {
setListShown(shown, true);
}
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
public void setListShownNoAnimation(boolean shown) {
setListShown(shown, false);
}
/** /**
* Implements StickyListHeadersAdapter from library * Implements StickyListHeadersAdapter from library
*/ */
@ -347,6 +458,8 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
private int mIndexIsRevoked; private int mIndexIsRevoked;
private int mMasterKeyId; private int mMasterKeyId;
private String mCurQuery;
@SuppressLint("UseSparseArrays") @SuppressLint("UseSparseArrays")
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>(); private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
@ -394,12 +507,12 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
String userId = cursor.getString(mIndexUserId); String userId = cursor.getString(mIndexUserId);
String[] userIdSplit = PgpKeyHelper.splitUserId(userId); String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
if (userIdSplit[0] != null) { if (userIdSplit[0] != null) {
mainUserId.setText(userIdSplit[0]); mainUserId.setText(highlightSearchQuery(userIdSplit[0]));
} else { } else {
mainUserId.setText(R.string.user_id_no_name); mainUserId.setText(R.string.user_id_no_name);
} }
if (userIdSplit[1] != null) { if (userIdSplit[1] != null) {
mainUserIdRest.setText(userIdSplit[1]); mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1]));
mainUserIdRest.setVisibility(View.VISIBLE); mainUserIdRest.setVisibility(View.VISIBLE);
} else { } else {
mainUserIdRest.setVisibility(View.GONE); mainUserIdRest.setVisibility(View.GONE);
@ -558,20 +671,6 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
notifyDataSetChanged(); notifyDataSetChanged();
} }
public boolean isPositionChecked(int position) {
Boolean result = mSelection.get(position);
return result == null ? false : result;
}
public long[] getCurrentSelectedItemIds() {
long[] ids = new long[mSelection.size()];
int i = 0;
// get master key ids
for (int pos : mSelection.keySet())
ids[i++] = mAdapter.getItemId(pos);
return ids;
}
public long[] getCurrentSelectedMasterKeyIds() { public long[] getCurrentSelectedMasterKeyIds() {
long[] ids = new long[mSelection.size()]; long[] ids = new long[mSelection.size()];
int i = 0; int i = 0;
@ -608,6 +707,34 @@ public class KeyListFragment extends Fragment implements AdapterView.OnItemClick
return v; return v;
} }
// search highlight methods
public void setSearchQuery(String searchQuery) {
mCurQuery = searchQuery;
}
public String getSearchQuery() {
return mCurQuery;
}
protected Spannable highlightSearchQuery(String text) {
Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
if (mCurQuery != null) {
Pattern pattern = Pattern.compile("(?i)" + mCurQuery);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
highlight.setSpan(
new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
matcher.start(),
matcher.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return highlight;
} else {
return highlight;
}
}
} }
} }

View File

@ -24,24 +24,27 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference;
import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import java.util.List;
@SuppressLint("NewApi")
public class PreferencesActivity extends PreferenceActivity { public class PreferencesActivity extends PreferenceActivity {
private IntegerListPreference mPassPhraseCacheTtl = null;
private IntegerListPreference mEncryptionAlgorithm = null; public final static String ACTION_PREFS_GEN = "org.sufficientlysecure.keychain.ui.PREFS_GEN";
private IntegerListPreference mHashAlgorithm = null; public final static String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
private IntegerListPreference mMessageCompression = null;
private IntegerListPreference mFileCompression = null;
private CheckBoxPreference mAsciiArmour = null;
private CheckBoxPreference mForceV3Signatures = null;
private PreferenceScreen mKeyServerPreference = null; private PreferenceScreen mKeyServerPreference = null;
private Preferences mPreferences; private static Preferences mPreferences;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -53,22 +56,218 @@ public class PreferencesActivity extends PreferenceActivity {
// actionBar.setDisplayHomeAsUpEnabled(false); // actionBar.setDisplayHomeAsUpEnabled(false);
// actionBar.setHomeButtonEnabled(false); // actionBar.setHomeButtonEnabled(false);
addPreferencesFromResource(R.xml.preferences); String action = getIntent().getAction();
mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL); if (action != null && action.equals(ACTION_PREFS_GEN)) {
mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl()); addPreferencesFromResource(R.xml.gen_preferences);
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry());
mPassPhraseCacheTtl initializePassPassPhraceCacheTtl(
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL));
public boolean onPreferenceChange(Preference preference, Object newValue) {
mPassPhraseCacheTtl.setValue(newValue.toString()); mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS);
mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); String servers[] = mPreferences.getKeyServers();
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString())); 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; return false;
} }
}); });
mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM); } 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());
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
return false;
}
});
}
private static void initializeEncryptionAlgorithm(final IntegerListPreference mEncryptionAlgorithm) {
int valueIds[] = { PGPEncryptedData.AES_128, PGPEncryptedData.AES_192, int valueIds[] = { PGPEncryptedData.AES_128, PGPEncryptedData.AES_192,
PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH, PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH,
PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES, PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES,
@ -93,8 +292,10 @@ public class PreferencesActivity extends PreferenceActivity {
return false; 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, valueIds = new int[] { HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160,
HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256,
HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, }; HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, };
@ -116,19 +317,10 @@ public class PreferencesActivity extends PreferenceActivity {
return false; 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.setEntries(entries);
mMessageCompression.setEntryValues(values); mMessageCompression.setEntryValues(values);
mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression()); mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression());
@ -143,8 +335,10 @@ public class PreferencesActivity extends PreferenceActivity {
return false; 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.setEntries(entries);
mFileCompression.setEntryValues(values); mFileCompression.setEntryValues(values);
mFileCompression.setValue("" + mPreferences.getDefaultFileCompression()); mFileCompression.setValue("" + mPreferences.getDefaultFileCompression());
@ -157,8 +351,9 @@ public class PreferencesActivity extends PreferenceActivity {
return false; return false;
} }
}); });
}
mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR); private static void initializeAsciiArmour(final CheckBoxPreference mAsciiArmour) {
mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
@ -167,8 +362,9 @@ public class PreferencesActivity extends PreferenceActivity {
return false; return false;
} }
}); });
}
mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES); private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) {
mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures()); mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures());
mForceV3Signatures mForceV3Signatures
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@ -178,43 +374,5 @@ public class PreferencesActivity extends PreferenceActivity {
return false; 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

@ -50,14 +50,14 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// ok // ok
okClicked(); okClicked();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// cancel // cancel
@ -81,11 +81,11 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
Intent intent = getIntent(); Intent intent = getIntent();
String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS); String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
if (servers != null) { if (servers != null) {
for (int i = 0; i < servers.length; ++i) { for (String serv : servers) {
KeyServerEditor view = (KeyServerEditor) mInflater.inflate( KeyServerEditor view = (KeyServerEditor) mInflater.inflate(
R.layout.key_server_editor, mEditors, false); R.layout.key_server_editor, mEditors, false);
view.setEditorListener(this); view.setEditorListener(this);
view.setValue(servers[i]); view.setValue(serv);
mEditors.addView(view); mEditors.addView(view);
} }
} }

View File

@ -46,14 +46,14 @@ public class SelectPublicKeyActivity extends ActionBarActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Inflate a "Done"/"Cancel" custom action bar view // Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
new View.OnClickListener() { new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// ok // ok
okClicked(); okClicked();
} }
}, R.string.btn_do_not_save, new View.OnClickListener() { }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// cancel // cancel

View File

@ -30,7 +30,7 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
import android.app.Activity; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.DatabaseUtils; import android.database.DatabaseUtils;
import android.net.Uri; import android.net.Uri;
@ -38,17 +38,35 @@ import android.os.Bundle;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
public class SelectPublicKeyFragment extends ListFragmentWorkaround implements public class SelectPublicKeyFragment extends ListFragmentWorkaround implements TextWatcher,
LoaderManager.LoaderCallbacks<Cursor> { LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids"; public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids";
private Activity mActivity;
private SelectKeyCursorAdapter mAdapter; private SelectKeyCursorAdapter mAdapter;
private ListView mListView; private EditText mSearchView;
private long mSelectedMasterKeyIds[]; private long mSelectedMasterKeyIds[];
private String mCurQuery;
// copied from ListFragment
static final int INTERNAL_EMPTY_ID = 0x00ff0001;
static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002;
static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003;
// added for search view
static final int SEARCH_ID = 0x00ff0004;
/** /**
* Creates new instance of this fragment * Creates new instance of this fragment
@ -67,10 +85,84 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS); mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS);
} }
/**
* Copied from ListFragment and added EditText for search on top of list.
* We do not use a custom layout here, because this breaks the progress bar functionality
* of ListFragment.
*
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final Context context = getActivity();
FrameLayout root = new FrameLayout(context);
// ------------------------------------------------------------------
LinearLayout pframe = new LinearLayout(context);
pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID);
pframe.setOrientation(LinearLayout.VERTICAL);
pframe.setVisibility(View.GONE);
pframe.setGravity(Gravity.CENTER);
ProgressBar progress = new ProgressBar(context, null,
android.R.attr.progressBarStyleLarge);
pframe.addView(progress, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(pframe, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
// ------------------------------------------------------------------
FrameLayout lframe = new FrameLayout(context);
lframe.setId(INTERNAL_LIST_CONTAINER_ID);
TextView tv = new TextView(getActivity());
tv.setId(INTERNAL_EMPTY_ID);
tv.setGravity(Gravity.CENTER);
lframe.addView(tv, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
// Added for search view: linearLayout, mSearchView
LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setOrientation(LinearLayout.VERTICAL);
mSearchView = new EditText(context);
mSearchView.setId(SEARCH_ID);
mSearchView.setHint(R.string.menu_search);
mSearchView.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(R.drawable.ic_action_search), null, null, null);
linearLayout.addView(mSearchView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
ListView lv = new ListView(getActivity());
lv.setId(android.R.id.list);
lv.setDrawSelectorOnTop(false);
linearLayout.addView(lv, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
lframe.addView(linearLayout, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
root.addView(lframe, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
// ------------------------------------------------------------------
root.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
return root;
}
/** /**
* Define Adapter and Loader on create of Activity * Define Adapter and Loader on create of Activity
*/ */
@ -78,16 +170,15 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
mActivity = getActivity(); getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mListView = getListView();
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
// Give some text to display if there is no data. In a real // Give some text to display if there is no data. In a real
// application this would come from a resource. // application this would come from a resource.
setEmptyText(getString(R.string.list_empty)); setEmptyText(getString(R.string.list_empty));
mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.public_key); mSearchView.addTextChangedListener(this);
mAdapter = new SelectKeyCursorAdapter(getActivity(), null, 0, getListView(), Id.type.public_key);
setListAdapter(mAdapter); setListAdapter(mAdapter);
@ -106,11 +197,11 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
*/ */
private void preselectMasterKeyIds(long[] masterKeyIds) { private void preselectMasterKeyIds(long[] masterKeyIds) {
if (masterKeyIds != null) { if (masterKeyIds != null) {
for (int i = 0; i < mListView.getCount(); ++i) { for (int i = 0; i < getListView().getCount(); ++i) {
long keyId = mAdapter.getMasterKeyId(i); long keyId = mAdapter.getMasterKeyId(i);
for (int j = 0; j < masterKeyIds.length; ++j) { for (long masterKeyId : masterKeyIds) {
if (keyId == masterKeyIds[j]) { if (keyId == masterKeyId) {
mListView.setItemChecked(i, true); getListView().setItemChecked(i, true);
break; break;
} }
} }
@ -127,8 +218,8 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
// mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key
// ids! // ids!
Vector<Long> vector = new Vector<Long>(); Vector<Long> vector = new Vector<Long>();
for (int i = 0; i < mListView.getCount(); ++i) { for (int i = 0; i < getListView().getCount(); ++i) {
if (mListView.isItemChecked(i)) { if (getListView().isItemChecked(i)) {
vector.add(mAdapter.getMasterKeyId(i)); vector.add(mAdapter.getMasterKeyId(i));
} }
} }
@ -149,8 +240,8 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
*/ */
public String[] getSelectedUserIds() { public String[] getSelectedUserIds() {
Vector<String> userIds = new Vector<String>(); Vector<String> userIds = new Vector<String>();
for (int i = 0; i < mListView.getCount(); ++i) { for (int i = 0; i < getListView().getCount(); ++i) {
if (mListView.isItemChecked(i)) { if (getListView().isItemChecked(i)) {
userIds.add((String) mAdapter.getUserId(i)); userIds.add((String) mAdapter.getUserId(i));
} }
} }
@ -199,37 +290,28 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
inMasterKeyList += ")"; inMasterKeyList += ")";
} }
// if (searchString != null && searchString.trim().length() > 0) {
// String[] chunks = searchString.trim().split(" +");
// qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME
// + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "."
// + Keys._ID);
// for (int i = 0; i < chunks.length; ++i) {
// qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
// qb.appendWhereEscapeString("%" + chunks[i] + "%");
// }
// qb.appendWhere("))");
//
// if (inIdList != null) {
// qb.appendWhere(" OR (" + inIdList + ")");
// }
// }
String orderBy = UserIds.USER_ID + " ASC"; String orderBy = UserIds.USER_ID + " ASC";
if (inMasterKeyList != null) { if (inMasterKeyList != null) {
// sort by selected master keys // sort by selected master keys
orderBy = inMasterKeyList + " DESC, " + orderBy; 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 // Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed. // 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 @Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the // Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.) // old cursor once we return.)
mAdapter.setSearchQuery(mCurQuery);
mAdapter.swapCursor(data); mAdapter.swapCursor(data);
// The list should now be shown. // The list should now be shown.
@ -250,4 +332,20 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
// longer using it. // longer using it.
mAdapter.swapCursor(null); 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) {
mCurQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null;
getLoaderManager().restartLoader(0, null, this);
}
} }

View File

@ -24,10 +24,13 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
@ -40,6 +43,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
private TextView mKeyUserId; private TextView mKeyUserId;
private TextView mKeyUserIdRest; private TextView mKeyUserIdRest;
private TextView mKeyMasterKeyIdHex;
private BootstrapButton mSelectKeyButton; private BootstrapButton mSelectKeyButton;
private Boolean mFilterCertify; private Boolean mFilterCertify;
@ -61,26 +65,52 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
public void selectKey(long secretKeyId) { public void selectKey(long secretKeyId) {
if (secretKeyId == Id.key.none) { if (secretKeyId == Id.key.none) {
mKeyUserId.setText(R.string.api_settings_no_key); mKeyMasterKeyIdHex.setText(R.string.api_settings_no_key);
mKeyUserIdRest.setText(""); mKeyUserIdRest.setText("");
mKeyUserId.setVisibility(View.GONE);
mKeyUserIdRest.setVisibility(View.GONE);
} else { } else {
String uid = getResources().getString(R.string.user_id_no_name);
String uidExtra = "";
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId( PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
getActivity(), secretKeyId); getActivity(), secretKeyId);
if (keyRing != null) { if (keyRing != null) {
PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing); PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing);
String masterkeyIdHex = PgpKeyHelper.convertKeyIdToHex(secretKeyId);
if (key != null) { if (key != null) {
String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key); String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key);
String chunks[] = userId.split(" <", 2);
uid = chunks[0]; String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
if (chunks.length > 1) { String userName, userEmail;
uidExtra = "<" + chunks[1];
if (userIdSplit[0] != null) {
userName = userIdSplit[0];
} else {
userName = getActivity().getResources().getString(R.string.user_id_no_name);
} }
if (userIdSplit[1] != null) {
userEmail = userIdSplit[1];
} else {
userEmail = getActivity().getResources().getString(R.string.error_user_id_no_email);
} }
mKeyMasterKeyIdHex.setText(masterkeyIdHex);
mKeyUserId.setText(userName);
mKeyUserIdRest.setText(userEmail);
mKeyUserId.setVisibility(View.VISIBLE);
mKeyUserIdRest.setVisibility(View.VISIBLE);
} else {
mKeyMasterKeyIdHex.setText(getActivity().getResources().getString(R.string.no_key));
mKeyUserId.setVisibility(View.GONE);
mKeyUserIdRest.setVisibility(View.GONE);
} }
mKeyUserId.setText(uid); } else {
mKeyUserIdRest.setText(uidExtra); mKeyMasterKeyIdHex.setText(getActivity().getResources().getString(R.string.no_keys_added_or_updated) + " for master id: " + secretKeyId);
mKeyUserId.setVisibility(View.GONE);
mKeyUserIdRest.setVisibility(View.GONE);
}
} }
} }
@ -98,6 +128,7 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id); mKeyUserId = (TextView) view.findViewById(R.id.select_secret_key_user_id);
mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest); mKeyUserIdRest = (TextView) view.findViewById(R.id.select_secret_key_user_id_rest);
mKeyMasterKeyIdHex = (TextView) view.findViewById(R.id.select_secret_key_master_key_hex);
mSelectKeyButton = (BootstrapButton) view mSelectKeyButton = (BootstrapButton) view
.findViewById(R.id.select_secret_key_select_key_button); .findViewById(R.id.select_secret_key_select_key_button);
mFilterCertify = false; mFilterCertify = false;
@ -117,6 +148,8 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
startActivityForResult(intent, REQUEST_CODE_SELECT_KEY); startActivityForResult(intent, REQUEST_CODE_SELECT_KEY);
} }
// Select Secret Key Activity delivers the intent which was sent by it using interface to Select
// Secret Key Fragment.Intent contains Master Key Id, User Email, User Name, Master Key Id Hex.
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode & 0xFFFF) { switch (requestCode & 0xFFFF) {
@ -125,7 +158,6 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
Bundle bundle = data.getExtras(); Bundle bundle = data.getExtras();
secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID); secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
selectKey(secretKeyId); selectKey(secretKeyId);
// remove displayed errors // remove displayed errors

View File

@ -102,7 +102,7 @@ public class UploadKeyActivity extends ActionBarActivity {
// Message is received after uploading is done in ApgService // Message is received after uploading is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
public void handleMessage(Message message) { public void handleMessage(Message message) {
// handle messages by standard ApgHandler first // handle messages by standard ApgHandler first
super.handleMessage(message); super.handleMessage(message);
@ -113,7 +113,7 @@ public class UploadKeyActivity extends ActionBarActivity {
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
finish(); finish();
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back

View File

@ -93,12 +93,12 @@ public class ViewKeyActivity extends ActionBarActivity {
Bundle mainBundle = new Bundle(); Bundle mainBundle = new Bundle();
mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri); mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_main)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_main)),
ViewKeyMainFragment.class, mainBundle, (selectedTab == 0 ? true : false)); ViewKeyMainFragment.class, mainBundle, (selectedTab == 0));
Bundle certBundle = new Bundle(); Bundle certBundle = new Bundle();
certBundle.putLong(ViewKeyCertsFragment.ARG_KEYRING_ROW_ID, rowId); certBundle.putLong(ViewKeyCertsFragment.ARG_KEYRING_ROW_ID, rowId);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)), mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)),
ViewKeyCertsFragment.class, certBundle, (selectedTab == 1 ? true : false)); ViewKeyCertsFragment.class, certBundle, (selectedTab == 1));
} }
@Override @Override
@ -123,8 +123,8 @@ public class ViewKeyActivity extends ActionBarActivity {
uploadToKeyserver(mDataUri); uploadToKeyserver(mDataUri);
return true; return true;
case R.id.menu_key_view_export_file: case R.id.menu_key_view_export_file:
mExportHelper.showExportKeysDialog(mDataUri, Id.type.public_key, Constants.path.APP_DIR long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())};
+ "/pubexport.asc"); mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.path.APP_DIR_FILE_PUB);
return true; return true;
case R.id.menu_key_view_share_default_fingerprint: case R.id.menu_key_view_share_default_fingerprint:
shareKey(mDataUri, true); shareKey(mDataUri, true);
@ -159,7 +159,7 @@ public class ViewKeyActivity extends ActionBarActivity {
} }
private void updateFromKeyserver(Uri dataUri) { private void updateFromKeyserver(Uri dataUri) {
long updateKeyId = ProviderHelper.getMasterKeyId(ViewKeyActivity.this, mDataUri); long updateKeyId = ProviderHelper.getMasterKeyId(ViewKeyActivity.this, dataUri);
if (updateKeyId == 0) { if (updateKeyId == 0) {
Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!"); Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!");

View File

@ -26,7 +26,10 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -273,7 +276,7 @@ public class ViewKeyMainFragment extends Fragment implements
// get key id from MASTER_KEY_ID // get key id from MASTER_KEY_ID
long keyId = data.getLong(KEYS_INDEX_KEY_ID); long keyId = data.getLong(KEYS_INDEX_KEY_ID);
String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId); String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
mKeyId.setText(keyIdStr); mKeyId.setText(keyIdStr);
// get creation date from CREATION // get creation date from CREATION
@ -306,9 +309,8 @@ public class ViewKeyMainFragment extends Fragment implements
fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), mDataUri); fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), mDataUri);
} }
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true); String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true);
fingerprint = fingerprint.replace(" ", "\n");
mFingerprint.setText(fingerprint); mFingerprint.setText(colorizeFingerprint(fingerprint));
} }
mKeysAdapter.swapCursor(data); mKeysAdapter.swapCursor(data);
@ -319,6 +321,25 @@ public class ViewKeyMainFragment extends Fragment implements
} }
} }
private SpannableStringBuilder colorizeFingerprint(String fingerprint) {
SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint);
ForegroundColorSpan fcs = new ForegroundColorSpan(Color.BLACK);
// for each 4 characters of the fingerprint + 1 space
for (int i = 0; i < fingerprint.length(); i += 5) {
int minFingLength = Math.min(i + 4, fingerprint.length());
String fourChars = fingerprint.substring(i, minFingLength);
// Create a foreground color by converting the 4 fingerprint chars to an int hashcode
// and then converting that int to hex to use as a color
fcs = new ForegroundColorSpan(
Color.parseColor(String.format("#%06X", (0xFFFFFF & fourChars.hashCode()))));
sb.setSpan(fcs, i, minFingLength, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
return sb;
}
/** /**
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
* We need to make sure we are no longer using it. * We need to make sure we are no longer using it.

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import org.sufficientlysecure.keychain.R;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class HighlightQueryCursorAdapter extends CursorAdapter {
private String mCurQuery;
public HighlightQueryCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mCurQuery = null;
}
public void setSearchQuery(String searchQuery) {
mCurQuery = searchQuery;
}
public String getSearchQuery() {
return mCurQuery;
}
protected Spannable highlightSearchQuery(String text) {
Spannable highlight = Spannable.Factory.getInstance().newSpannable(text);
if (mCurQuery != null) {
Pattern pattern = Pattern.compile("(?i)" + mCurQuery);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
highlight.setSpan(
new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)),
matcher.start(),
matcher.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return highlight;
} else {
return highlight;
}
}
}

View File

@ -42,7 +42,15 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
protected Activity mActivity; protected Activity mActivity;
protected List<ImportKeysListEntry> data; protected List<ImportKeysListEntry> data;
static class ViewHolder{
private TextView mainUserId;
private TextView mainUserIdRest;
private TextView keyId;
private TextView fingerprint;
private TextView algorithm;
private TextView status;
}
public ImportKeysAdapter(Activity activity) { public ImportKeysAdapter(Activity activity) {
super(activity, -1); super(activity, -1);
mActivity = activity; mActivity = activity;
@ -86,16 +94,21 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
ImportKeysListEntry entry = data.get(position); ImportKeysListEntry entry = data.get(position);
ViewHolder holder;
View view = mInflater.inflate(R.layout.import_keys_list_entry, null); if(convertView == null) {
holder = new ViewHolder();
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); convertView = mInflater.inflate(R.layout.import_keys_list_entry, null);
TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
TextView keyId = (TextView) view.findViewById(R.id.keyId); holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
TextView fingerprint = (TextView) view.findViewById(R.id.fingerprint); holder.keyId = (TextView) convertView.findViewById(R.id.keyId);
TextView algorithm = (TextView) view.findViewById(R.id.algorithm); holder.fingerprint = (TextView) convertView.findViewById(R.id.fingerprint);
TextView status = (TextView) view.findViewById(R.id.status); holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm);
holder.status = (TextView) convertView.findViewById(R.id.status);
convertView.setTag(holder);
}
else{
holder = (ViewHolder)convertView.getTag();
}
// main user id // main user id
String userId = entry.userIds.get(0); String userId = entry.userIds.get(0);
String[] userIdSplit = PgpKeyHelper.splitUserId(userId); String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
@ -105,39 +118,39 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
// show red user id if it is a secret key // show red user id if it is a secret key
if (entry.secretKey) { if (entry.secretKey) {
userIdSplit[0] = mActivity.getString(R.string.secret_key) + " " + userIdSplit[0]; userIdSplit[0] = mActivity.getString(R.string.secret_key) + " " + userIdSplit[0];
mainUserId.setTextColor(Color.RED); holder.mainUserId.setTextColor(Color.RED);
} }
mainUserId.setText(userIdSplit[0]); holder.mainUserId.setText(userIdSplit[0]);
} else { } else {
mainUserId.setText(R.string.user_id_no_name); holder.mainUserId.setText(R.string.user_id_no_name);
} }
// email // email
if (userIdSplit[1] != null) { if (userIdSplit[1] != null) {
mainUserIdRest.setText(userIdSplit[1]); holder.mainUserIdRest.setText(userIdSplit[1]);
mainUserIdRest.setVisibility(View.VISIBLE); holder.mainUserIdRest.setVisibility(View.VISIBLE);
} else { } else {
mainUserIdRest.setVisibility(View.GONE); holder.mainUserIdRest.setVisibility(View.GONE);
} }
keyId.setText(entry.hexKeyId); holder.keyId.setText(entry.hexKeyId);
if (entry.fingerPrint != null) { if (entry.fingerPrint != null) {
fingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint); holder.fingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint);
fingerprint.setVisibility(View.VISIBLE); holder.fingerprint.setVisibility(View.VISIBLE);
} else { } else {
fingerprint.setVisibility(View.GONE); holder.fingerprint.setVisibility(View.GONE);
} }
algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm); holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
if (entry.revoked) { if (entry.revoked) {
status.setText(R.string.revoked); holder.status.setText(R.string.revoked);
} else { } else {
status.setVisibility(View.GONE); holder.status.setVisibility(View.GONE);
} }
LinearLayout ll = (LinearLayout) view.findViewById(R.id.list); LinearLayout ll = (LinearLayout) convertView.findViewById(R.id.list);
if (entry.userIds.size() == 1) { if (entry.userIds.size() == 1) {
ll.setVisibility(View.GONE); ll.setVisibility(View.GONE);
} else { } else {
@ -162,10 +175,10 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
} }
} }
CheckBox cBox = (CheckBox) view.findViewById(R.id.selected); CheckBox cBox = (CheckBox) convertView.findViewById(R.id.selected);
cBox.setChecked(entry.isSelected()); cBox.setChecked(entry.isSelected());
return view; return convertView;
} }
} }

View File

@ -125,7 +125,10 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
* Constructor for later querying from keyserver * Constructor for later querying from keyserver
*/ */
public ImportKeysListEntry() { public ImportKeysListEntry() {
// keys from keyserver are always public keys
secretKey = false; secretKey = false;
// do not select by default
selected = false;
userIds = new ArrayList<String>(); userIds = new ArrayList<String>();
} }
@ -167,7 +170,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
this.revoked = pgpKeyRing.getPublicKey().isRevoked(); this.revoked = pgpKeyRing.getPublicKey().isRevoked();
this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey() this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
.getFingerprint(), true); .getFingerprint(), true);
this.hexKeyId = "0x" + PgpKeyHelper.convertKeyIdToHex(keyId); this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId);
this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength(); this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength();
int algorithm = pgpKeyRing.getPublicKey().getAlgorithm(); int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL

View File

@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.ui.adapter;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPObjectFactory;
@ -34,6 +33,21 @@ import android.content.Context;
import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.AsyncTaskLoader;
public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> { public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
public static class FileHasNoContent extends Exception {
}
public static class NonPgpPart extends Exception {
private int count;
public NonPgpPart(int count) {
this.count = count;
}
public int getCount() {
return count;
}
}
Context mContext; Context mContext;
InputData mInputData; InputData mInputData;
@ -88,21 +102,26 @@ public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper
/** /**
* Reads all PGPKeyRing objects from input * Reads all PGPKeyRing objects from input
* *
* @param keyringBytes * @param inputData
* @return * @return
*/ */
private void generateListOfKeyrings(InputData inputData) { private void generateListOfKeyrings(InputData inputData) {
boolean isEmpty = true;
int nonPgpCounter = 0;
PositionAwareInputStream progressIn = new PositionAwareInputStream( PositionAwareInputStream progressIn = new PositionAwareInputStream(
inputData.getInputStream()); inputData.getInputStream());
// need to have access to the bufferedInput, so we can reuse it for the possible // 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 // PGPObject chunks after the first one, e.g. files with several consecutive ASCII
// armour blocks // armor blocks
BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
try { try {
// read all available blocks... (asc files can contain many blocks with BEGIN END) // read all available blocks... (asc files can contain many blocks with BEGIN END)
while (bufferedInput.available() > 0) { while (bufferedInput.available() > 0) {
isEmpty = false;
InputStream in = PGPUtil.getDecoderStream(bufferedInput); InputStream in = PGPUtil.getDecoderStream(bufferedInput);
PGPObjectFactory objectFactory = new PGPObjectFactory(in); PGPObjectFactory objectFactory = new PGPObjectFactory(in);
@ -116,11 +135,25 @@ public class ImportKeysListLoader extends AsyncTaskLoader<AsyncTaskResultWrapper
addToData(newKeyring); addToData(newKeyring);
} else { } else {
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!"); Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
nonPgpCounter++;
} }
} }
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(Constants.TAG, "Exception on parsing key file!", e); Log.e(Constants.TAG, "Exception on parsing key file!", e);
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(data, e);
nonPgpCounter = 0;
}
if(isEmpty) {
Log.e(Constants.TAG, "File has no content!", new FileHasNoContent());
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>
(data, new FileHasNoContent());
}
if(nonPgpCounter > 0) {
entryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>
(data, new NonPgpPart(nonPgpCounter));
} }
} }

View File

@ -25,7 +25,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -33,7 +32,9 @@ import android.widget.CheckBox;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
public class SelectKeyCursorAdapter extends CursorAdapter {
public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter {
protected int mKeyType; protected int mKeyType;
@ -55,7 +56,6 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
mInflater = LayoutInflater.from(context); mInflater = LayoutInflater.from(context);
mListView = listView; mListView = listView;
mKeyType = keyType; mKeyType = keyType;
initIndex(c); initIndex(c);
} }
@ -104,12 +104,12 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
String[] userIdSplit = PgpKeyHelper.splitUserId(userId); String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
if (userIdSplit[0] != null) { if (userIdSplit[0] != null) {
mainUserId.setText(userIdSplit[0]); mainUserId.setText(highlightSearchQuery(userIdSplit[0]));
} else { } else {
mainUserId.setText(R.string.user_id_no_name); mainUserId.setText(R.string.user_id_no_name);
} }
if (userIdSplit[1] != null) { if (userIdSplit[1] != null) {
mainUserIdRest.setText(userIdSplit[1]); mainUserIdRest.setText(highlightSearchQuery(userIdSplit[1]));
} else { } else {
mainUserIdRest.setText(""); mainUserIdRest.setText("");
} }
@ -164,5 +164,4 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
public View newView(Context context, Cursor cursor, ViewGroup parent) { public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.select_key_item, null); return mInflater.inflate(R.layout.select_key_item, null);
} }
} }

View File

@ -83,7 +83,7 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
String keyIdStr = "0x" + PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId)); String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId));
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm), String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm),
cursor.getInt(mIndexKeySize)); cursor.getInt(mIndexKeySize));

View File

@ -32,6 +32,7 @@ import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Choice; import org.sufficientlysecure.keychain.util.Choice;
import java.util.ArrayList;
import java.util.Vector; import java.util.Vector;
public class CreateKeyDialogFragment extends DialogFragment { public class CreateKeyDialogFragment extends DialogFragment {
@ -78,7 +79,7 @@ public class CreateKeyDialogFragment extends DialogFragment {
boolean wouldBeMasterKey = (childCount == 0); boolean wouldBeMasterKey = (childCount == 0);
final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm); final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm);
Vector<Choice> choices = new Vector<Choice>(); ArrayList<Choice> choices = new ArrayList<Choice>();
choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString( choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString(
R.string.dsa))); R.string.dsa)));
if (!wouldBeMasterKey) { if (!wouldBeMasterKey) {

View File

@ -83,7 +83,10 @@ public class DeleteFileDialogFragment extends DialogFragment {
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance( ProgressDialogFragment deletingDialog = ProgressDialogFragment.newInstance(
R.string.progress_deleting_securely, ProgressDialog.STYLE_HORIZONTAL, false, null); getString(R.string.progress_deleting_securely),
ProgressDialog.STYLE_HORIZONTAL,
false,
null);
// Message is received after deleting is done in ApgService // Message is received after deleting is done in ApgService
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(activity, deletingDialog) { KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(activity, deletingDialog) {
@ -95,7 +98,7 @@ public class DeleteFileDialogFragment extends DialogFragment {
Toast.makeText(activity, R.string.file_delete_successful, Toast.makeText(activity, R.string.file_delete_successful,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back

View File

@ -33,7 +33,6 @@ import android.os.Message;
import android.os.Messenger; import android.os.Messenger;
import android.os.RemoteException; import android.os.RemoteException;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.text.Html;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;

View File

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

View File

@ -30,7 +30,7 @@ import android.view.KeyEvent;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
public class ProgressDialogFragment extends DialogFragment { public class ProgressDialogFragment extends DialogFragment {
private static final String ARG_MESSAGE_ID = "message_id"; private static final String ARG_MESSAGE = "message";
private static final String ARG_STYLE = "style"; private static final String ARG_STYLE = "style";
private static final String ARG_CANCELABLE = "cancelable"; private static final String ARG_CANCELABLE = "cancelable";
@ -39,16 +39,16 @@ public class ProgressDialogFragment extends DialogFragment {
/** /**
* Creates new instance of this fragment * Creates new instance of this fragment
* *
* @param messageId * @param message
* @param style * @param style
* @param cancelable * @param cancelable
* @return * @return
*/ */
public static ProgressDialogFragment newInstance(int messageId, int style, boolean cancelable, public static ProgressDialogFragment newInstance(String message, int style, boolean cancelable,
OnCancelListener onCancelListener) { OnCancelListener onCancelListener) {
ProgressDialogFragment frag = new ProgressDialogFragment(); ProgressDialogFragment frag = new ProgressDialogFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putInt(ARG_MESSAGE_ID, messageId); args.putString(ARG_MESSAGE, message);
args.putInt(ARG_STYLE, style); args.putInt(ARG_STYLE, style);
args.putBoolean(ARG_CANCELABLE, cancelable); args.putBoolean(ARG_CANCELABLE, cancelable);
@ -117,11 +117,11 @@ public class ProgressDialogFragment extends DialogFragment {
dialog.setCancelable(false); dialog.setCancelable(false);
dialog.setCanceledOnTouchOutside(false); dialog.setCanceledOnTouchOutside(false);
int messageId = getArguments().getInt(ARG_MESSAGE_ID); String message = getArguments().getString(ARG_MESSAGE);
int style = getArguments().getInt(ARG_STYLE); int style = getArguments().getInt(ARG_STYLE);
boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE); boolean cancelable = getArguments().getBoolean(ARG_CANCELABLE);
dialog.setMessage(getString(messageId)); dialog.setMessage(message);
dialog.setProgressStyle(style); dialog.setProgressStyle(style);
if (cancelable) { if (cancelable) {
@ -140,7 +140,6 @@ public class ProgressDialogFragment extends DialogFragment {
@Override @Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
return true; return true;
} }

View File

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

View File

@ -53,7 +53,6 @@ public class ShareNfcDialogFragment extends DialogFragment {
AlertDialog.Builder alert = new AlertDialog.Builder(activity); AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setIcon(android.R.drawable.ic_dialog_info);
alert.setTitle(R.string.share_nfc_dialog); alert.setTitle(R.string.share_nfc_dialog);
alert.setCancelable(true); alert.setCancelable(true);

View File

@ -34,6 +34,7 @@ import android.app.DatePickerDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.text.format.DateUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
@ -58,6 +59,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
Spinner mUsage; Spinner mUsage;
TextView mCreationDate; TextView mCreationDate;
BootstrapButton mExpiryDateButton; BootstrapButton mExpiryDateButton;
GregorianCalendar mCreatedDate;
GregorianCalendar mExpiryDate; GregorianCalendar mExpiryDate;
private int mDatePickerResultCount = 0; private int mDatePickerResultCount = 0;
@ -113,8 +115,12 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
if (date == null) { if (date == null) {
date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); 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), mExpiryDateSetListener, date.get(Calendar.YEAR), date.get(Calendar.MONTH),
date.get(Calendar.DAY_OF_MONTH)); date.get(Calendar.DAY_OF_MONTH));
mDatePickerResultCount = 0; mDatePickerResultCount = 0;
@ -129,6 +135,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(); dialog.show();
} }
}); });
@ -153,9 +174,8 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
} }
mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(key)); mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(key));
String keyId1Str = PgpKeyHelper.convertKeyIdToHex(key.getKeyID()); String keyIdStr = PgpKeyHelper.convertKeyIdToHex(key.getKeyID());
String keyId2Str = PgpKeyHelper.convertKeyIdToHex(key.getKeyID() >> 32); mKeyId.setText(keyIdStr);
mKeyId.setText(keyId1Str + " " + keyId2Str);
Vector<Choice> choices = new Vector<Choice>(); Vector<Choice> choices = new Vector<Choice>();
boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT);
@ -205,7 +225,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
cal.setTime(PgpKeyHelper.getCreationDate(key)); cal.setTime(PgpKeyHelper.getCreationDate(key));
mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime())); setCreatedDate(cal);
cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
Date expiryDate = PgpKeyHelper.getExpiryDate(key); Date expiryDate = PgpKeyHelper.getExpiryDate(key);
if (expiryDate == null) { if (expiryDate == null) {
@ -235,6 +255,15 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener {
mEditorListener = listener; 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) { private void setExpiryDate(GregorianCalendar date) {
mExpiryDate = date; mExpiryDate = date;
if (date == null) { if (date == null) {
@ -253,3 +282,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

@ -103,7 +103,9 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
} }
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
@Override @Override
protected void onFinishInflate() { protected void onFinishInflate() {
mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@ -121,7 +123,9 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
super.onFinishInflate(); super.onFinishInflate();
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
public void onDeleted(Editor editor) { public void onDeleted(Editor editor) {
this.updateEditorsVisible(); this.updateEditorsVisible();
} }
@ -131,7 +135,9 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE);
} }
/** {@inheritDoc} */ /**
* {@inheritDoc}
*/
public void onClick(View v) { public void onClick(View v) {
if (canEdit) { if (canEdit) {
switch (mType) { switch (mType) {
@ -220,26 +226,29 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
Bundle data = new Bundle(); Bundle data = new Bundle();
Boolean isMasterKey; Boolean isMasterKey;
String passPhrase; String passphrase;
if (mEditors.getChildCount() > 0) { if (mEditors.getChildCount() > 0) {
PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue(); PGPSecretKey masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue();
passPhrase = PassphraseCacheService passphrase = PassphraseCacheService
.getCachedPassphrase(mActivity, masterKey.getKeyID()); .getCachedPassphrase(mActivity, masterKey.getKeyID());
isMasterKey = false; isMasterKey = false;
} else { } else {
passPhrase = ""; passphrase = "";
isMasterKey = true; isMasterKey = true;
} }
data.putBoolean(KeychainIntentService.GENERATE_KEY_MASTER_KEY, isMasterKey); 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_ALGORITHM, mNewKeyAlgorithmChoice.getId());
data.putInt(KeychainIntentService.GENERATE_KEY_KEY_SIZE, mNewKeySize); data.putInt(KeychainIntentService.GENERATE_KEY_KEY_SIZE, mNewKeySize);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data); intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// show progress dialog // show progress dialog
mGeneratingDialog = ProgressDialogFragment.newInstance(R.string.progress_generating, mGeneratingDialog = ProgressDialogFragment.newInstance(
ProgressDialog.STYLE_SPINNER, true, new DialogInterface.OnCancelListener() { getResources().getQuantityString(R.plurals.progress_generating, 1),
ProgressDialog.STYLE_SPINNER,
true,
new DialogInterface.OnCancelListener() {
@Override @Override
public void onCancel(DialogInterface dialog) { public void onCancel(DialogInterface dialog) {
mActivity.stopService(intent); mActivity.stopService(intent);
@ -261,7 +270,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
.getByteArray(KeychainIntentService.RESULT_NEW_KEY)); .getByteArray(KeychainIntentService.RESULT_NEW_KEY));
addGeneratedKeyToView(newKey); addGeneratedKeyToView(newKey);
} }
}; }
}; };
// Create a new Messenger for the communication back // Create a new Messenger for the communication back

View File

@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.widget;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import android.widget.*;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import android.content.Context; import android.content.Context;
@ -26,11 +27,9 @@ import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import com.beardedhen.androidbootstrap.BootstrapButton; import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.helper.ContactHelper;
public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { public class UserIdEditor extends LinearLayout implements Editor, OnClickListener {
private EditorListener mEditorListener = null; private EditorListener mEditorListener = null;
@ -38,7 +37,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
private BootstrapButton mDeleteButton; private BootstrapButton mDeleteButton;
private RadioButton mIsMainUserId; private RadioButton mIsMainUserId;
private EditText mName; private EditText mName;
private EditText mEmail; private AutoCompleteTextView mEmail;
private EditText mComment; private EditText mComment;
// see http://www.regular-expressions.info/email.html // see http://www.regular-expressions.info/email.html
@ -102,9 +101,17 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene
mIsMainUserId.setOnClickListener(this); mIsMainUserId.setOnClickListener(this);
mName = (EditText) findViewById(R.id.name); mName = (EditText) findViewById(R.id.name);
mEmail = (EditText) findViewById(R.id.email); mEmail = (AutoCompleteTextView) findViewById(R.id.email);
mComment = (EditText) findViewById(R.id.comment); mComment = (EditText) findViewById(R.id.comment);
mEmail.setThreshold(1); // Start working from first character
mEmail.setAdapter(
new ArrayAdapter<String>
(this.getContext(), android.R.layout.simple_dropdown_item_1line,
ContactHelper.getMailAccounts(getContext())
));
super.onFinishInflate(); super.onFinishInflate();
} }

View File

@ -1,4 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2011 Thialfihar <thi@thialfihar.org>
* Copyright (C) 2011 Senecaso * Copyright (C) 2011 Senecaso
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -224,7 +226,7 @@ public class HkpKeyServer extends KeyServer {
HttpClient client = new DefaultHttpClient(); HttpClient client = new DefaultHttpClient();
try { try {
HttpGet get = new HttpGet("http://" + mHost + ":" + mPort HttpGet get = new HttpGet("http://" + mHost + ":" + mPort
+ "/pks/lookup?op=get&search=0x" + PgpKeyHelper.convertKeyToHex(keyId)); + "/pks/lookup?op=get&search=" + PgpKeyHelper.convertKeyIdToHex(keyId));
HttpResponse response = client.execute(get); HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {

View File

@ -1,4 +1,6 @@
/* /*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2011 Thialfihar <thi@thialfihar.org>
* Copyright (C) 2011 Senecaso * Copyright (C) 2011 Senecaso
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -16,14 +18,8 @@
package org.sufficientlysecure.keychain.util; package org.sufficientlysecure.keychain.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import android.os.Parcel;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
public abstract class KeyServer { public abstract class KeyServer {
@ -52,5 +48,5 @@ public abstract class KeyServer {
abstract String get(long keyId) throws QueryException; 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: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

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