diff --git a/app/build.gradle b/app/build.gradle
index 4cae5a1af4..4698c30df7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -299,7 +299,8 @@ dependencies {
// https://github.com/open-keychain/openpgp-api
// https://mvnrepository.com/artifact/org.sufficientlysecure/openpgp-api
- implementation "org.sufficientlysecure:openpgp-api:$openpgp_version"
+ //implementation "org.sufficientlysecure:openpgp-api:$openpgp_version"
+ implementation project(':openpgp-api')
// https://www.sqlite.org/changes.html
// https://github.com/requery/sqlite-android/
diff --git a/openpgp-api/.gitignore b/openpgp-api/.gitignore
new file mode 100644
index 0000000000..a44cc0f0fa
--- /dev/null
+++ b/openpgp-api/.gitignore
@@ -0,0 +1,33 @@
+#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
+
+#Lint output
+lint-report.html
+lint-report_files/*
\ No newline at end of file
diff --git a/openpgp-api/build.gradle b/openpgp-api/build.gradle
new file mode 100644
index 0000000000..9f68bc4cc8
--- /dev/null
+++ b/openpgp-api/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.library'
+//apply plugin: 'bintray-release' // must be applied after your artifact generating plugin (eg. java / com.android.library)
+
+android {
+ compileSdkVersion 27
+ buildToolsVersion '27.0.3'
+
+ defaultConfig {
+ versionCode 9
+ versionName '12.0' // API-Version . minor
+ minSdkVersion 9
+ targetSdkVersion 25
+ }
+
+ // Do not abort build if lint finds errors
+ lintOptions {
+ abortOnError false
+ }
+}
+
+//publish {
+// userOrg = 'sufficientlysecure'
+// groupId = 'org.sufficientlysecure'
+// artifactId = 'openpgp-api'
+// version = '12.0'
+// description = 'The OpenPGP API provides methods to execute OpenPGP operations, such as sign, encrypt, decrypt, verify, and more without user interaction from background threads.'
+// website = 'https://github.com/open-keychain/openpgp-api'
+//}
diff --git a/openpgp-api/src/main/AndroidManifest.xml b/openpgp-api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..f1f29cf3c4
--- /dev/null
+++ b/openpgp-api/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openpgp-api/src/main/aidl/org/openintents/openpgp/IOpenPgpService.aidl b/openpgp-api/src/main/aidl/org/openintents/openpgp/IOpenPgpService.aidl
new file mode 100644
index 0000000000..3689d174bd
--- /dev/null
+++ b/openpgp-api/src/main/aidl/org/openintents/openpgp/IOpenPgpService.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ *
+ * 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 {
+
+ /**
+ * do NOT use this, data returned from the service through "output" may be truncated
+ * @deprecated
+ */
+ Intent execute(in Intent data, in ParcelFileDescriptor input, in ParcelFileDescriptor output);
+
+}
\ No newline at end of file
diff --git a/openpgp-api/src/main/aidl/org/openintents/openpgp/IOpenPgpService2.aidl b/openpgp-api/src/main/aidl/org/openintents/openpgp/IOpenPgpService2.aidl
new file mode 100644
index 0000000000..8aa4dd2e1c
--- /dev/null
+++ b/openpgp-api/src/main/aidl/org/openintents/openpgp/IOpenPgpService2.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann
+ *
+ * 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 IOpenPgpService2 {
+
+ /**
+ * see org.openintents.openpgp.util.OpenPgpApi for documentation
+ */
+ ParcelFileDescriptor createOutputPipe(in int pipeId);
+
+ /**
+ * see org.openintents.openpgp.util.OpenPgpApi for documentation
+ */
+ Intent execute(in Intent data, in ParcelFileDescriptor input, int pipeId);
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java b/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java
new file mode 100644
index 0000000000..37a5a572ce
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ *
+ * 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 java.util.Date;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+@SuppressWarnings("unused")
+public class AutocryptPeerUpdate 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.
+ */
+ private static final int PARCELABLE_VERSION = 1;
+
+
+ private final byte[] keyData;
+ private final Date effectiveDate;
+ private final PreferEncrypt preferEncrypt;
+
+
+ private AutocryptPeerUpdate(byte[] keyData, Date effectiveDate, PreferEncrypt preferEncrypt) {
+ this.keyData = keyData;
+ this.effectiveDate = effectiveDate;
+ this.preferEncrypt = preferEncrypt;
+ }
+
+ private AutocryptPeerUpdate(Parcel source, int version) {
+ this.keyData = source.createByteArray();
+ this.effectiveDate = source.readInt() != 0 ? new Date(source.readLong()) : null;
+ this.preferEncrypt = PreferEncrypt.values()[source.readInt()];
+ }
+
+
+ public static AutocryptPeerUpdate create(byte[] keyData, Date timestamp, boolean isMutual) {
+ return new AutocryptPeerUpdate(keyData, timestamp, isMutual ? PreferEncrypt.MUTUAL : PreferEncrypt.NOPREFERENCE);
+ }
+
+ public byte[] getKeyData() {
+ return keyData;
+ }
+
+ public boolean hasKeyData() {
+ return keyData != null;
+ }
+
+ public Date getEffectiveDate() {
+ return effectiveDate;
+ }
+
+ public PreferEncrypt getPreferEncrypt() {
+ return preferEncrypt;
+ }
+
+
+ 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.writeByteArray(keyData);
+ if (effectiveDate != null) {
+ dest.writeInt(1);
+ dest.writeLong(effectiveDate.getTime());
+ } else {
+ dest.writeInt(0);
+ }
+
+ dest.writeInt(preferEncrypt.ordinal());
+
+ // 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 CREATOR = new Creator() {
+ public AutocryptPeerUpdate createFromParcel(final Parcel source) {
+ int version = source.readInt(); // parcelableVersion
+ int parcelableSize = source.readInt();
+ int startPosition = source.dataPosition();
+
+ AutocryptPeerUpdate vr = new AutocryptPeerUpdate(source, version);
+
+ // skip over all fields added in future versions of this parcel
+ source.setDataPosition(startPosition + parcelableSize);
+
+ return vr;
+ }
+
+ public AutocryptPeerUpdate[] newArray(final int size) {
+ return new AutocryptPeerUpdate[size];
+ }
+ };
+
+ public enum PreferEncrypt {
+ NOPREFERENCE, MUTUAL
+ }
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.java b/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.java
new file mode 100644
index 0000000000..ee34bfd474
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann
+ *
+ * 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;
+
+public class OpenPgpDecryptionResult 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 = 2;
+
+ // content not encrypted
+ public static final int RESULT_NOT_ENCRYPTED = -1;
+ // insecure!
+ public static final int RESULT_INSECURE = 0;
+ // encrypted
+ public static final int RESULT_ENCRYPTED = 1;
+
+ public final int result;
+ public final byte[] sessionKey;
+ public final byte[] decryptedSessionKey;
+
+ public int getResult() {
+ return result;
+ }
+
+ public OpenPgpDecryptionResult(int result) {
+ this.result = result;
+ this.sessionKey = null;
+ this.decryptedSessionKey = null;
+ }
+
+ public OpenPgpDecryptionResult(int result, byte[] sessionKey, byte[] decryptedSessionKey) {
+ this.result = result;
+ if ((sessionKey == null) != (decryptedSessionKey == null)) {
+ throw new AssertionError("sessionkey must be null iff decryptedSessionKey is null");
+ }
+ this.sessionKey = sessionKey;
+ this.decryptedSessionKey = decryptedSessionKey;
+ }
+
+ public OpenPgpDecryptionResult(OpenPgpDecryptionResult b) {
+ this.result = b.result;
+ this.sessionKey = b.sessionKey;
+ this.decryptedSessionKey = b.decryptedSessionKey;
+ }
+
+ 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(result);
+ // version 2
+ dest.writeByteArray(sessionKey);
+ dest.writeByteArray(decryptedSessionKey);
+ // 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 CREATOR = new Creator() {
+ public OpenPgpDecryptionResult createFromParcel(final Parcel source) {
+ int version = source.readInt(); // parcelableVersion
+ int parcelableSize = source.readInt();
+ int startPosition = source.dataPosition();
+
+ int result = source.readInt();
+ byte[] sessionKey = version > 1 ? source.createByteArray() : null;
+ byte[] decryptedSessionKey = version > 1 ? source.createByteArray() : null;
+
+ OpenPgpDecryptionResult vr = new OpenPgpDecryptionResult(result, sessionKey, decryptedSessionKey);
+
+ // skip over all fields added in future versions of this parcel
+ source.setDataPosition(startPosition + parcelableSize);
+
+ return vr;
+ }
+
+ public OpenPgpDecryptionResult[] newArray(final int size) {
+ return new OpenPgpDecryptionResult[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "\nresult: " + result;
+ }
+
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpError.java b/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpError.java
new file mode 100644
index 0000000000..3d4a6e3890
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpError.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ *
+ * 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;
+
+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;
+ public static final int OPPORTUNISTIC_MISSING_KEYS = 4;
+
+ 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 String toString() {
+ return "OpenPGP-Api Error (" + errorId + "): " + message;
+ }
+
+ 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 CREATOR = new Creator() {
+ public OpenPgpError createFromParcel(final Parcel source) {
+ source.readInt(); // parcelableVersion
+ 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];
+ }
+ };
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpMetadata.java b/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpMetadata.java
new file mode 100644
index 0000000000..0d766c7c23
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpMetadata.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ *
+ * 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;
+
+public class OpenPgpMetadata 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 = 2;
+
+ String filename;
+ String mimeType;
+ String charset;
+ long modificationTime;
+ long originalSize;
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public long getModificationTime() {
+ return modificationTime;
+ }
+
+ public long getOriginalSize() {
+ return originalSize;
+ }
+
+ public String getCharset() {
+ return charset;
+ }
+
+ public OpenPgpMetadata() {
+ }
+
+ public OpenPgpMetadata(String filename, String mimeType, long modificationTime,
+ long originalSize, String charset) {
+ this.filename = filename;
+ this.mimeType = mimeType;
+ this.modificationTime = modificationTime;
+ this.originalSize = originalSize;
+ this.charset = charset;
+ }
+
+ public OpenPgpMetadata(String filename, String mimeType, long modificationTime,
+ long originalSize) {
+ this.filename = filename;
+ this.mimeType = mimeType;
+ this.modificationTime = modificationTime;
+ this.originalSize = originalSize;
+ }
+
+ public OpenPgpMetadata(OpenPgpMetadata b) {
+ this.filename = b.filename;
+ this.mimeType = b.mimeType;
+ this.modificationTime = b.modificationTime;
+ this.originalSize = b.originalSize;
+ }
+
+ 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.writeString(filename);
+ dest.writeString(mimeType);
+ dest.writeLong(modificationTime);
+ dest.writeLong(originalSize);
+ // version 2
+ dest.writeString(charset);
+ // 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 CREATOR = new Creator() {
+ public OpenPgpMetadata createFromParcel(final Parcel source) {
+ int version = source.readInt(); // parcelableVersion
+ int parcelableSize = source.readInt();
+ int startPosition = source.dataPosition();
+
+ OpenPgpMetadata vr = new OpenPgpMetadata();
+ vr.filename = source.readString();
+ vr.mimeType = source.readString();
+ vr.modificationTime = source.readLong();
+ vr.originalSize = source.readLong();
+ if (version >= 2) {
+ vr.charset = source.readString();
+ }
+
+ // skip over all fields added in future versions of this parcel
+ source.setDataPosition(startPosition + parcelableSize);
+
+ return vr;
+ }
+
+ public OpenPgpMetadata[] newArray(final int size) {
+ return new OpenPgpMetadata[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ String out = "\nfilename: " + filename;
+ out += "\nmimeType: " + mimeType;
+ out += "\nmodificationTime: " + modificationTime;
+ out += "\noriginalSize: " + originalSize;
+ out += "\ncharset: " + charset;
+ return out;
+ }
+
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java b/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java
new file mode 100644
index 0000000000..b4bb50fc9a
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ *
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.openintents.openpgp.util.OpenPgpUtils;
+
+@SuppressWarnings("unused")
+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.
+ */
+ private static final int PARCELABLE_VERSION = 3;
+
+ // content not signed
+ public static final int RESULT_NO_SIGNATURE = -1;
+ // invalid signature!
+ public static final int RESULT_INVALID_SIGNATURE = 0;
+ // successfully verified signature, with confirmed key
+ @Deprecated
+ public static final int RESULT_VALID_CONFIRMED = 1;
+ public static final int RESULT_VALID_KEY_CONFIRMED = 1;
+ // no key was found for this signature verification
+ public static final int RESULT_KEY_MISSING = 2;
+ // successfully verified signature, but with unconfirmed key
+ @Deprecated
+ public static final int RESULT_VALID_UNCONFIRMED = 3;
+ public static final int RESULT_VALID_KEY_UNCONFIRMED = 3;
+ // key has been revoked -> invalid signature!
+ public static final int RESULT_INVALID_KEY_REVOKED = 4;
+ // key is expired -> invalid signature!
+ public static final int RESULT_INVALID_KEY_EXPIRED = 5;
+ // insecure cryptographic algorithms/protocol -> invalid signature!
+ @Deprecated
+ public static final int RESULT_INVALID_INSECURE = 6;
+ public static final int RESULT_INVALID_KEY_INSECURE = 6;
+
+ private final int result;
+ private final long keyId;
+ private final String primaryUserId;
+ private final ArrayList userIds;
+ private final ArrayList confirmedUserIds;
+ private final SenderStatusResult senderStatusResult;
+
+ private OpenPgpSignatureResult(int signatureStatus, String signatureUserId, long keyId,
+ ArrayList userIds, ArrayList confirmedUserIds, SenderStatusResult senderStatusResult,
+ Boolean signatureOnly) {
+ this.result = signatureStatus;
+ this.primaryUserId = signatureUserId;
+ this.keyId = keyId;
+ this.userIds = userIds;
+ this.confirmedUserIds = confirmedUserIds;
+ this.senderStatusResult = senderStatusResult;
+ }
+
+ private OpenPgpSignatureResult(Parcel source, int version) {
+ this.result = source.readInt();
+ // we dropped support for signatureOnly, but need to skip the value for compatibility
+ source.readByte();
+ this.primaryUserId = source.readString();
+ this.keyId = source.readLong();
+
+ if (version > 1) {
+ this.userIds = source.createStringArrayList();
+ } else {
+ this.userIds = null;
+ }
+ if (version > 2) {
+ this.senderStatusResult = readEnumWithNullAndFallback(
+ source, SenderStatusResult.VALUES, SenderStatusResult.UNKNOWN);
+ this.confirmedUserIds = source.createStringArrayList();
+ } else {
+ this.senderStatusResult = SenderStatusResult.UNKNOWN;
+ this.confirmedUserIds = null;
+ }
+ }
+
+ public int getResult() {
+ return result;
+ }
+
+ public SenderStatusResult getSenderStatusResult() {
+ return senderStatusResult;
+ }
+
+ public String getPrimaryUserId() {
+ return primaryUserId;
+ }
+
+ public List getUserIds() {
+ return Collections.unmodifiableList(userIds);
+ }
+
+ public List getConfirmedUserIds() {
+ return Collections.unmodifiableList(confirmedUserIds);
+ }
+
+ public long getKeyId() {
+ return 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(result);
+ // signatureOnly is deprecated since version 3. we pass a dummy value for compatibility
+ dest.writeByte((byte) 0);
+ dest.writeString(primaryUserId);
+ dest.writeLong(keyId);
+ // version 2
+ dest.writeStringList(userIds);
+ // version 3
+ writeEnumWithNull(dest, senderStatusResult);
+ dest.writeStringList(confirmedUserIds);
+ // 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 CREATOR = new Creator() {
+ public OpenPgpSignatureResult createFromParcel(final Parcel source) {
+ int version = source.readInt(); // parcelableVersion
+ int parcelableSize = source.readInt();
+ int startPosition = source.dataPosition();
+
+ OpenPgpSignatureResult vr = new OpenPgpSignatureResult(source, version);
+
+ // 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 = "\nresult: " + result;
+ out += "\nprimaryUserId: " + primaryUserId;
+ out += "\nuserIds: " + userIds;
+ out += "\nkeyId: " + OpenPgpUtils.convertKeyIdToHex(keyId);
+ return out;
+ }
+
+ public static OpenPgpSignatureResult createWithValidSignature(int signatureStatus, String primaryUserId,
+ long keyId, ArrayList userIds, ArrayList confirmedUserIds, SenderStatusResult senderStatusResult) {
+ if (signatureStatus == RESULT_NO_SIGNATURE || signatureStatus == RESULT_KEY_MISSING ||
+ signatureStatus == RESULT_INVALID_SIGNATURE) {
+ throw new IllegalArgumentException("can only use this method for valid types of signatures");
+ }
+ return new OpenPgpSignatureResult(
+ signatureStatus, primaryUserId, keyId, userIds, confirmedUserIds, senderStatusResult, null);
+ }
+
+ public static OpenPgpSignatureResult createWithNoSignature() {
+ return new OpenPgpSignatureResult(RESULT_NO_SIGNATURE, null, 0L, null, null, null, null);
+ }
+
+ public static OpenPgpSignatureResult createWithKeyMissing(long keyId) {
+ return new OpenPgpSignatureResult(RESULT_KEY_MISSING, null, keyId, null, null, null, null);
+ }
+
+ public static OpenPgpSignatureResult createWithInvalidSignature() {
+ return new OpenPgpSignatureResult(RESULT_INVALID_SIGNATURE, null, 0L, null, null, null, null);
+ }
+
+ @Deprecated
+ public OpenPgpSignatureResult withSignatureOnlyFlag(boolean signatureOnly) {
+ return new OpenPgpSignatureResult(
+ result, primaryUserId, keyId, userIds, confirmedUserIds, senderStatusResult, signatureOnly);
+ }
+
+ private static > T readEnumWithNullAndFallback(Parcel source, T[] enumValues, T fallback) {
+ int valueOrdinal = source.readInt();
+ if (valueOrdinal == -1) {
+ return null;
+ }
+ if (valueOrdinal >= enumValues.length) {
+ return fallback;
+ }
+ return enumValues[valueOrdinal];
+ }
+
+ private static void writeEnumWithNull(Parcel dest, Enum> enumValue) {
+ if (enumValue == null) {
+ dest.writeInt(-1);
+ return;
+ }
+ dest.writeInt(enumValue.ordinal());
+ }
+
+ public enum SenderStatusResult {
+ // Order is significant here - only add to the end for parcelable compatibility!
+ UNKNOWN, USER_ID_CONFIRMED, USER_ID_UNCONFIRMED, USER_ID_MISSING;
+ public static final SenderStatusResult[] VALUES = values();
+ }
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java b/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java
new file mode 100644
index 0000000000..af2e17b167
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java
@@ -0,0 +1,667 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ *
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import org.openintents.openpgp.IOpenPgpService2;
+import org.openintents.openpgp.OpenPgpError;
+import org.openintents.openpgp.util.ParcelFileDescriptorUtil.DataSinkTransferThread;
+import org.openintents.openpgp.util.ParcelFileDescriptorUtil.DataSourceTransferThread;
+
+
+public class OpenPgpApi {
+
+ public static final String TAG = "OpenPgp API";
+
+ public static final String SERVICE_INTENT_2 = "org.openintents.openpgp.IOpenPgpService2";
+
+ /**
+ * see CHANGELOG.md
+ */
+ public static final int API_VERSION = 12;
+
+ /**
+ * 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)
+ */
+
+ /**
+ * This action performs no operation, but can be used to check if the App has permission
+ * to access the API in general, returning a user interaction PendingIntent otherwise.
+ * This can be used to trigger the permission dialog explicitly.
+ *
+ * This action uses no extras.
+ */
+ public static final String ACTION_CHECK_PERMISSION = "org.openintents.openpgp.action.CHECK_PERMISSION";
+
+ @Deprecated
+ public static final String ACTION_SIGN = "org.openintents.openpgp.action.SIGN";
+
+ /**
+ * Sign text resulting in a cleartext signature
+ * Some magic pre-processing of the text is done to convert it to a format usable for
+ * cleartext signatures per RFC 4880 before the text is actually signed:
+ * - end cleartext with newline
+ * - remove whitespaces on line endings
+ *
+ * required extras:
+ * long EXTRA_SIGN_KEY_ID (key id of signing key)
+ *
+ * optional extras:
+ * char[] EXTRA_PASSPHRASE (key passphrase)
+ */
+ public static final String ACTION_CLEARTEXT_SIGN = "org.openintents.openpgp.action.CLEARTEXT_SIGN";
+
+ /**
+ * Sign text or binary data resulting in a detached signature.
+ * No OutputStream necessary for ACTION_DETACHED_SIGN (No magic pre-processing like in ACTION_CLEARTEXT_SIGN)!
+ * The detached signature is returned separately in RESULT_DETACHED_SIGNATURE.
+ *
+ * required extras:
+ * long EXTRA_SIGN_KEY_ID (key id of signing key)
+ *
+ * optional extras:
+ * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for detached signature)
+ * char[] EXTRA_PASSPHRASE (key passphrase)
+ *
+ * returned extras:
+ * byte[] RESULT_DETACHED_SIGNATURE
+ * String RESULT_SIGNATURE_MICALG (contains the name of the used signature algorithm as a string)
+ */
+ public static final String ACTION_DETACHED_SIGN = "org.openintents.openpgp.action.DETACHED_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 output)
+ * char[] EXTRA_PASSPHRASE (key passphrase)
+ * String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata)
+ * boolean EXTRA_ENABLE_COMPRESSION (enable ZLIB compression, default ist true)
+ */
+ public static final String ACTION_ENCRYPT = "org.openintents.openpgp.action.ENCRYPT";
+
+ /**
+ * Sign and encrypt
+ *
+ * required extras:
+ * String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT)
+ * or
+ * long[] EXTRA_KEY_IDS
+ *
+ * optional extras:
+ * long EXTRA_SIGN_KEY_ID (key id of signing key)
+ * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output)
+ * char[] EXTRA_PASSPHRASE (key passphrase)
+ * String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata)
+ * boolean EXTRA_ENABLE_COMPRESSION (enable ZLIB compression, default ist true)
+ */
+ public static final String ACTION_SIGN_AND_ENCRYPT = "org.openintents.openpgp.action.SIGN_AND_ENCRYPT";
+
+ public static final String ACTION_QUERY_AUTOCRYPT_STATUS = "org.openintents.openpgp.action.QUERY_AUTOCRYPT_STATUS";
+
+ /**
+ * Decrypts and verifies given input stream. This methods handles encrypted-only, signed-and-encrypted,
+ * and also signed-only input.
+ * OutputStream is optional, e.g., for verifying detached signatures!
+ *
+ * If OpenPgpSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_KEY_MISSING
+ * in addition a PendingIntent is returned via RESULT_INTENT to download missing keys.
+ * On all other status, in addition a PendingIntent is returned via RESULT_INTENT to open
+ * the key view in OpenKeychain.
+ *
+ * optional extras:
+ * byte[] EXTRA_DETACHED_SIGNATURE (detached signature)
+ *
+ * returned extras:
+ * OpenPgpSignatureResult RESULT_SIGNATURE
+ * OpenPgpDecryptionResult RESULT_DECRYPTION
+ * OpenPgpDecryptMetadata RESULT_METADATA
+ * String RESULT_CHARSET (charset which was specified in the headers of ascii armored input, if any)
+ */
+ public static final String ACTION_DECRYPT_VERIFY = "org.openintents.openpgp.action.DECRYPT_VERIFY";
+
+ /**
+ * Decrypts the header of an encrypted file to retrieve metadata such as original filename.
+ *
+ * This does not decrypt the actual content of the file.
+ *
+ * returned extras:
+ * OpenPgpDecryptMetadata RESULT_METADATA
+ * String RESULT_CHARSET (charset which was specified in the headers of ascii armored input, if any)
+ */
+ public static final String ACTION_DECRYPT_METADATA = "org.openintents.openpgp.action.DECRYPT_METADATA";
+
+ /**
+ * Select key id for signing
+ *
+ * optional extras:
+ * String EXTRA_USER_ID
+ *
+ * returned extras:
+ * long EXTRA_SIGN_KEY_ID
+ */
+ public static final String ACTION_GET_SIGN_KEY_ID = "org.openintents.openpgp.action.GET_SIGN_KEY_ID";
+
+ /**
+ * Get key ids based on given user ids (=emails)
+ *
+ * required extras:
+ * String[] EXTRA_USER_IDS
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * If an Output stream has been defined the whole public key is returned.
+ * required extras:
+ * long EXTRA_KEY_ID
+ *
+ * optional extras:
+ * String EXTRA_REQUEST_ASCII_ARMOR (request that the returned key is encoded in ASCII Armor)
+ */
+ public static final String ACTION_GET_KEY = "org.openintents.openpgp.action.GET_KEY";
+
+ /**
+ * Backup all keys given by EXTRA_KEY_IDS and if requested their secret parts.
+ * The encrypted backup will be written to the OutputStream.
+ * The client app has no access to the backup code used to encrypt the backup!
+ * This operation always requires user interaction with RESULT_CODE_USER_INTERACTION_REQUIRED!
+ *
+ * required extras:
+ * long[] EXTRA_KEY_IDS (keys that should be included in the backup)
+ * boolean EXTRA_BACKUP_SECRET (also backup secret keys)
+ */
+ public static final String ACTION_BACKUP = "org.openintents.openpgp.action.BACKUP";
+
+ /**
+ * Update the status of some Autocrypt peer, identified by their peer id.
+ *
+ * required extras:
+ * String EXTRA_AUTOCRYPT_PEER_ID (autocrypt peer id to update)
+ * AutocryptPeerUpdate EXTRA_AUTOCRYPT_PEER_UPDATE (actual peer update)
+ */
+ public static final String ACTION_UPDATE_AUTOCRYPT_PEER = "org.openintents.openpgp.action.UPDATE_AUTOCRYPT_PEER";
+
+ /* Intent extras */
+ public static final String EXTRA_API_VERSION = "api_version";
+
+ // ACTION_DETACHED_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";
+
+ // ACTION_DETACHED_SIGN
+ public static final String RESULT_DETACHED_SIGNATURE = "detached_signature";
+ public static final String RESULT_SIGNATURE_MICALG = "signature_micalg";
+
+ // ENCRYPT, SIGN_AND_ENCRYPT, QUERY_AUTOCRYPT_STATUS
+ 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_SELECTED = "key_ids_selected";
+ public static final String EXTRA_SIGN_KEY_ID = "sign_key_id";
+
+ public static final String RESULT_KEYS_CONFIRMED = "keys_confirmed";
+ public static final String RESULT_AUTOCRYPT_STATUS = "autocrypt_status";
+ public static final int AUTOCRYPT_STATUS_UNAVAILABLE = 0;
+ public static final int AUTOCRYPT_STATUS_DISCOURAGE = 1;
+ public static final int AUTOCRYPT_STATUS_AVAILABLE = 2;
+ public static final int AUTOCRYPT_STATUS_MUTUAL = 3;
+
+ // optional extras:
+ public static final String EXTRA_PASSPHRASE = "passphrase";
+ public static final String EXTRA_ORIGINAL_FILENAME = "original_filename";
+ public static final String EXTRA_ENABLE_COMPRESSION = "enable_compression";
+ public static final String EXTRA_OPPORTUNISTIC_ENCRYPTION = "opportunistic";
+
+ // GET_SIGN_KEY_ID
+ public static final String EXTRA_USER_ID = "user_id";
+ public static final String EXTRA_PRESELECT_KEY_ID = "preselect_key_id";
+ public static final String EXTRA_SHOW_AUTOCRYPT_HINT = "show_autocrypt_hint";
+
+ // GET_KEY
+ public static final String EXTRA_KEY_ID = "key_id";
+ public static final String EXTRA_MINIMIZE = "minimize";
+ public static final String EXTRA_MINIMIZE_USER_ID = "minimize_user_id";
+ public static final String RESULT_KEY_IDS = "key_ids";
+
+ // AUTOCRYPT_KEY_TRANSFER
+ public static final String ACTION_AUTOCRYPT_KEY_TRANSFER = "autocrypt_key_transfer";
+
+ // BACKUP
+ public static final String EXTRA_BACKUP_SECRET = "backup_secret";
+
+ /* 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 EXTRA_DETACHED_SIGNATURE = "detached_signature";
+ public static final String EXTRA_PROGRESS_MESSENGER = "progress_messenger";
+ public static final String EXTRA_DATA_LENGTH = "data_length";
+ public static final String EXTRA_DECRYPTION_RESULT = "decryption_result";
+ public static final String EXTRA_SENDER_ADDRESS = "sender_address";
+ public static final String EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING = "support_override_crpto_warning";
+ public static final String RESULT_SIGNATURE = "signature";
+ public static final String RESULT_DECRYPTION = "decryption";
+ public static final String RESULT_METADATA = "metadata";
+ public static final String RESULT_INSECURE_DETAIL_INTENT = "insecure_detail_intent";
+ public static final String RESULT_OVERRIDE_CRYPTO_WARNING = "override_crypto_warning";
+ // This will be the charset which was specified in the headers of ascii armored input, if any
+ public static final String RESULT_CHARSET = "charset";
+
+ // UPDATE_AUTOCRYPT_PEER
+ public static final String EXTRA_AUTOCRYPT_PEER_ID = "autocrypt_peer_id";
+ public static final String EXTRA_AUTOCRYPT_PEER_UPDATE = "autocrypt_peer_update";
+ public static final String EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES = "autocrypt_peer_gossip_updates";
+
+ // INTERNAL, must not be used
+ public static final String EXTRA_CALL_UUID1 = "call_uuid1";
+ public static final String EXTRA_CALL_UUID2 = "call_uuid2";
+
+ IOpenPgpService2 mService;
+ Context mContext;
+ final AtomicInteger mPipeIdGen = new AtomicInteger();
+
+ public OpenPgpApi(Context context, IOpenPgpService2 service) {
+ this.mContext = context;
+ this.mService = service;
+ }
+
+ public interface IOpenPgpCallback {
+ void onReturn(final Intent result);
+ }
+
+ public interface IOpenPgpSinkResultCallback {
+ void onProgress(int current, int max);
+ void onReturn(final Intent result, T sinkResult);
+ }
+
+ public interface CancelableBackgroundOperation {
+ void cancelOperation();
+ }
+
+ private class OpenPgpSourceSinkAsyncTask extends AsyncTask>
+ implements CancelableBackgroundOperation {
+ Intent data;
+ OpenPgpDataSource dataSource;
+ OpenPgpDataSink dataSink;
+ IOpenPgpSinkResultCallback callback;
+
+ private OpenPgpSourceSinkAsyncTask(Intent data, OpenPgpDataSource dataSource,
+ OpenPgpDataSink dataSink, IOpenPgpSinkResultCallback callback) {
+ this.data = data;
+ this.dataSource = dataSource;
+ this.dataSink = dataSink;
+ this.callback = callback;
+ }
+
+ @Override
+ protected OpenPgpDataResult doInBackground(Void... unused) {
+ return executeApi(data, dataSource, dataSink);
+ }
+
+ protected void onPostExecute(OpenPgpDataResult result) {
+ callback.onReturn(result.apiResult, result.sinkResult);
+ }
+
+ @Override
+ public void cancelOperation() {
+ cancel(true);
+ if (dataSource != null) {
+ dataSource.cancel();
+ }
+ }
+ }
+
+ class OpenPgpAsyncTask extends AsyncTask {
+ 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);
+ }
+ }
+
+ public CancelableBackgroundOperation executeApiAsync(Intent data, OpenPgpDataSource dataSource,
+ OpenPgpDataSink dataSink, final IOpenPgpSinkResultCallback callback) {
+ Messenger messenger = new Messenger(new Handler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message message) {
+ callback.onProgress(message.arg1, message.arg2);
+ return true;
+ }
+ }));
+ data.putExtra(EXTRA_PROGRESS_MESSENGER, messenger);
+
+ OpenPgpSourceSinkAsyncTask task = new OpenPgpSourceSinkAsyncTask<>(data, dataSource, dataSink, callback);
+
+ // don't serialize async tasks!
+ // http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+
+ return task;
+ }
+
+ public AsyncTask executeApiAsync(Intent data, OpenPgpDataSource dataSource, IOpenPgpSinkResultCallback callback) {
+ OpenPgpSourceSinkAsyncTask task = new OpenPgpSourceSinkAsyncTask<>(data, dataSource, null, callback);
+
+ // don't serialize async tasks!
+ // http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+
+ return task;
+ }
+
+ 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
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ public static class OpenPgpDataResult {
+ Intent apiResult;
+ T sinkResult;
+
+ public OpenPgpDataResult(Intent apiResult, T sinkResult) {
+ this.apiResult = apiResult;
+ this.sinkResult = sinkResult;
+ }
+ }
+
+ public OpenPgpDataResult executeApi(Intent data, OpenPgpDataSource dataSource, OpenPgpDataSink dataSink) {
+ ParcelFileDescriptor input = null;
+ ParcelFileDescriptor output = null;
+ try {
+ if (dataSource != null) {
+ Long expectedSize = dataSource.getSizeForProgress();
+ if (expectedSize != null) {
+ data.putExtra(EXTRA_DATA_LENGTH, (long) expectedSize);
+ } else {
+ data.removeExtra(EXTRA_PROGRESS_MESSENGER);
+ }
+ input = dataSource.startPumpThread();
+ }
+
+ DataSinkTransferThread pumpThread = null;
+ int outputPipeId = 0;
+
+ if (dataSink != null) {
+ outputPipeId = mPipeIdGen.incrementAndGet();
+ output = mService.createOutputPipe(outputPipeId);
+ pumpThread = ParcelFileDescriptorUtil.asyncPipeToDataSink(dataSink, output);
+ }
+
+ Intent result = executeApi(data, input, outputPipeId);
+
+ if (pumpThread == null) {
+ return new OpenPgpDataResult<>(result, null);
+ }
+
+ // wait for ALL data being pumped from remote side
+ pumpThread.join();
+ return new OpenPgpDataResult<>(result, pumpThread.getResult());
+ } 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 new OpenPgpDataResult<>(result, null);
+ } finally {
+ closeLoudly(output);
+ }
+ }
+
+ public Intent executeApi(Intent data, InputStream is, OutputStream os) {
+ ParcelFileDescriptor input = null;
+ ParcelFileDescriptor output = null;
+ try {
+ if (is != null) {
+ input = ParcelFileDescriptorUtil.pipeFrom(is);
+ }
+
+ Thread pumpThread = null;
+ int outputPipeId = 0;
+
+ if (os != null) {
+ outputPipeId = mPipeIdGen.incrementAndGet();
+ output = mService.createOutputPipe(outputPipeId);
+ pumpThread = ParcelFileDescriptorUtil.pipeTo(os, output);
+ }
+
+ Intent result = executeApi(data, input, outputPipeId);
+
+ // wait for ALL data being pumped from remote side
+ if (pumpThread != null) {
+ pumpThread.join();
+ }
+
+ 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;
+ } finally {
+ closeLoudly(output);
+ }
+ }
+
+ public static abstract class OpenPgpDataSource {
+ private boolean isCancelled;
+ private ParcelFileDescriptor writeSidePfd;
+
+
+ public abstract void writeTo(OutputStream os) throws IOException;
+
+ public Long getSizeForProgress() {
+ return null;
+ }
+
+ public boolean isCancelled() {
+ return isCancelled;
+ }
+
+ public ParcelFileDescriptor startPumpThread() throws IOException {
+ if (writeSidePfd != null) {
+ throw new IllegalStateException("startPumpThread() must only be called once!");
+ }
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ ParcelFileDescriptor readSidePfd = pipe[0];
+ writeSidePfd = pipe[1];
+
+ new DataSourceTransferThread(this, new ParcelFileDescriptor.AutoCloseOutputStream(writeSidePfd)).start();
+
+ return readSidePfd;
+ }
+
+ private void cancel() {
+ isCancelled = true;
+ try {
+ writeSidePfd.close();
+ } catch (IOException e) {
+ // this is fine
+ }
+ }
+ }
+
+ public interface OpenPgpDataSink {
+ T processData(InputStream is) throws IOException;
+ }
+
+ public Intent executeApi(Intent data, OpenPgpDataSource dataSource, OutputStream os) {
+ ParcelFileDescriptor input = null;
+ ParcelFileDescriptor output;
+ try {
+ if (dataSource != null) {
+ Long expectedSize = dataSource.getSizeForProgress();
+ if (expectedSize != null) {
+ data.putExtra(EXTRA_DATA_LENGTH, (long) expectedSize);
+ } else {
+ data.removeExtra(EXTRA_PROGRESS_MESSENGER);
+ }
+ input = dataSource.startPumpThread();
+ }
+
+ Thread pumpThread = null;
+ int outputPipeId = 0;
+
+ if (os != null) {
+ outputPipeId = mPipeIdGen.incrementAndGet();
+ output = mService.createOutputPipe(outputPipeId);
+ pumpThread = ParcelFileDescriptorUtil.pipeTo(os, output);
+ }
+
+ Intent result = executeApi(data, input, outputPipeId);
+
+ // wait for ALL data being pumped from remote side
+ if (pumpThread != null) {
+ pumpThread.join();
+ }
+
+ 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;
+ }
+ }
+
+ /**
+ * InputStream and OutputStreams are always closed after operating on them!
+ */
+ private Intent executeApi(Intent data, ParcelFileDescriptor input, int outputPipeId) {
+ try {
+ // always send version from client
+ data.putExtra(EXTRA_API_VERSION, OpenPgpApi.API_VERSION);
+
+ Intent result;
+
+ // blocks until result is ready
+ result = mService.execute(data, input, outputPipeId);
+
+ // 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;
+ } finally {
+ // close() is required to halt the TransferThread
+ closeLoudly(input);
+ }
+ }
+
+ private static void closeLoudly(ParcelFileDescriptor input) {
+ if (input != null) {
+ try {
+ input.close();
+ } catch (IOException e) {
+ Log.e(OpenPgpApi.TAG, "IOException when closing ParcelFileDescriptor!", e);
+ }
+ }
+ }
+
+ public interface PermissionPingCallback {
+ void onPgpPermissionCheckResult(Intent result);
+ }
+
+ public void checkPermissionPing(final PermissionPingCallback permissionPingCallback) {
+ Intent intent = new Intent(OpenPgpApi.ACTION_CHECK_PERMISSION);
+ executeApiAsync(intent, null, null, new IOpenPgpCallback() {
+ @Override
+ public void onReturn(Intent result) {
+ permissionPingCallback.onPgpPermissionCheckResult(result);
+ }
+ });
+ }
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java b/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java
new file mode 100644
index 0000000000..d412fdfac3
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java
@@ -0,0 +1,62 @@
+package org.openintents.openpgp.util;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+
+public class OpenPgpProviderUtil {
+ private static final String PACKAGE_NAME_APG = "org.thialfihar.android.apg";
+ private static final ArrayList PROVIDER_BLACKLIST = new ArrayList<>();
+ static {
+ PROVIDER_BLACKLIST.add(PACKAGE_NAME_APG);
+ }
+
+ public static List getOpenPgpProviderPackages(Context context) {
+ ArrayList result = new ArrayList<>();
+
+ Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT_2);
+ List resInfo = context.getPackageManager().queryIntentServices(intent, 0);
+ if (resInfo == null) {
+ return result;
+ }
+
+ for (ResolveInfo resolveInfo : resInfo) {
+ if (resolveInfo.serviceInfo == null) {
+ continue;
+ }
+
+ result.add(resolveInfo.serviceInfo.packageName);
+ }
+
+ return result;
+ }
+
+ public static String getOpenPgpProviderName(PackageManager packageManager, String openPgpProvider) {
+ Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT_2);
+ intent.setPackage(openPgpProvider);
+ List resInfo = packageManager.queryIntentServices(intent, 0);
+ if (resInfo == null) {
+ return null;
+ }
+
+ for (ResolveInfo resolveInfo : resInfo) {
+ if (resolveInfo.serviceInfo == null) {
+ continue;
+ }
+
+ return String.valueOf(resolveInfo.serviceInfo.loadLabel(packageManager));
+ }
+
+ return null;
+ }
+
+ public static boolean isBlacklisted(String packageName) {
+ return PROVIDER_BLACKLIST.contains(packageName);
+ }
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpServiceConnection.java b/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpServiceConnection.java
new file mode 100644
index 0000000000..61ccbfe6d2
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpServiceConnection.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ *
+ * 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import org.openintents.openpgp.IOpenPgpService2;
+
+public class OpenPgpServiceConnection {
+
+ // callback interface
+ public interface OnBound {
+ void onBound(IOpenPgpService2 service);
+
+ void onError(Exception e);
+ }
+
+ private Context mApplicationContext;
+
+ private IOpenPgpService2 mService;
+ private String mProviderPackageName;
+
+ private OnBound mOnBoundListener;
+
+ /**
+ * Create new connection
+ *
+ * @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 connection with callback
+ *
+ * @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(context, providerPackageName);
+ this.mOnBoundListener = onBoundListener;
+ }
+
+ public IOpenPgpService2 getService() {
+ return mService;
+ }
+
+ public boolean isBound() {
+ return (mService != null);
+ }
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = IOpenPgpService2.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 void bindToService() {
+ // if not already bound...
+ if (mService == null) {
+ try {
+ Intent serviceIntent = new Intent(OpenPgpApi.SERVICE_INTENT_2);
+ // NOTE: setPackage is very important to restrict the intent to this provider only!
+ serviceIntent.setPackage(mProviderPackageName);
+ boolean connect = mApplicationContext.bindService(serviceIntent, mServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ if (!connect) {
+ throw new Exception("bindService() returned false!");
+ }
+ } catch (Exception e) {
+ if (mOnBoundListener != null) {
+ mOnBoundListener.onError(e);
+ }
+ }
+ } else {
+ // already bound, but also inform client about it with callback
+ if (mOnBoundListener != null) {
+ mOnBoundListener.onBound(mService);
+ }
+ }
+ }
+
+ public void unbindFromService() {
+ mApplicationContext.unbindService(mServiceConnection);
+ }
+
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpUtils.java b/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpUtils.java
new file mode 100644
index 0000000000..3662208291
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpUtils.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ *
+ * 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;
+import android.text.TextUtils;
+
+public class OpenPgpUtils {
+
+ public static final Pattern PGP_MESSAGE = Pattern.compile(
+ "(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
+ Pattern.DOTALL);
+
+ public static final String PGP_MARKER_CLEARSIGN_BEGIN_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----";
+ public static final String PGP_MARKER_CLEARSIGN_BEGIN_SIGNATURE = "-----BEGIN PGP SIGNATURE-----";
+
+ 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) {
+ return parseMessage(message, false);
+ }
+
+ public static int parseMessage(String message, boolean anchorToStart) {
+ Matcher matcherSigned = PGP_SIGNED_MESSAGE.matcher(message);
+ Matcher matcherMessage = PGP_MESSAGE.matcher(message);
+
+ if (anchorToStart ? matcherMessage.matches() : matcherMessage.find()) {
+ return PARSE_RESULT_MESSAGE;
+ } else if (anchorToStart ? matcherSigned.matches() : matcherSigned.find()) {
+ return PARSE_RESULT_SIGNED_MESSAGE;
+ } else {
+ return PARSE_RESULT_NO_PGP;
+ }
+ }
+
+ public static boolean isAvailable(Context context) {
+ Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT_2);
+ List resInfo = context.getPackageManager().queryIntentServices(intent, 0);
+ return !resInfo.isEmpty();
+ }
+
+ 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.ENGLISH);
+ while (hexString.length() < 8) {
+ hexString = "0" + hexString;
+ }
+ return hexString;
+ }
+
+ public static String extractClearsignedMessage(String text) {
+ if (text == null || !text.startsWith(PGP_MARKER_CLEARSIGN_BEGIN_MESSAGE)) {
+ return null;
+ }
+ int endOfHeader = text.indexOf("\r\n\r\n") +4;
+ if (endOfHeader < 0) {
+ return null;
+ }
+ int endOfCleartext = text.indexOf(PGP_MARKER_CLEARSIGN_BEGIN_SIGNATURE);
+ if (endOfCleartext < 0) {
+ endOfCleartext = text.length();
+ }
+
+ return text.substring(endOfHeader, endOfCleartext);
+ }
+
+ private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
+
+ /**
+ * Splits userId string into naming part, email part, and comment part
+ *
+ * User ID matching:
+ * http://fiddle.re/t4p6f
+ *
+ * @param userId
+ * @return theParsedUserInfo
+ */
+ public static UserId splitUserId(final String userId) {
+ if (!TextUtils.isEmpty(userId)) {
+ final Matcher matcher = USER_ID_PATTERN.matcher(userId);
+ if (matcher.matches()) {
+ return new UserId(matcher.group(1), matcher.group(3), matcher.group(2));
+ }
+ }
+ return new UserId(null, null, null);
+ }
+
+ /**
+ * Returns a composed user id. Returns null if name is null!
+ */
+ public static String createUserId(UserId userId) {
+ String userIdString = userId.name; // consider name a required value
+ if (userIdString != null && !TextUtils.isEmpty(userId.comment)) {
+ userIdString += " (" + userId.comment + ")";
+ }
+ if (userIdString != null && !TextUtils.isEmpty(userId.email)) {
+ userIdString += " <" + userId.email + ">";
+ }
+
+ return userIdString;
+ }
+
+ public static class UserId {
+ public final String name;
+ public final String email;
+ public final String comment;
+
+ public UserId(String name, String email, String comment) {
+ this.name = name;
+ this.email = email;
+ this.comment = comment;
+ }
+ }
+}
diff --git a/openpgp-api/src/main/java/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java b/openpgp-api/src/main/java/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java
new file mode 100644
index 0000000000..514e1cdc1a
--- /dev/null
+++ b/openpgp-api/src/main/java/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann
+ * 2013 Florian Schmaus
+ *
+ * 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.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSink;
+import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource;
+
+
+public class ParcelFileDescriptorUtil {
+
+ public static ParcelFileDescriptor pipeFrom(InputStream inputStream)
+ throws IOException {
+ ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+ ParcelFileDescriptor readSide = pipe[0];
+ ParcelFileDescriptor writeSide = pipe[1];
+
+ new TransferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writeSide))
+ .start();
+
+ return readSide;
+ }
+
+ public static TransferThread pipeTo(OutputStream outputStream, ParcelFileDescriptor output) {
+
+ AutoCloseInputStream InputStream = new AutoCloseInputStream(output);
+ TransferThread t = new TransferThread(InputStream, outputStream);
+
+ t.start();
+ return t;
+ }
+
+ static class TransferThread extends Thread {
+ final InputStream mIn;
+ final OutputStream mOut;
+
+ TransferThread(InputStream in, OutputStream out) {
+ super("IPC Transfer Thread");
+ mIn = in;
+ mOut = out;
+ setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ byte[] buf = new byte[4096];
+ int len;
+
+ try {
+ while ((len = mIn.read(buf)) > 0) {
+ mOut.write(buf, 0, len);
+ }
+ } catch (IOException e) {
+ Log.e(OpenPgpApi.TAG, "IOException when writing to out", e);
+ } finally {
+ try {
+ mIn.close();
+ } catch (IOException ignored) {
+ }
+ try {
+ mOut.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ public static DataSinkTransferThread asyncPipeToDataSink(
+ OpenPgpDataSink dataSink, ParcelFileDescriptor output) {
+ InputStream inputStream = new BufferedInputStream(new AutoCloseInputStream(output));
+ DataSinkTransferThread dataSinkTransferThread = new DataSinkTransferThread<>(dataSink, inputStream);
+ dataSinkTransferThread.start();
+ return dataSinkTransferThread;
+ }
+
+ static class DataSourceTransferThread extends Thread {
+ final OpenPgpDataSource dataSource;
+ final OutputStream outputStream;
+
+ DataSourceTransferThread(OpenPgpDataSource dataSource, OutputStream outputStream) {
+ super("IPC Transfer Thread (TO service)");
+ this.dataSource = dataSource;
+ this.outputStream = outputStream;
+ setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ try {
+ dataSource.writeTo(outputStream);
+ } catch (IOException e) {
+ if (dataSource.isCancelled()) {
+ Log.d(OpenPgpApi.TAG, "Stopped writing because operation was cancelled.");
+ } else if (isIOExceptionCausedByEPIPE(e)) {
+ Log.d(OpenPgpApi.TAG, "Stopped writing due to broken pipe (other end closed pipe?)");
+ } else {
+ Log.e(OpenPgpApi.TAG, "IOException when writing to out", e);
+ }
+ } finally {
+ try {
+ outputStream.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ private static boolean isIOExceptionCausedByEPIPE(IOException e) {
+ Throwable cause = e.getCause();
+ return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE;
+ }
+
+ static class DataSinkTransferThread extends Thread {
+ final OpenPgpDataSink dataSink;
+ final InputStream inputStream;
+ T sinkResult;
+
+ DataSinkTransferThread(OpenPgpDataSink dataSink, InputStream inputStream) {
+ super("IPC Transfer Thread (FROM service)");
+ this.dataSink = dataSink;
+ this.inputStream = inputStream;
+ setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ try {
+ sinkResult = dataSink.processData(inputStream);
+ } catch (IOException e) {
+ if (isIOExceptionCausedByEPIPE(e)) {
+ Log.e(OpenPgpApi.TAG, "Stopped read due to broken pipe (other end closed pipe?)");
+ } else {
+ Log.e(OpenPgpApi.TAG, "IOException while reading from in", e);
+ }
+ sinkResult = null;
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ T getResult() {
+ if (isAlive()) {
+ throw new IllegalStateException("result must be accessed only *after* the thread finished execution!");
+ }
+ return sinkResult;
+ }
+ }
+
+}
diff --git a/openpgp-api/src/main/res/drawable-hdpi/ic_action_cancel_launchersize.png b/openpgp-api/src/main/res/drawable-hdpi/ic_action_cancel_launchersize.png
new file mode 100644
index 0000000000..71b9118dc0
Binary files /dev/null and b/openpgp-api/src/main/res/drawable-hdpi/ic_action_cancel_launchersize.png differ
diff --git a/openpgp-api/src/main/res/drawable-hdpi/ic_action_cancel_launchersize_light.png b/openpgp-api/src/main/res/drawable-hdpi/ic_action_cancel_launchersize_light.png
new file mode 100644
index 0000000000..73b1d08f3b
Binary files /dev/null and b/openpgp-api/src/main/res/drawable-hdpi/ic_action_cancel_launchersize_light.png differ
diff --git a/openpgp-api/src/main/res/drawable-mdpi/ic_action_cancel_launchersize.png b/openpgp-api/src/main/res/drawable-mdpi/ic_action_cancel_launchersize.png
new file mode 100644
index 0000000000..270abf45fd
Binary files /dev/null and b/openpgp-api/src/main/res/drawable-mdpi/ic_action_cancel_launchersize.png differ
diff --git a/openpgp-api/src/main/res/drawable-mdpi/ic_action_cancel_launchersize_light.png b/openpgp-api/src/main/res/drawable-mdpi/ic_action_cancel_launchersize_light.png
new file mode 100644
index 0000000000..d841821c88
Binary files /dev/null and b/openpgp-api/src/main/res/drawable-mdpi/ic_action_cancel_launchersize_light.png differ
diff --git a/openpgp-api/src/main/res/drawable-xhdpi/ic_action_cancel_launchersize.png b/openpgp-api/src/main/res/drawable-xhdpi/ic_action_cancel_launchersize.png
new file mode 100644
index 0000000000..1e3571fa54
Binary files /dev/null and b/openpgp-api/src/main/res/drawable-xhdpi/ic_action_cancel_launchersize.png differ
diff --git a/openpgp-api/src/main/res/drawable-xhdpi/ic_action_cancel_launchersize_light.png b/openpgp-api/src/main/res/drawable-xhdpi/ic_action_cancel_launchersize_light.png
new file mode 100644
index 0000000000..d505046b4d
Binary files /dev/null and b/openpgp-api/src/main/res/drawable-xhdpi/ic_action_cancel_launchersize_light.png differ
diff --git a/openpgp-api/src/main/res/drawable-xxhdpi/ic_action_cancel_launchersize.png b/openpgp-api/src/main/res/drawable-xxhdpi/ic_action_cancel_launchersize.png
new file mode 100644
index 0000000000..52044601e4
Binary files /dev/null and b/openpgp-api/src/main/res/drawable-xxhdpi/ic_action_cancel_launchersize.png differ
diff --git a/openpgp-api/src/main/res/drawable-xxhdpi/ic_action_cancel_launchersize_light.png b/openpgp-api/src/main/res/drawable-xxhdpi/ic_action_cancel_launchersize_light.png
new file mode 100644
index 0000000000..d6fb86bdd1
Binary files /dev/null and b/openpgp-api/src/main/res/drawable-xxhdpi/ic_action_cancel_launchersize_light.png differ
diff --git a/openpgp-api/src/main/res/values-ar/strings.xml b/openpgp-api/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000000..c757504ac2
--- /dev/null
+++ b/openpgp-api/src/main/res/values-ar/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/openpgp-api/src/main/res/values-bg/strings.xml b/openpgp-api/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000000..c757504ac2
--- /dev/null
+++ b/openpgp-api/src/main/res/values-bg/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/openpgp-api/src/main/res/values-cs/strings.xml b/openpgp-api/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000000..c9fe1fab7d
--- /dev/null
+++ b/openpgp-api/src/main/res/values-cs/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Žádný
+ Instalovat OpenKeychain pomocí %s
+
diff --git a/openpgp-api/src/main/res/values-de/strings.xml b/openpgp-api/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000000..01574edbdf
--- /dev/null
+++ b/openpgp-api/src/main/res/values-de/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Keine Auswahl
+ Installiere OpenKeychain über %s
+ Kein Schlüssel ausgewählt
+ Schlüssel wurde ausgewählt
+
diff --git a/openpgp-api/src/main/res/values-es/strings.xml b/openpgp-api/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000000..b83a6ca975
--- /dev/null
+++ b/openpgp-api/src/main/res/values-es/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Ninguno
+ Instalar OpenKeychain mediante %s
+ No se seleccionó clave
+ Se ha seleccionado clave
+
diff --git a/openpgp-api/src/main/res/values-et/strings.xml b/openpgp-api/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000000..c757504ac2
--- /dev/null
+++ b/openpgp-api/src/main/res/values-et/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/openpgp-api/src/main/res/values-eu/strings.xml b/openpgp-api/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000000..b143f9f41a
--- /dev/null
+++ b/openpgp-api/src/main/res/values-eu/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Ezer ez
+ Ezarri OpenKeychain %s bidez
+ Ez da giltzarik hautatu
+ Giltza hautatu da
+
diff --git a/openpgp-api/src/main/res/values-fi/strings.xml b/openpgp-api/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000000..c757504ac2
--- /dev/null
+++ b/openpgp-api/src/main/res/values-fi/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/openpgp-api/src/main/res/values-fr/strings.xml b/openpgp-api/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000000..4792138e15
--- /dev/null
+++ b/openpgp-api/src/main/res/values-fr/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Aucun
+ Installer OpenKeychain par %s
+ Aucune clef sélectionnée
+ La clef a été sélectionnée
+
diff --git a/openpgp-api/src/main/res/values-is/strings.xml b/openpgp-api/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000000..c757504ac2
--- /dev/null
+++ b/openpgp-api/src/main/res/values-is/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/openpgp-api/src/main/res/values-it/strings.xml b/openpgp-api/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000000..23e8e8013f
--- /dev/null
+++ b/openpgp-api/src/main/res/values-it/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Nessuno
+ Installa OpenKeychain via %s
+
diff --git a/openpgp-api/src/main/res/values-ja/strings.xml b/openpgp-api/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000000..ab9ba21863
--- /dev/null
+++ b/openpgp-api/src/main/res/values-ja/strings.xml
@@ -0,0 +1,7 @@
+
+
+ 無し
+ %s 経由でOpenKeychainをインストール
+ 鍵が選択されていません
+ 鍵は選択済みです
+
diff --git a/openpgp-api/src/main/res/values-nl/strings.xml b/openpgp-api/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000000..50938038c6
--- /dev/null
+++ b/openpgp-api/src/main/res/values-nl/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Geen
+ Installeer OpenKeychain via %s
+ Geen sleutel geselecteerd
+ Sleutel is geselecteerd
+
diff --git a/openpgp-api/src/main/res/values-pl/strings.xml b/openpgp-api/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000000..f42bfd0419
--- /dev/null
+++ b/openpgp-api/src/main/res/values-pl/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Brak
+ Instaluj OpenKeychain przez %s
+
diff --git a/openpgp-api/src/main/res/values-pt/strings.xml b/openpgp-api/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000000..c757504ac2
--- /dev/null
+++ b/openpgp-api/src/main/res/values-pt/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/openpgp-api/src/main/res/values-ro/strings.xml b/openpgp-api/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000000..c757504ac2
--- /dev/null
+++ b/openpgp-api/src/main/res/values-ro/strings.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/openpgp-api/src/main/res/values-ru/strings.xml b/openpgp-api/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000000..33e4277cd4
--- /dev/null
+++ b/openpgp-api/src/main/res/values-ru/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Нет
+ Установить OpenKeychain через %s
+ Ключ не выбран
+ Ключ был выбран
+
diff --git a/openpgp-api/src/main/res/values-sl/strings.xml b/openpgp-api/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000000..74d49493e3
--- /dev/null
+++ b/openpgp-api/src/main/res/values-sl/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Brez
+ Namesti OpenKeychain prek %s
+ Izbran ni noben ključ
+ Ključ je bil izbran
+
diff --git a/openpgp-api/src/main/res/values-sr/strings.xml b/openpgp-api/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000000..4535f70e8b
--- /dev/null
+++ b/openpgp-api/src/main/res/values-sr/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Ништа
+ Инсталирај Отворени кључарник преко %s
+ Није изабран кључ
+ Кључ је изабран
+
diff --git a/openpgp-api/src/main/res/values-sv/strings.xml b/openpgp-api/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000000..1efa961a9a
--- /dev/null
+++ b/openpgp-api/src/main/res/values-sv/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Ingen
+ Installera OpenKeychain via %s
+
diff --git a/openpgp-api/src/main/res/values-tr/strings.xml b/openpgp-api/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000000..a5f518dfa6
--- /dev/null
+++ b/openpgp-api/src/main/res/values-tr/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Hiç
+ %s aracılığıyla OpenKeychain\'i yükle
+
diff --git a/openpgp-api/src/main/res/values-uk/strings.xml b/openpgp-api/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000000..baf600a9f7
--- /dev/null
+++ b/openpgp-api/src/main/res/values-uk/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Жоден
+ Встановити OpenKeychain через %s
+
diff --git a/openpgp-api/src/main/res/values-zh-rTW/strings.xml b/openpgp-api/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000000..b911092548
--- /dev/null
+++ b/openpgp-api/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,4 @@
+
+
+ 無
+
diff --git a/openpgp-api/src/main/res/values-zh/strings.xml b/openpgp-api/src/main/res/values-zh/strings.xml
new file mode 100644
index 0000000000..ce62ff0f5d
--- /dev/null
+++ b/openpgp-api/src/main/res/values-zh/strings.xml
@@ -0,0 +1,5 @@
+
+
+ 尚未选择密钥
+ 已经选中密钥
+
diff --git a/openpgp-api/src/main/res/values/strings.xml b/openpgp-api/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..b4722e0c66
--- /dev/null
+++ b/openpgp-api/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+
+
+ None
+ Install OpenKeychain via %s
+
+ "Configure end-to-end key"
+ No end-to-end key selected
+ End-to-end key selected
+ "Using key: %s"
+ "Using key: ]]>"
+ "Created %s"
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index e7b4def49c..fd78ac7fec 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,2 @@
-include ':app'
+include ':app', ':openpgp-api'
+project(':openpgp-api').projectDir = new File('openpgp-api')