Initial import from main repo

Dominik Schürmann created

Change summary

.gitignore                                                     |  29 
AndroidManifest.xml                                            |  13 
LICENSE                                                        | 202 ++
build.gradle                                                   |  35 
build.xml                                                      |  92 +
proguard-project.txt                                           |  20 
project.properties                                             |  15 
res/drawable-hdpi/ic_action_cancel_launchersize.png            |   0 
res/drawable-hdpi/ic_action_cancel_launchersize_light.png      |   0 
res/drawable-mdpi/ic_action_cancel_launchersize.png            |   0 
res/drawable-mdpi/ic_action_cancel_launchersize_light.png      |   0 
res/drawable-xhdpi/ic_action_cancel_launchersize.png           |   0 
res/drawable-xhdpi/ic_action_cancel_launchersize_light.png     |   0 
res/drawable-xxhdpi/ic_action_cancel_launchersize.png          |   0 
res/drawable-xxhdpi/ic_action_cancel_launchersize_light.png    |   0 
res/values/strings.xml                                         |   7 
src/org/openintents/openpgp/IOpenPgpService.aidl               |  24 
src/org/openintents/openpgp/OpenPgpError.java                  | 118 +
src/org/openintents/openpgp/OpenPgpSignatureResult.java        | 163 ++
src/org/openintents/openpgp/util/OpenPgpApi.java               | 270 ++++
src/org/openintents/openpgp/util/OpenPgpListPreference.java    | 257 +++
src/org/openintents/openpgp/util/OpenPgpServiceConnection.java | 118 +
src/org/openintents/openpgp/util/OpenPgpUtils.java             |  76 +
src/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java | 103 +
24 files changed, 1,542 insertions(+)

Detailed changes

.gitignore 🔗

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

AndroidManifest.xml 🔗

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

LICENSE 🔗

@@ -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.

build.gradle 🔗

@@ -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.10.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
+    }
+}

build.xml 🔗

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

proguard-project.txt 🔗

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

project.properties 🔗

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

res/values/strings.xml 🔗

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="openpgp_list_preference_none">None</string>
+    <string name="openpgp_install_openkeychain_via">Install OpenKeychain via %s</string>
+
+</resources>

src/org/openintents/openpgp/IOpenPgpService.aidl 🔗

@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ 
+package org.openintents.openpgp;
+
+interface IOpenPgpService {
+
+    // see OpenPgpApi for documentation
+    Intent execute(in Intent data, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
+
+}

src/org/openintents/openpgp/OpenPgpError.java 🔗

@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openintents.openpgp;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable versioning has been copied from Dashclock Widget
+ * https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java
+ */
+public class OpenPgpError implements Parcelable {
+    /**
+     * Since there might be a case where new versions of the client using the library getting
+     * old versions of the protocol (and thus old versions of this class), we need a versioning
+     * system for the parcels sent between the clients and the providers.
+     */
+    public static final int PARCELABLE_VERSION = 1;
+
+    // possible values for errorId
+    public static final int CLIENT_SIDE_ERROR = -1;
+    public static final int GENERIC_ERROR = 0;
+    public static final int INCOMPATIBLE_API_VERSIONS = 1;
+    public static final int NO_OR_WRONG_PASSPHRASE = 2;
+    public static final int NO_USER_IDS = 3;
+
+    int errorId;
+    String message;
+
+    public OpenPgpError() {
+    }
+
+    public OpenPgpError(int errorId, String message) {
+        this.errorId = errorId;
+        this.message = message;
+    }
+
+    public OpenPgpError(OpenPgpError b) {
+        this.errorId = b.errorId;
+        this.message = b.message;
+    }
+
+    public int getErrorId() {
+        return errorId;
+    }
+
+    public void setErrorId(int errorId) {
+        this.errorId = errorId;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        /**
+         * NOTE: When adding fields in the process of updating this API, make sure to bump
+         * {@link #PARCELABLE_VERSION}.
+         */
+        dest.writeInt(PARCELABLE_VERSION);
+        // Inject a placeholder that will store the parcel size from this point on
+        // (not including the size itself).
+        int sizePosition = dest.dataPosition();
+        dest.writeInt(0);
+        int startPosition = dest.dataPosition();
+        // version 1
+        dest.writeInt(errorId);
+        dest.writeString(message);
+        // Go back and write the size
+        int parcelableSize = dest.dataPosition() - startPosition;
+        dest.setDataPosition(sizePosition);
+        dest.writeInt(parcelableSize);
+        dest.setDataPosition(startPosition + parcelableSize);
+    }
+
+    public static final Creator<OpenPgpError> CREATOR = new Creator<OpenPgpError>() {
+        public OpenPgpError createFromParcel(final Parcel source) {
+            int parcelableVersion = source.readInt();
+            int parcelableSize = source.readInt();
+            int startPosition = source.dataPosition();
+
+            OpenPgpError error = new OpenPgpError();
+            error.errorId = source.readInt();
+            error.message = source.readString();
+
+            // skip over all fields added in future versions of this parcel
+            source.setDataPosition(startPosition + parcelableSize);
+
+            return error;
+        }
+
+        public OpenPgpError[] newArray(final int size) {
+            return new OpenPgpError[size];
+        }
+    };
+}

src/org/openintents/openpgp/OpenPgpSignatureResult.java 🔗

@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openintents.openpgp;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.openintents.openpgp.util.OpenPgpUtils;
+
+import java.util.Locale;
+
+/**
+ * Parcelable versioning has been copied from Dashclock Widget
+ * https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java
+ */
+public class OpenPgpSignatureResult implements Parcelable {
+    /**
+     * Since there might be a case where new versions of the client using the library getting
+     * old versions of the protocol (and thus old versions of this class), we need a versioning
+     * system for the parcels sent between the clients and the providers.
+     */
+    public static final int PARCELABLE_VERSION = 1;
+
+    // generic error on signature verification
+    public static final int SIGNATURE_ERROR = 0;
+    // successfully verified signature, with certified public key
+    public static final int SIGNATURE_SUCCESS_CERTIFIED = 1;
+    // no public key was found for this signature verification
+    public static final int SIGNATURE_UNKNOWN_PUB_KEY = 2;
+    // successfully verified signature, but with uncertified public key
+    public static final int SIGNATURE_SUCCESS_UNCERTIFIED = 3;
+
+    int status;
+    boolean signatureOnly;
+    String userId;
+    long keyId;
+
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+    public boolean isSignatureOnly() {
+        return signatureOnly;
+    }
+
+    public void setSignatureOnly(boolean signatureOnly) {
+        this.signatureOnly = signatureOnly;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public long getKeyId() {
+        return keyId;
+    }
+
+    public void setKeyId(long keyId) {
+        this.keyId = keyId;
+    }
+
+    public OpenPgpSignatureResult() {
+
+    }
+
+    public OpenPgpSignatureResult(int signatureStatus, String signatureUserId,
+                                  boolean signatureOnly, long keyId) {
+        this.status = signatureStatus;
+        this.signatureOnly = signatureOnly;
+        this.userId = signatureUserId;
+        this.keyId = keyId;
+    }
+
+    public OpenPgpSignatureResult(OpenPgpSignatureResult b) {
+        this.status = b.status;
+        this.userId = b.userId;
+        this.signatureOnly = b.signatureOnly;
+        this.keyId = b.keyId;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        /**
+         * NOTE: When adding fields in the process of updating this API, make sure to bump
+         * {@link #PARCELABLE_VERSION}.
+         */
+        dest.writeInt(PARCELABLE_VERSION);
+        // Inject a placeholder that will store the parcel size from this point on
+        // (not including the size itself).
+        int sizePosition = dest.dataPosition();
+        dest.writeInt(0);
+        int startPosition = dest.dataPosition();
+        // version 1
+        dest.writeInt(status);
+        dest.writeByte((byte) (signatureOnly ? 1 : 0));
+        dest.writeString(userId);
+        dest.writeLong(keyId);
+        // Go back and write the size
+        int parcelableSize = dest.dataPosition() - startPosition;
+        dest.setDataPosition(sizePosition);
+        dest.writeInt(parcelableSize);
+        dest.setDataPosition(startPosition + parcelableSize);
+    }
+
+    public static final Creator<OpenPgpSignatureResult> CREATOR = new Creator<OpenPgpSignatureResult>() {
+        public OpenPgpSignatureResult createFromParcel(final Parcel source) {
+            int parcelableVersion = source.readInt();
+            int parcelableSize = source.readInt();
+            int startPosition = source.dataPosition();
+
+            OpenPgpSignatureResult vr = new OpenPgpSignatureResult();
+            vr.status = source.readInt();
+            vr.signatureOnly = source.readByte() == 1;
+            vr.userId = source.readString();
+            vr.keyId = source.readLong();
+
+            // skip over all fields added in future versions of this parcel
+            source.setDataPosition(startPosition + parcelableSize);
+
+            return vr;
+        }
+
+        public OpenPgpSignatureResult[] newArray(final int size) {
+            return new OpenPgpSignatureResult[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        String out = new String();
+        out += "\nstatus: " + status;
+        out += "\nuserId: " + userId;
+        out += "\nsignatureOnly: " + signatureOnly;
+        out += "\nkeyId: " + OpenPgpUtils.convertKeyIdToHex(keyId);
+        return out;
+    }
+
+}

src/org/openintents/openpgp/util/OpenPgpApi.java 🔗

@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openintents.openpgp.util;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import org.openintents.openpgp.IOpenPgpService;
+import org.openintents.openpgp.OpenPgpError;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class OpenPgpApi {
+
+    public static final String TAG = "OpenPgp API";
+
+    public static final int API_VERSION = 3;
+    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
+     * <p/>
+     * optional extras:
+     * boolean       EXTRA_REQUEST_ASCII_ARMOR   (request ascii armor for ouput)
+     * String        EXTRA_PASSPHRASE            (key passphrase)
+     */
+    public static final String ACTION_SIGN = "org.openintents.openpgp.action.SIGN";
+
+    /**
+     * Encrypt
+     * <p/>
+     * 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
+     * <p/>
+     * optional extras:
+     * boolean       EXTRA_REQUEST_ASCII_ARMOR   (request ascii armor for ouput)
+     * String        EXTRA_PASSPHRASE            (key passphrase)
+     */
+    public static final String ACTION_ENCRYPT = "org.openintents.openpgp.action.ENCRYPT";
+
+    /**
+     * Sign and encrypt
+     * <p/>
+     * 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
+     * <p/>
+     * 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.
+     * <p/>
+     * If OpenPgpSignatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY
+     * in addition a PendingIntent is returned via RESULT_INTENT to download missing keys.
+     * <p/>
+     * optional extras:
+     * boolean       EXTRA_REQUEST_ASCII_ARMOR   (request ascii armor for ouput)
+     * <p/>
+     * returned extras:
+     * OpenPgpSignatureResult   RESULT_SIGNATURE
+     */
+    public static final String ACTION_DECRYPT_VERIFY = "org.openintents.openpgp.action.DECRYPT_VERIFY";
+
+    /**
+     * Get key ids based on given user ids (=emails)
+     * <p/>
+     * required extras:
+     * String[]      EXTRA_USER_IDS
+     * <p/>
+     * returned extras:
+     * long[]        RESULT_KEY_IDS
+     */
+    public static final String ACTION_GET_KEY_IDS = "org.openintents.openpgp.action.GET_KEY_IDS";
+
+    /**
+     * This action returns RESULT_CODE_SUCCESS if the OpenPGP Provider already has the key
+     * corresponding to the given key id in its database.
+     * <p/>
+     * 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.
+     * <p/>
+     * 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_ACCOUNT_NAME = "account_name";
+
+    // SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
+    // request ASCII Armor for output
+    // OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
+    public static final String EXTRA_REQUEST_ASCII_ARMOR = "ascii_armor";
+
+    // ENCRYPT, SIGN_AND_ENCRYPT
+    public static final String EXTRA_USER_IDS = "user_ids";
+    public static final String EXTRA_KEY_IDS = "key_ids";
+    // optional extras:
+    public static final String EXTRA_PASSPHRASE = "passphrase";
+
+    // GET_KEY
+    public static final String EXTRA_KEY_ID = "key_id";
+    public static final String RESULT_KEY_IDS = "key_ids";
+
+    /* Service Intent returns */
+    public static final String RESULT_CODE = "result_code";
+
+    // get actual error object from RESULT_ERROR
+    public static final int RESULT_CODE_ERROR = 0;
+    // success!
+    public static final int RESULT_CODE_SUCCESS = 1;
+    // get PendingIntent from RESULT_INTENT, start PendingIntent with startIntentSenderForResult,
+    // and execute service method again in onActivityResult
+    public static final int RESULT_CODE_USER_INTERACTION_REQUIRED = 2;
+
+    public static final String RESULT_ERROR = "error";
+    public static final String RESULT_INTENT = "intent";
+
+    // DECRYPT_VERIFY
+    public static final String RESULT_SIGNATURE = "signature";
+
+    IOpenPgpService mService;
+    Context mContext;
+
+    public OpenPgpApi(Context context, IOpenPgpService service) {
+        this.mContext = context;
+        this.mService = service;
+    }
+
+    public interface IOpenPgpCallback {
+        void onReturn(final Intent result);
+    }
+
+    private class OpenPgpAsyncTask extends AsyncTask<Void, Integer, Intent> {
+        Intent data;
+        InputStream is;
+        OutputStream os;
+        IOpenPgpCallback callback;
+
+        private OpenPgpAsyncTask(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) {
+            this.data = data;
+            this.is = is;
+            this.os = os;
+            this.callback = callback;
+        }
+
+        @Override
+        protected Intent doInBackground(Void... unused) {
+            return executeApi(data, is, os);
+        }
+
+        protected void onPostExecute(Intent result) {
+            callback.onReturn(result);
+        }
+
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public void executeApiAsync(Intent data, InputStream is, OutputStream os, IOpenPgpCallback callback) {
+        OpenPgpAsyncTask task = new OpenPgpAsyncTask(data, is, os, callback);
+
+        // don't serialize async tasks!
+        // http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+        } else {
+            task.execute((Void[]) null);
+        }
+    }
+
+    public Intent executeApi(Intent data, InputStream is, OutputStream os) {
+        try {
+            data.putExtra(EXTRA_API_VERSION, OpenPgpApi.API_VERSION);
+
+            Intent result;
+
+            // pipe the input and output
+            ParcelFileDescriptor input = null;
+            if (is != null) {
+                input = ParcelFileDescriptorUtil.pipeFrom(is,
+                        new ParcelFileDescriptorUtil.IThreadListener() {
+
+                            @Override
+                            public void onThreadFinished(Thread thread) {
+                                //Log.d(OpenPgpApi.TAG, "Copy to service finished");
+                            }
+                        }
+                );
+            }
+            ParcelFileDescriptor output = null;
+            if (os != null) {
+                output = ParcelFileDescriptorUtil.pipeTo(os,
+                        new ParcelFileDescriptorUtil.IThreadListener() {
+
+                            @Override
+                            public void onThreadFinished(Thread thread) {
+                                //Log.d(OpenPgpApi.TAG, "Service finished writing!");
+                            }
+                        }
+                );
+            }
+
+            // blocks until result is ready
+            result = mService.execute(data, input, output);
+            // close() is required to halt the TransferThread
+            if (output != null) {
+                output.close();
+            }
+            // TODO: close input?
+
+            // set class loader to current context to allow unparcelling
+            // of OpenPgpError and OpenPgpSignatureResult
+            // http://stackoverflow.com/a/3806769
+            result.setExtrasClassLoader(mContext.getClassLoader());
+
+            return result;
+        } catch (Exception e) {
+            Log.e(OpenPgpApi.TAG, "Exception in executeApi call", e);
+            Intent result = new Intent();
+            result.putExtra(RESULT_CODE, RESULT_CODE_ERROR);
+            result.putExtra(RESULT_ERROR,
+                    new OpenPgpError(OpenPgpError.CLIENT_SIDE_ERROR, e.getMessage()));
+            return result;
+        }
+    }
+
+}

src/org/openintents/openpgp/util/OpenPgpListPreference.java 🔗

@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openintents.openpgp.util;
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+import org.openintents.openpgp.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Does not extend ListPreference, but is very similar to it!
+ * http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/preference/ListPreference.java/?v=source
+ */
+public class OpenPgpListPreference extends DialogPreference {
+    private static final String OPENKEYCHAIN_PACKAGE = "org.sufficientlysecure.keychain";
+    private static final String MARKET_INTENT_URI_BASE = "market://details?id=%s";
+    private static final Intent MARKET_INTENT = new Intent(Intent.ACTION_VIEW, Uri.parse(
+            String.format(MARKET_INTENT_URI_BASE, OPENKEYCHAIN_PACKAGE)));
+
+    private ArrayList<OpenPgpProviderEntry> mLegacyList = new ArrayList<OpenPgpProviderEntry>();
+    private ArrayList<OpenPgpProviderEntry> mList = new ArrayList<OpenPgpProviderEntry>();
+
+    private String mSelectedPackage;
+
+    public OpenPgpListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public OpenPgpListPreference(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Public method to add new entries for legacy applications
+     *
+     * @param packageName
+     * @param simpleName
+     * @param icon
+     */
+    public void addLegacyProvider(int position, String packageName, String simpleName, Drawable icon) {
+        mLegacyList.add(position, new OpenPgpProviderEntry(packageName, simpleName, icon));
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(Builder builder) {
+        mList.clear();
+        
+        // add "none"-entry
+        mList.add(0, new OpenPgpProviderEntry("",
+                getContext().getString(R.string.openpgp_list_preference_none),
+                getContext().getResources().getDrawable(R.drawable.ic_action_cancel_launchersize)));
+        
+        // add all additional (legacy) providers
+        mList.addAll(mLegacyList);
+        
+        // search for OpenPGP providers...
+        ArrayList<OpenPgpProviderEntry> providerList = new ArrayList<OpenPgpProviderEntry>();
+        Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT);
+        List<ResolveInfo> resInfo = getContext().getPackageManager().queryIntentServices(intent, 0);
+        if (!resInfo.isEmpty()) {
+            for (ResolveInfo resolveInfo : resInfo) {
+                if (resolveInfo.serviceInfo == null)
+                    continue;
+
+                String packageName = resolveInfo.serviceInfo.packageName;
+                String simpleName = String.valueOf(resolveInfo.serviceInfo.loadLabel(getContext()
+                        .getPackageManager()));
+                Drawable icon = resolveInfo.serviceInfo.loadIcon(getContext().getPackageManager());
+
+                providerList.add(new OpenPgpProviderEntry(packageName, simpleName, icon));
+            }
+        }
+
+        if (providerList.isEmpty()) {
+            // add install links if provider list is empty
+            resInfo = getContext().getPackageManager().queryIntentActivities
+                    (MARKET_INTENT, 0);
+            for (ResolveInfo resolveInfo : resInfo) {
+                Intent marketIntent = new Intent(MARKET_INTENT);
+                marketIntent.setPackage(resolveInfo.activityInfo.packageName);
+                Drawable icon = resolveInfo.activityInfo.loadIcon(getContext().getPackageManager());
+                String marketName = String.valueOf(resolveInfo.activityInfo.applicationInfo
+                        .loadLabel(getContext().getPackageManager()));
+                String simpleName = String.format(getContext().getString(R.string
+                        .openpgp_install_openkeychain_via), marketName);
+                mList.add(new OpenPgpProviderEntry(OPENKEYCHAIN_PACKAGE, simpleName,
+                        icon, marketIntent));
+            }
+        } else {
+            // add provider
+            mList.addAll(providerList);
+        }
+
+        // Init ArrayAdapter with OpenPGP Providers
+        ListAdapter adapter = new ArrayAdapter<OpenPgpProviderEntry>(getContext(),
+                android.R.layout.select_dialog_singlechoice, android.R.id.text1, mList) {
+            public View getView(int position, View convertView, ViewGroup parent) {
+                // User super class to create the View
+                View v = super.getView(position, convertView, parent);
+                TextView tv = (TextView) v.findViewById(android.R.id.text1);
+
+                // Put the image on the TextView
+                tv.setCompoundDrawablesWithIntrinsicBounds(mList.get(position).icon, null,
+                        null, null);
+
+                // Add margin between image and text (support various screen densities)
+                int dp10 = (int) (10 * getContext().getResources().getDisplayMetrics().density + 0.5f);
+                tv.setCompoundDrawablePadding(dp10);
+
+                return v;
+            }
+        };
+
+        builder.setSingleChoiceItems(adapter, getIndexOfProviderList(getValue()),
+                new DialogInterface.OnClickListener() {
+
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        OpenPgpProviderEntry entry = mList.get(which);
+
+                        if (entry.intent != null) {
+                            /*
+                             * Intents are called as activity
+                             *
+                             * Current approach is to assume the user installed the app.
+                             * If he does not, the selected package is not valid.
+                             *
+                             * However  applications should always consider this could happen,
+                             * as the user might remove the currently used OpenPGP app.
+                             */
+                            getContext().startActivity(entry.intent);
+                        }
+
+                        mSelectedPackage = entry.packageName;
+
+                        /*
+                         * Clicking on an item simulates the positive button click, and dismisses
+                         * the dialog.
+                         */
+                        OpenPgpListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
+                        dialog.dismiss();
+                    }
+                });
+
+        /*
+         * The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
+         * dialog instead of the user having to press 'Ok'.
+         */
+        builder.setPositiveButton(null, null);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+
+        if (positiveResult && (mSelectedPackage != null)) {
+            if (callChangeListener(mSelectedPackage)) {
+                setValue(mSelectedPackage);
+            }
+        }
+    }
+
+    private int getIndexOfProviderList(String packageName) {
+        for (OpenPgpProviderEntry app : mList) {
+            if (app.packageName.equals(packageName)) {
+                return mList.indexOf(app);
+            }
+        }
+
+        return -1;
+    }
+
+    public void setValue(String packageName) {
+        mSelectedPackage = packageName;
+        persistString(packageName);
+    }
+
+    public String getValue() {
+        return mSelectedPackage;
+    }
+
+    public String getEntry() {
+        return getEntryByValue(mSelectedPackage);
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getString(index);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        setValue(restoreValue ? getPersistedString(mSelectedPackage) : (String) defaultValue);
+    }
+
+    public String getEntryByValue(String packageName) {
+        for (OpenPgpProviderEntry app : mList) {
+            if (app.packageName.equals(packageName)) {
+                return app.simpleName;
+            }
+        }
+
+        return null;
+    }
+
+    private static class OpenPgpProviderEntry {
+        private String packageName;
+        private String simpleName;
+        private Drawable icon;
+        private Intent intent;
+
+        public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon) {
+            this.packageName = packageName;
+            this.simpleName = simpleName;
+            this.icon = icon;
+        }
+
+        public OpenPgpProviderEntry(String packageName, String simpleName, Drawable icon, Intent intent) {
+            this(packageName, simpleName, icon);
+            this.intent = intent;
+        }
+
+        @Override
+        public String toString() {
+            return simpleName;
+        }
+    }
+}

src/org/openintents/openpgp/util/OpenPgpServiceConnection.java 🔗

@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openintents.openpgp.util;
+
+import org.openintents.openpgp.IOpenPgpService;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+public class OpenPgpServiceConnection {
+
+    // interface to create callbacks for onServiceConnected
+    public interface OnBound {
+        public void onBound(IOpenPgpService service);
+    }
+
+    private Context mApplicationContext;
+
+    private IOpenPgpService mService;
+    private String mProviderPackageName;
+
+    private OnBound mOnBoundListener;
+
+    /**
+     * Create new OpenPgpServiceConnection
+     *
+     * @param context
+     * @param providerPackageName specify package name of OpenPGP provider,
+     *                            e.g., "org.sufficientlysecure.keychain"
+     */
+    public OpenPgpServiceConnection(Context context, String providerPackageName) {
+        this.mApplicationContext = context.getApplicationContext();
+        this.mProviderPackageName = providerPackageName;
+    }
+
+    /**
+     * Create new OpenPgpServiceConnection
+     *
+     * @param context
+     * @param providerPackageName specify package name of OpenPGP provider,
+     *                            e.g., "org.sufficientlysecure.keychain"
+     * @param onBoundListener     callback, executed when connection to service has been established
+     */
+    public OpenPgpServiceConnection(Context context, String providerPackageName,
+                                    OnBound onBoundListener) {
+        this.mApplicationContext = context.getApplicationContext();
+        this.mProviderPackageName = providerPackageName;
+        this.mOnBoundListener = onBoundListener;
+    }
+
+    public IOpenPgpService getService() {
+        return mService;
+    }
+
+    public boolean isBound() {
+        return (mService != null);
+    }
+
+    private ServiceConnection mServiceConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mService = IOpenPgpService.Stub.asInterface(service);
+            if (mOnBoundListener != null) {
+                mOnBoundListener.onBound(mService);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            mService = null;
+        }
+    };
+
+    /**
+     * If not already bound, bind to service!
+     *
+     * @return
+     */
+    public boolean bindToService() {
+        // if not already bound...
+        if (mService == null) {
+            try {
+                Intent serviceIntent = new Intent();
+                serviceIntent.setAction(IOpenPgpService.class.getName());
+                // NOTE: setPackage is very important to restrict the intent to this provider only!
+                serviceIntent.setPackage(mProviderPackageName);
+                mApplicationContext.bindService(serviceIntent, mServiceConnection,
+                        Context.BIND_AUTO_CREATE);
+
+                return true;
+            } catch (Exception e) {
+                return false;
+            }
+        } else {
+            return true;
+        }
+    }
+
+    public void unbindFromService() {
+        mApplicationContext.unbindService(mServiceConnection);
+    }
+
+}

src/org/openintents/openpgp/util/OpenPgpUtils.java 🔗

@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openintents.openpgp.util;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+public class OpenPgpUtils {
+
+    public static final Pattern PGP_MESSAGE = Pattern.compile(
+            ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
+            Pattern.DOTALL);
+
+    public static final Pattern PGP_SIGNED_MESSAGE = Pattern.compile(
+            ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
+            Pattern.DOTALL);
+
+    public static final int PARSE_RESULT_NO_PGP = -1;
+    public static final int PARSE_RESULT_MESSAGE = 0;
+    public static final int PARSE_RESULT_SIGNED_MESSAGE = 1;
+
+    public static int parseMessage(String message) {
+        Matcher matcherSigned = PGP_SIGNED_MESSAGE.matcher(message);
+        Matcher matcherMessage = PGP_MESSAGE.matcher(message);
+
+        if (matcherMessage.matches()) {
+            return PARSE_RESULT_MESSAGE;
+        } else if (matcherSigned.matches()) {
+            return PARSE_RESULT_SIGNED_MESSAGE;
+        } else {
+            return PARSE_RESULT_NO_PGP;
+        }
+    }
+
+    public static boolean isAvailable(Context context) {
+        Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT);
+        List<ResolveInfo> resInfo = context.getPackageManager().queryIntentServices(intent, 0);
+        if (!resInfo.isEmpty()) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public static String convertKeyIdToHex(long keyId) {
+        return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId);
+    }
+
+    private static String convertKeyIdToHex32bit(long keyId) {
+        String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US);
+        while (hexString.length() < 8) {
+            hexString = "0" + hexString;
+        }
+        return hexString;
+    }
+}

src/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java 🔗

@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *               2013 Flow (http://stackoverflow.com/questions/18212152/transfer-inputstream-to-another-service-across-process-boundaries-with-parcelf)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openintents.openpgp.util;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class ParcelFileDescriptorUtil {
+
+    public interface IThreadListener {
+        void onThreadFinished(final Thread thread);
+    }
+
+    public static ParcelFileDescriptor pipeFrom(InputStream inputStream, IThreadListener listener)
+            throws IOException {
+        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+        ParcelFileDescriptor readSide = pipe[0];
+        ParcelFileDescriptor writeSide = pipe[1];
+
+        // start the transfer thread
+        new TransferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writeSide),
+                listener)
+                .start();
+
+        return readSide;
+    }
+
+    public static ParcelFileDescriptor pipeTo(OutputStream outputStream, IThreadListener listener)
+            throws IOException {
+        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+        ParcelFileDescriptor readSide = pipe[0];
+        ParcelFileDescriptor writeSide = pipe[1];
+
+        // start the transfer thread
+        new TransferThread(new ParcelFileDescriptor.AutoCloseInputStream(readSide), outputStream,
+                listener)
+                .start();
+
+        return writeSide;
+    }
+
+    static class TransferThread extends Thread {
+        final InputStream mIn;
+        final OutputStream mOut;
+        final IThreadListener mListener;
+
+        TransferThread(InputStream in, OutputStream out, IThreadListener listener) {
+            super("ParcelFileDescriptor Transfer Thread");
+            mIn = in;
+            mOut = out;
+            mListener = listener;
+            setDaemon(true);
+        }
+
+        @Override
+        public void run() {
+            byte[] buf = new byte[1024];
+            int len;
+
+            try {
+                while ((len = mIn.read(buf)) > 0) {
+                    mOut.write(buf, 0, len);
+                }
+                mOut.flush(); // just to be safe
+            } catch (IOException e) {
+                //Log.e(OpenPgpApi.TAG, "TransferThread" + getId() + ": writing failed", e);
+            } finally {
+                try {
+                    mIn.close();
+                } catch (IOException e) {
+                    //Log.e(OpenPgpApi.TAG, "TransferThread" + getId(), e);
+                }
+                try {
+                    mOut.close();
+                } catch (IOException e) {
+                    //Log.e(OpenPgpApi.TAG, "TransferThread" + getId(), e);
+                }
+            }
+            if (mListener != null) {
+                //Log.d(OpenPgpApi.TAG, "TransferThread " + getId() + " finished!");
+                mListener.onThreadFinished(this);
+            }
+        }
+    }
+}