summaryrefslogtreecommitdiff
path: root/plugins/openpgp-api-lib/openpgp-api/src/main/java
diff options
context:
space:
mode:
authortest@dangofactory.cos <you@example.com>2026-08-20 16:57:18 +0000
committertest@dangofactory.cos <you@example.com>2026-08-20 16:57:18 +0000
commit50ce5d5e6cd0d720ff437d35eace545816848428 (patch)
treeb59af206a36265cf081945e434900b32b0c43edd /plugins/openpgp-api-lib/openpgp-api/src/main/java
downloadmonocles_mail-50ce5d5e6cd0d720ff437d35eace545816848428.tar.gz
monocles_mail-50ce5d5e6cd0d720ff437d35eace545816848428.tar.bz2
monocles_mail-50ce5d5e6cd0d720ff437d35eace545816848428.zip
Inital
Diffstat (limited to 'plugins/openpgp-api-lib/openpgp-api/src/main/java')
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java131
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpApiManager.java250
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.java121
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpError.java119
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpMetadata.java148
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java238
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java664
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpKeyPreference.java334
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java62
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpServiceConnection.java124
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpUtils.java145
-rw-r--r--plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java178
12 files changed, 2514 insertions, 0 deletions
diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java
new file mode 100644
index 0000000..37a5a57
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014-2015 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 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<AutocryptPeerUpdate> CREATOR = new Creator<AutocryptPeerUpdate>() {
+ 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/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpApiManager.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpApiManager.java
new file mode 100644
index 0000000..57516d0
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpApiManager.java
@@ -0,0 +1,250 @@
+package org.openintents.openpgp;
+
+
+import android.app.PendingIntent;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle.Event;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.OnLifecycleEvent;
+import android.content.Context;
+import android.content.Intent;
+import androidx.annotation.Nullable;
+import android.text.TextUtils;
+
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
+import org.openintents.openpgp.util.OpenPgpProviderUtil;
+import org.openintents.openpgp.util.OpenPgpServiceConnection;
+import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound;
+import timber.log.Timber;
+
+
+public class OpenPgpApiManager implements LifecycleObserver {
+ private final Context context;
+
+ @Nullable
+ private String openPgpProvider;
+ @Nullable
+ private OpenPgpApiManagerCallback callback;
+
+ private OpenPgpServiceConnection openPgpServiceConnection;
+ private OpenPgpApi openPgpApi;
+ private PendingIntent userInteractionPendingIntent;
+ private OpenPgpProviderState openPgpProviderState = OpenPgpProviderState.UNCONFIGURED;
+
+ public OpenPgpApiManager(Context context, LifecycleOwner lifecycleOwner) {
+ this.context = context;
+
+ lifecycleOwner.getLifecycle().addObserver(this);
+ }
+
+ @OnLifecycleEvent(Event.ON_CREATE)
+ void onLifecycleCreate() {
+ setupCryptoProvider();
+ }
+
+ @OnLifecycleEvent(Event.ON_START)
+ void onLifecycleStart() {
+ refreshConnection();
+ }
+
+ @OnLifecycleEvent(Event.ON_DESTROY)
+ public void onLifecycleDestroy() {
+ disconnect();
+ }
+
+ public void setOpenPgpProvider(@Nullable String openPgpProvider, OpenPgpApiManagerCallback callback) {
+ if (openPgpProvider == null || !openPgpProvider.equals(this.openPgpProvider)) {
+ disconnect();
+ }
+
+ this.openPgpProvider = openPgpProvider;
+ this.callback = callback;
+
+ setupCryptoProvider();
+ }
+
+ private void setupCryptoProvider() {
+ if (TextUtils.isEmpty(openPgpProvider)) {
+ setOpenPgpProviderState(OpenPgpProviderState.UNCONFIGURED);
+ return;
+ }
+
+ boolean providerIsBound = openPgpServiceConnection != null && openPgpServiceConnection.isBound();
+ if (providerIsBound) {
+ refreshConnection();
+ return;
+ }
+
+ if (openPgpServiceConnection != null) {
+ // An OpenPgpServiceConnection has already been created, but hasn't been bound yet.
+ // We'll just wait for OnBound.onBound() to be called.
+ return;
+ }
+
+ setOpenPgpProviderState(OpenPgpProviderState.UNINITIALIZED);
+ openPgpServiceConnection = new OpenPgpServiceConnection(context, openPgpProvider, new OnBound() {
+ @Override
+ public void onBound(IOpenPgpService2 service) {
+ openPgpApi = new OpenPgpApi(context, service);
+ refreshConnection();
+ }
+
+ @Override
+ public void onError(Exception e) {
+ Timber.e(e, "error connecting to crypto provider!");
+ setOpenPgpProviderState(OpenPgpProviderState.ERROR);
+ callbackOpenPgpProviderError(OpenPgpProviderError.ConnectionFailed);
+ }
+ });
+ refreshConnection();
+ }
+
+ public void refreshConnection() {
+ boolean isOkStateButLostConnection = openPgpProviderState == OpenPgpProviderState.OK &&
+ (openPgpServiceConnection == null || !openPgpServiceConnection.isBound());
+ if (isOkStateButLostConnection) {
+ userInteractionPendingIntent = null;
+ setOpenPgpProviderState(OpenPgpProviderState.ERROR);
+ callbackOpenPgpProviderError(OpenPgpProviderError.ConnectionLost);
+ return;
+ }
+
+ if (openPgpServiceConnection == null) {
+ userInteractionPendingIntent = null;
+ setupCryptoProvider();
+ return;
+ }
+
+ if (!openPgpServiceConnection.isBound()) {
+ userInteractionPendingIntent = null;
+ Timber.d("attempting to bind to openpgp provider: %s (%s)", openPgpProvider, openPgpServiceConnection);
+ openPgpServiceConnection.bindToService();
+ return;
+ }
+
+ if (userInteractionPendingIntent != null) {
+ setOpenPgpProviderState(OpenPgpProviderState.UI_REQUIRED);
+ return;
+ }
+
+ Intent intent = new Intent(OpenPgpApi.ACTION_CHECK_PERMISSION);
+ getOpenPgpApi().executeApiAsync(intent, null, null, new IOpenPgpCallback() {
+ @Override
+ public void onReturn(Intent result) {
+ if (openPgpProviderState != OpenPgpProviderState.UNCONFIGURED) {
+ onPgpPermissionCheckResult(result);
+ }
+ }
+ });
+ }
+
+ public void onUserInteractionResult() {
+ userInteractionPendingIntent = null;
+ refreshConnection();
+ }
+
+ public PendingIntent getUserInteractionPendingIntent() {
+ return userInteractionPendingIntent;
+ }
+
+ private void onPgpPermissionCheckResult(Intent result) {
+ int resultCode = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ switch (resultCode) {
+ case OpenPgpApi.RESULT_CODE_SUCCESS:
+ setOpenPgpProviderState(OpenPgpProviderState.OK);
+ break;
+
+ case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+ userInteractionPendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
+ setOpenPgpProviderState(OpenPgpProviderState.UI_REQUIRED);
+ break;
+
+ case OpenPgpApi.RESULT_CODE_ERROR:
+ default:
+ if (result.hasExtra(OpenPgpApi.RESULT_ERROR)) {
+ OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
+ handleOpenPgpError(error);
+ } else {
+ setOpenPgpProviderState(OpenPgpProviderState.ERROR);
+ callbackOpenPgpProviderError(OpenPgpProviderError.ConnectionFailed);
+ }
+ break;
+ }
+ }
+
+ private void setOpenPgpProviderState(OpenPgpProviderState state) {
+ boolean statusChanged = openPgpProviderState != state;
+ if (statusChanged) {
+ Timber.d("callback provider status changed from %s to %s", openPgpProviderState, state);
+ openPgpProviderState = state;
+ if (callback != null) {
+ callback.onOpenPgpProviderStatusChanged();
+ }
+ }
+ }
+
+ private void handleOpenPgpError(@Nullable OpenPgpError error) {
+ Timber.e("OpenPGP Api error: %s", error);
+
+ if (error != null && error.getErrorId() == OpenPgpError.INCOMPATIBLE_API_VERSIONS) {
+ callbackOpenPgpProviderError(OpenPgpProviderError.VersionIncompatible);
+ setOpenPgpProviderState(OpenPgpProviderState.UNCONFIGURED);
+ } else {
+ callbackOpenPgpProviderError(OpenPgpProviderError.ConnectionFailed);
+ setOpenPgpProviderState(OpenPgpProviderState.ERROR);
+ }
+ }
+
+ private void callbackOpenPgpProviderError(OpenPgpProviderError providerError) {
+ Timber.d("callback provider connection error %s", providerError);
+ if (callback != null) {
+ callback.onOpenPgpProviderError(providerError);
+ }
+ }
+
+ private void disconnect() {
+ openPgpApi = null;
+ if (openPgpServiceConnection != null) {
+ openPgpServiceConnection.unbindFromService();
+ }
+ openPgpServiceConnection = null;
+ }
+
+ public OpenPgpApi getOpenPgpApi() {
+ if (openPgpServiceConnection == null || !openPgpServiceConnection.isBound()) {
+ Timber.e("Obtained OpenPgpApi object, but service is not bound! Inconsistent state?");
+ }
+ return openPgpApi;
+ }
+
+ public String getReadableOpenPgpProviderName() {
+ String openPgpProviderName =
+ OpenPgpProviderUtil.getOpenPgpProviderName(context.getPackageManager(), openPgpProvider);
+ return openPgpProviderName != null ? openPgpProviderName : openPgpProvider;
+ }
+
+ @NonNull
+ public OpenPgpProviderState getOpenPgpProviderState() {
+ return openPgpProviderState;
+ }
+
+ public enum OpenPgpProviderState {
+ UNCONFIGURED,
+ UNINITIALIZED,
+ UI_REQUIRED,
+ ERROR,
+ OK
+ }
+
+ public enum OpenPgpProviderError {
+ ConnectionFailed, ConnectionLost, VersionIncompatible
+ }
+
+ public interface OpenPgpApiManagerCallback {
+ void onOpenPgpProviderStatusChanged();
+ void onOpenPgpProviderError(OpenPgpProviderError error);
+ }
+}
diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.java
new file mode 100644
index 0000000..ee34bfd
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2015 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;
+
+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<OpenPgpDecryptionResult> CREATOR = new Creator<OpenPgpDecryptionResult>() {
+ 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/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpError.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpError.java
new file mode 100644
index 0000000..3d4a6e3
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpError.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2014-2015 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;
+
+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<OpenPgpError> CREATOR = new Creator<OpenPgpError>() {
+ 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/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpMetadata.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpMetadata.java
new file mode 100644
index 0000000..0d766c7
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpMetadata.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2014-2015 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;
+
+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<OpenPgpMetadata> CREATOR = new Creator<OpenPgpMetadata>() {
+ 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/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java
new file mode 100644
index 0000000..b4bb50f
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2014-2015 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 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<String> userIds;
+ private final ArrayList<String> confirmedUserIds;
+ private final SenderStatusResult senderStatusResult;
+
+ private OpenPgpSignatureResult(int signatureStatus, String signatureUserId, long keyId,
+ ArrayList<String> userIds, ArrayList<String> 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<String> getUserIds() {
+ return Collections.unmodifiableList(userIds);
+ }
+
+ public List<String> 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<OpenPgpSignatureResult> CREATOR = new Creator<OpenPgpSignatureResult>() {
+ 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<String> userIds, ArrayList<String> 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 extends Enum<T>> 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/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java
new file mode 100644
index 0000000..8274369
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java
@@ -0,0 +1,664 @@
+/*
+ * Copyright (C) 2014-2015 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.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.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+
+import org.openintents.openpgp.IOpenPgpService2;
+import org.openintents.openpgp.OpenPgpError;
+import org.openintents.openpgp.util.ParcelFileDescriptorUtil.DataSinkTransferThread;
+import org.openintents.openpgp.util.ParcelFileDescriptorUtil.DataSourceTransferThread;
+import timber.log.Timber;
+
+
+public class OpenPgpApi {
+
+ 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<T> {
+ void onProgress(int current, int max);
+ void onReturn(final Intent result, T sinkResult);
+ }
+
+ public interface CancelableBackgroundOperation {
+ void cancelOperation();
+ }
+
+ private class OpenPgpSourceSinkAsyncTask<T> extends AsyncTask<Void, Integer, OpenPgpDataResult<T>>
+ implements CancelableBackgroundOperation {
+ Intent data;
+ OpenPgpDataSource dataSource;
+ OpenPgpDataSink<T> dataSink;
+ IOpenPgpSinkResultCallback<T> callback;
+
+ private OpenPgpSourceSinkAsyncTask(Intent data, OpenPgpDataSource dataSource,
+ OpenPgpDataSink<T> dataSink, IOpenPgpSinkResultCallback<T> callback) {
+ this.data = data;
+ this.dataSource = dataSource;
+ this.dataSink = dataSink;
+ this.callback = callback;
+ }
+
+ @Override
+ protected OpenPgpDataResult<T> doInBackground(Void... unused) {
+ return executeApi(data, dataSource, dataSink);
+ }
+
+ protected void onPostExecute(OpenPgpDataResult<T> result) {
+ callback.onReturn(result.apiResult, result.sinkResult);
+ }
+
+ @Override
+ public void cancelOperation() {
+ cancel(true);
+ if (dataSource != null) {
+ dataSource.cancel();
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ public <T> CancelableBackgroundOperation executeApiAsync(Intent data, OpenPgpDataSource dataSource,
+ OpenPgpDataSink<T> dataSink, final IOpenPgpSinkResultCallback<T> 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<T> 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<Void> callback) {
+ OpenPgpSourceSinkAsyncTask<Void> 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<T> {
+ Intent apiResult;
+ T sinkResult;
+
+ public OpenPgpDataResult(Intent apiResult, T sinkResult) {
+ this.apiResult = apiResult;
+ this.sinkResult = sinkResult;
+ }
+ }
+
+ public <T> OpenPgpDataResult<T> executeApi(Intent data, OpenPgpDataSource dataSource, OpenPgpDataSink<T> 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<T> 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) {
+ Timber.e(e, "Exception in executeApi call");
+ 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) {
+ Timber.e(e, "Exception in executeApi call");
+ 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> {
+ 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) {
+ Timber.e(e, "Exception in executeApi call");
+ 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) {
+ Timber.e(e, "Exception in executeApi call");
+ 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) {
+ Timber.e(e, "IOException when closing ParcelFileDescriptor!");
+ }
+ }
+ }
+
+ 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/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpKeyPreference.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpKeyPreference.java
new file mode 100644
index 0000000..850f76a
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpKeyPreference.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2018 The K-9 Dog Walkers
+ * Copyright (C) 2015 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.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.res.TypedArray;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+
+import org.openintents.openpgp.OpenPgpApiManager;
+import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback;
+import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderError;
+import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState;
+import org.openintents.openpgp.OpenPgpError;
+import org.openintents.openpgp.R;
+import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
+import org.openintents.openpgp.util.OpenPgpUtils.UserId;
+import timber.log.Timber;
+
+
+public class OpenPgpKeyPreference extends Preference implements OpenPgpApiManagerCallback {
+ private long keyId;
+ private String defaultUserId;
+ private boolean showAutocryptHint;
+ private OpenPgpApiManager openPgpApiManager;
+ private Intent cachedActivityResultData;
+ private Fragment intentSenderFragment;
+
+ private PendingIntent pendingIntentSelectKey;
+ private boolean pendingIntentRunImmediately;
+
+ private String keyPrimaryUserId;
+ private long keyCreationTime;
+
+ private static final int REQUEST_CODE_API_MANAGER = 9998;
+ private static final int REQUEST_CODE_KEY_PREFERENCE = 9999;
+
+ private static final int NO_KEY = 0;
+
+ public OpenPgpKeyPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setOpenPgpProvider(OpenPgpApiManager openPgpApiManager, String openPgpProvider) {
+ this.openPgpApiManager = openPgpApiManager;
+ this.openPgpApiManager.setOpenPgpProvider(openPgpProvider, this);
+ refreshTitleAndSummary();
+ }
+
+ public void setIntentSenderFragment(Fragment fragment) {
+ intentSenderFragment = fragment;
+ }
+
+ public void setDefaultUserId(String userId) {
+ defaultUserId = userId;
+ }
+
+ public void setShowAutocryptHint(boolean showAutocryptHint) {
+ this.showAutocryptHint = showAutocryptHint;
+ }
+
+ @Override
+ protected void onClick() {
+ switch (openPgpApiManager.getOpenPgpProviderState()) {
+ // The GET_SIGN_KEY action is special, in that it can be used as an implicit registration
+ // to the API. Therefore, we can ignore the UI_REQUIRED here. If it comes up regardless,
+ // it will also work as a regular pending intent.
+ case UI_REQUIRED:
+ case OK: {
+ apiGetOrStartPendingIntent();
+ break;
+ }
+ default: {
+ refreshTitleAndSummary();
+ openPgpApiManager.refreshConnection();
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onOpenPgpProviderStatusChanged() {
+ if (openPgpApiManager.getOpenPgpProviderState() == OpenPgpProviderState.OK) {
+ apiRetrievePendingIntentAndKeyInfo();
+ } else {
+ pendingIntentSelectKey = null;
+ pendingIntentRunImmediately = false;
+ cachedActivityResultData = null;
+ refreshTitleAndSummary();
+ }
+ }
+
+ @Override
+ public void onOpenPgpProviderError(OpenPgpProviderError error) {
+ if (error == OpenPgpProviderError.ConnectionLost) {
+ openPgpApiManager.refreshConnection();
+ }
+ }
+
+ private void apiRetrievePendingIntentAndKeyInfo() {
+ Intent data;
+ if (cachedActivityResultData != null) {
+ data = cachedActivityResultData;
+ cachedActivityResultData = null;
+ } else {
+ data = new Intent();
+ }
+ apiRetrievePendingIntentAndKeyInfo(data);
+ }
+
+ private void apiRetrievePendingIntentAndKeyInfo(Intent data) {
+ data.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
+ data.putExtra(OpenPgpApi.EXTRA_USER_ID, defaultUserId);
+ data.putExtra(OpenPgpApi.EXTRA_PRESELECT_KEY_ID, keyId);
+ data.putExtra(OpenPgpApi.EXTRA_SHOW_AUTOCRYPT_HINT, showAutocryptHint);
+ OpenPgpApi api = openPgpApiManager.getOpenPgpApi();
+ api.executeApiAsync(data, null, null, openPgpCallback);
+ }
+
+ private IOpenPgpCallback openPgpCallback = new IOpenPgpCallback() {
+ @Override
+ public void onReturn(Intent result) {
+ int resultCode = result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ switch (resultCode) {
+ case OpenPgpApi.RESULT_CODE_SUCCESS:
+ case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
+ PendingIntent pendingIntentSelectKey = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
+
+ if (result.hasExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
+ long keyId = result.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, NO_KEY);
+ long keyCreationTime = result.getLongExtra("key_creation_time", 0);
+ String primaryUserId = result.getStringExtra("primary_user_id");
+
+ updateWidgetData(keyId, primaryUserId, keyCreationTime, pendingIntentSelectKey);
+ } else {
+ updateWidgetData(pendingIntentSelectKey);
+ }
+
+ break;
+ }
+ case OpenPgpApi.RESULT_CODE_ERROR: {
+ OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
+ Timber.e("RESULT_CODE_ERROR: %s", error.getMessage());
+
+ break;
+ }
+ }
+ }
+ };
+
+ private void apiGetOrStartPendingIntent() {
+ if (pendingIntentSelectKey != null) {
+ apiStartPendingIntent();
+ return;
+ }
+
+ pendingIntentRunImmediately = true;
+ apiRetrievePendingIntentAndKeyInfo();
+ }
+
+ private void apiStartPendingIntent() {
+ if (pendingIntentSelectKey == null) {
+ Timber.e("Tried to launch pending intent but didn't have any?");
+ return;
+ }
+
+ try {
+ intentSenderFragment
+ .startIntentSenderForResult(pendingIntentSelectKey.getIntentSender(), REQUEST_CODE_KEY_PREFERENCE,
+ null, 0, 0, 0, null);
+ } catch (IntentSender.SendIntentException e) {
+ Timber.e(e,"Error launching pending intent");
+ } finally {
+ pendingIntentSelectKey = null;
+ }
+ }
+
+ private void updateWidgetData(PendingIntent pendingIntentSelectKey) {
+ this.keyPrimaryUserId = null;
+ this.keyCreationTime = 0;
+ this.pendingIntentSelectKey = pendingIntentSelectKey;
+
+ maybeRunPendingIntentImmediately();
+ refreshTitleAndSummary();
+ }
+
+ private void updateWidgetData(long keyId, String primaryUserId, long keyCreationTime,
+ PendingIntent pendingIntentSelectKey) {
+ setAndPersist(keyId);
+ this.keyPrimaryUserId = primaryUserId;
+ this.keyCreationTime = keyCreationTime;
+ this.pendingIntentSelectKey = pendingIntentSelectKey;
+
+ callChangeListener(keyId);
+ maybeRunPendingIntentImmediately();
+ refreshTitleAndSummary();
+ }
+
+ private void maybeRunPendingIntentImmediately() {
+ if (!pendingIntentRunImmediately) {
+ return;
+ }
+
+ pendingIntentRunImmediately = false;
+ apiStartPendingIntent();
+ }
+
+ private void refreshTitleAndSummary() {
+ boolean isConfigured = openPgpApiManager != null &&
+ openPgpApiManager.getOpenPgpProviderState() != OpenPgpProviderState.UNCONFIGURED;
+ setEnabled(isConfigured);
+
+ if (this.keyId == NO_KEY) {
+ setTitle(R.string.openpgp_key_title);
+ setSummary(R.string.openpgp_no_key_selected);
+
+ return;
+ }
+
+ if (this.keyPrimaryUserId != null && this.keyCreationTime != 0) {
+ Context context = getContext();
+
+ UserId userId = OpenPgpUtils.splitUserId(keyPrimaryUserId);
+ if (userId.email != null) {
+ setTitle(context.getString(R.string.openpgp_key_using, userId.email));
+ } else if (userId.name != null) {
+ setTitle(context.getString(R.string.openpgp_key_using, userId.name));
+ } else {
+ setTitle(R.string.openpgp_key_using_no_name);
+ }
+
+ String creationTimeStr = DateUtils.formatDateTime(context, keyCreationTime,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME |
+ DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH);
+ setSummary(context.getString(R.string.openpgp_key_created, creationTimeStr));
+ } else {
+ setTitle(R.string.openpgp_key_title);
+ setSummary(R.string.openpgp_key_selected);
+ }
+ }
+
+ /**
+ * Public API
+ */
+ public void setValue(long keyId) {
+ setAndPersist(keyId);
+ refreshTitleAndSummary();
+ }
+
+ /**
+ * Public API
+ */
+ public long getValue() {
+ return keyId;
+ }
+
+ private void setAndPersist(long newValue) {
+ keyId = newValue;
+ notifyDependencyChange(shouldDisableDependents());
+
+ // Save to persistent storage (this method will make sure this
+ // preference should be persistent, along with other useful checks)
+ persistLong(keyId);
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ // This preference type's value type is Long, so we read the default
+ // value from the attributes as an Integer.
+ return (long) a.getInteger(index, NO_KEY);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ if (restoreValue) {
+ // Restore state
+ keyId = getPersistedLong(keyId);
+ notifyDependencyChange(shouldDisableDependents());
+ } else {
+ // Set state
+ long value = (Long) defaultValue;
+ setAndPersist(value);
+ }
+ }
+
+ @Override
+ public boolean shouldDisableDependents() {
+ return keyId == NO_KEY || super.shouldDisableDependents();
+ }
+
+ public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_API_MANAGER:
+ openPgpApiManager.onUserInteractionResult();
+ return true;
+ case REQUEST_CODE_KEY_PREFERENCE:
+ if (resultCode == Activity.RESULT_OK) {
+ cachedActivityResultData = data;
+ // this might happen early in the lifecycle (e.g. before onResume). if the provider isn't connected
+ // here, apiRetrievePendingIntentAndKeyInfo() will be called as soon as it is.
+ OpenPgpProviderState openPgpProviderState = openPgpApiManager.getOpenPgpProviderState();
+ if (openPgpProviderState == OpenPgpProviderState.OK ||
+ openPgpProviderState == OpenPgpProviderState.UI_REQUIRED) {
+ apiRetrievePendingIntentAndKeyInfo();
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpProviderUtil.java
new file mode 100644
index 0000000..b5e9b1e
--- /dev/null
+++ b/plugins/openpgp-api-lib/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<String> DISALLOWED_PROVIDERS = new ArrayList<>();
+ static {
+ DISALLOWED_PROVIDERS.add(PACKAGE_NAME_APG);
+ }
+
+ public static List<String> getOpenPgpProviderPackages(Context context) {
+ ArrayList<String> result = new ArrayList<>();
+
+ Intent intent = new Intent(OpenPgpApi.SERVICE_INTENT_2);
+ List<ResolveInfo> 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<ResolveInfo> 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 isProviderAllowed(String packageName) {
+ return !DISALLOWED_PROVIDERS.contains(packageName);
+ }
+}
diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpServiceConnection.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpServiceConnection.java
new file mode 100644
index 0000000..61ccbfe
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpServiceConnection.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014-2015 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.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/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpUtils.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpUtils.java
new file mode 100644
index 0000000..3662208
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpUtils.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014-2015 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;
+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<ResolveInfo> 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
+ * <p/>
+ * 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/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java
new file mode 100644
index 0000000..77f7ae4
--- /dev/null
+++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/ParcelFileDescriptorUtil.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * 2013 Florian Schmaus <flo@geekplace.eu>
+ *
+ * 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 org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSink;
+import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource;
+import timber.log.Timber;
+
+
+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) {
+ Timber.e(e, "IOException when writing to out");
+ } finally {
+ try {
+ mIn.close();
+ } catch (IOException ignored) {
+ }
+ try {
+ mOut.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ public static <T> DataSinkTransferThread<T> asyncPipeToDataSink(
+ OpenPgpDataSink<T> dataSink, ParcelFileDescriptor output) {
+ InputStream inputStream = new BufferedInputStream(new AutoCloseInputStream(output));
+ DataSinkTransferThread<T> 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()) {
+ Timber.d("Stopped writing because operation was cancelled.");
+ } else if (isIOExceptionCausedByEPIPE(e)) {
+ Timber.d("Stopped writing due to broken pipe (other end closed pipe?)");
+ } else {
+ Timber.e(e, "IOException when writing to out");
+ }
+ } 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<T> extends Thread {
+ final OpenPgpDataSink<T> dataSink;
+ final InputStream inputStream;
+ T sinkResult;
+
+ DataSinkTransferThread(OpenPgpDataSink<T> 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)) {
+ Timber.e("Stopped read due to broken pipe (other end closed pipe?)");
+ } else {
+ Timber.e(e, "IOException while reading from in");
+ }
+ 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;
+ }
+ }
+
+}