net.inbox.InboxGPG.java Source code

Java tutorial

Introduction

Here is the source code for net.inbox.InboxGPG.java

Source

/**
 * InboxGPG interacts with OpenKeychain encryption package.
 * Copyright (C) 2016-2017  ITPROJECTS
 * Copyright (C) 2013-2015 Dominik Schrmann <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 net.inbox;

import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Base64;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import net.inbox.dialogs.Dialogs;
import net.inbox.server.Utils;

import org.openintents.openpgp.IOpenPgpService2;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;

public class InboxGPG extends AppCompatActivity {

    public static final int NO_KEY = 0;
    public static final int REQUEST_CODE_CLEARTEXT_SIGN = 9910;
    public static final int REQUEST_CODE_ENCRYPT = 9911;
    public static final int REQUEST_CODE_SIGN_AND_ENCRYPT = 9912;
    public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913;
    public static final int REQUEST_CODE_GET_KEY_IDS = 9915;
    public static final int REQUEST_CODE_DETACHED_SIGN = 9916;
    public static final int REQUEST_CODE_DECRYPT_AND_VERIFY_DETACHED = 9917;
    public static final int REQUEST_CODE_KEY_PREFERENCE = 9999;

    private static long l_sign_key_id;
    private static long[] rcpt_keys = null;

    private int intent_request_code = 0;

    private boolean msg_ready;
    private boolean msg_encrypted;
    private boolean msg_signed;
    private String msg_contents;// given message
    private String msg_crypto;// encrypted/decrypted message
    private String msg_signature;// signature for verification or return
    private String msg_integrity;// message integrity check

    private String open_pgp_provider = "org.sufficientlysecure.keychain";
    private String[] gpg_actions = null;
    private String[] rcpt_mailboxes = null;
    private ArrayList<String> attachment_paths = null;

    private CheckBox cb_sign_to_self;
    private TextView tv_signing_key;
    private TextView tv_recipients_pick;
    private TextView tv_recipients_count;
    private TextView tv_recipients_list;
    private TextView tv_message;
    private TextView tv_cipher_text;
    private TextView tv_signature;
    private Spinner spinner_gpg_action;

    private OpenPgpServiceConnection open_pgp_service_connection;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.openpgp);

        Toolbar tb = (Toolbar) findViewById(R.id.send_toolbar);
        setSupportActionBar(tb);

        // Find the title
        TextView tv_t;
        for (int i = 0; i < tb.getChildCount(); ++i) {
            int idd = tb.getChildAt(i).getId();
            if (idd == -1) {
                tv_t = (TextView) tb.getChildAt(i);
                tv_t.setTextColor(ContextCompat.getColor(this, R.color.color_title));
                tv_t.setTypeface(Pager.tf);
                break;
            }
        }

        if (getSupportActionBar() != null) {
            String s_title = getString(R.string.open_pgp_dialog_title);
            getSupportActionBar().setTitle(s_title.toUpperCase());
        }

        TextView tv_reset = (TextView) findViewById(R.id.tv_reset);
        tv_reset.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                l_sign_key_id = 0;
                tv_signing_key.setText(getString(R.string.open_pgp_signing_key_cross));
                rcpt_keys = null;
                tv_recipients_pick.setText(getString(R.string.open_pgp_rcpt_key_cross));
                reset_activity();
            }
        });

        TextView tv_ready = (TextView) findViewById(R.id.tv_ready);
        tv_ready.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                if (msg_ready) {
                    Intent intent_a = getIntent();
                    Bundle b = new Bundle();
                    b.putInt("ret-code", intent_request_code);
                    switch (intent_request_code) {
                    case 91:// encrypt and/or sign
                        msg_crypto = pgp_mime_serialization();
                        b.putString("message-crypto", msg_crypto);
                        break;
                    case 92:// decrypted and verified
                        if (msg_crypto != null) {
                            b.putString("message-crypto", msg_crypto);
                        }
                        b.putString("msg-signature", msg_signature);
                        break;
                    case 93:// verified clear text signature
                        b.putString("msg-signature", msg_signature);
                        break;
                    }
                    intent_a.putExtras(b);
                    setResult(19091, intent_a);
                    finish();
                }
            }
        });

        tv_signing_key = (TextView) findViewById(R.id.tv_pick_sign_key);
        tv_recipients_pick = (TextView) findViewById(R.id.tv_recipients_pick);
        tv_recipients_count = (TextView) findViewById(R.id.tv_recipients_count);
        tv_recipients_list = (TextView) findViewById(R.id.tv_recipients_list);
        tv_message = (TextView) findViewById(R.id.tv_message);
        tv_cipher_text = (TextView) findViewById(R.id.tv_encrypted);
        tv_signature = (TextView) findViewById(R.id.tv_signature);

        // Obtain request code
        intent_request_code = getIntent().getIntExtra("request-code", 0);

        init_ui();
    }

    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(R.anim.right_in, R.anim.right_out);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // Try again after user interaction
        if (resultCode == RESULT_OK) {
            /*
             * The data originally given to one of the methods above, is again
             * returned here to be used when calling the method again after user
             * interaction. The Intent now also contains results from the user
             * interaction, for example selected key ids.
             */
            switch (requestCode) {
            case REQUEST_CODE_CLEARTEXT_SIGN: {
                cleartext_sign(data);
                break;
            }
            case REQUEST_CODE_DETACHED_SIGN: {
                detached_sign(data);
                break;
            }
            case REQUEST_CODE_ENCRYPT: {
                encrypt(data);
                break;
            }
            case REQUEST_CODE_SIGN_AND_ENCRYPT: {
                sign_and_encrypt(data);
                break;
            }
            case REQUEST_CODE_DECRYPT_AND_VERIFY: {
                decrypt_and_verify(data);
                break;
            }
            case REQUEST_CODE_GET_KEY_IDS: {
                get_rcpt_keys(data);
                break;
            }
            case REQUEST_CODE_DECRYPT_AND_VERIFY_DETACHED: {
                decrypt_and_verify_detached(data);
                break;
            }
            case REQUEST_CODE_KEY_PREFERENCE: {
                if (data == null) {
                    l_sign_key_id = NO_KEY;
                    tv_signing_key.setText(R.string.open_pgp_signing_key_cross);
                } else {
                    l_sign_key_id = data.getLongExtra("sign_key_id", -1);
                    tv_signing_key.setText(R.string.open_pgp_signing_key_check);
                }
                break;
            }
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (open_pgp_service_connection != null)
            open_pgp_service_connection.unbindFromService();
    }

    private void init_ui() {
        switch (intent_request_code) {
        case 91: {
            // Encryption options
            gpg_actions = new String[] { getString(R.string.open_pgp_list_items_encrypt),
                    getString(R.string.open_pgp_list_items_sign_encrypt),
                    getString(R.string.open_pgp_list_items_detach_sign) };

            // Obtaining recipients
            String rcpt_s = getIntent().getExtras().getString("recipients");
            if (rcpt_s != null && !rcpt_s.isEmpty()) {
                rcpt_mailboxes = rcpt_s.split(",");
                for (int i = 0; i < rcpt_mailboxes.length; ++i) {
                    rcpt_mailboxes[i] = rcpt_mailboxes[i].trim();
                }
                tv_recipients_list.setText(rcpt_s);
            }

            // Message data
            msg_contents = getIntent().getStringExtra("message-data");
            tv_message.setText(msg_contents);
            if (msg_contents != null) {
                if (msg_contents.length() > 500) {
                    tv_message.setText(msg_contents.substring(0, 500));
                } else {
                    tv_message.setText(msg_contents);
                }
            }

            // Obtaining attachments
            attachment_paths = getIntent().getExtras().getStringArrayList("attachments");
            break;
        }
        case 92: {// decrypt and verify signature
            gpg_actions = new String[] { getString(R.string.open_pgp_list_items_decrypt_and_verify) };
            msg_encrypted = true;
            msg_contents = getIntent().getStringExtra("message-data");
            if (msg_contents.length() > 500) {
                tv_cipher_text.setText(msg_contents.substring(0, 500));
            } else
                tv_cipher_text.setText(msg_contents);
            break;
        }
        case 93: {// clear text verify signature
            gpg_actions = new String[] { getString(R.string.open_pgp_list_items_decrypt_and_verify) };
            msg_signed = true;
            msg_signature = getIntent().getStringExtra("signature");
            msg_contents = getIntent().getStringExtra("message-data");
            tv_message.setText(msg_contents);
            if (msg_contents != null) {
                if (msg_contents.length() > 500) {
                    tv_message.setText(msg_contents.substring(0, 500));
                } else {
                    tv_message.setText(msg_contents);
                }
            }
            tv_signature.setText(msg_signature);
            break;
        }
        }

        cb_sign_to_self = (CheckBox) findViewById(R.id.cb_sign_to_self);
        if (msg_encrypted || msg_signed) {
            // Free visual space
            cb_sign_to_self.setVisibility(View.GONE);
            tv_recipients_pick.setVisibility(View.GONE);
            TextView tv_1 = (TextView) findViewById(R.id.tv_pick_sign_key);
            TextView tv_2 = (TextView) findViewById(R.id.tv_recipients);
            TextView tv_3 = (TextView) findViewById(R.id.tv_recipients_list);
            tv_1.setVisibility(View.GONE);
            tv_2.setVisibility(View.GONE);
            tv_3.setVisibility(View.GONE);
        }

        spinner_gpg_action = (Spinner) findViewById(R.id.spinner_gpg_action);
        ArrayAdapter<String> adapt = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, gpg_actions);
        adapt.setDropDownViewResource(android.R.layout.simple_list_item_checked);
        spinner_gpg_action.setAdapter(adapt);

        TextView tv_start = (TextView) findViewById(R.id.tv_start);
        tv_start.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // Reset first
                reset_activity();
                if (msg_signed || msg_encrypted) {
                    open_connection();
                } else {
                    switch (spinner_gpg_action.getSelectedItemPosition()) {
                    case 0:// Encrypt
                        if (l_sign_key_id == NO_KEY) {
                            toaster(getString(R.string.err_pick_signing_key));
                        } else if (rcpt_keys == null) {
                            toaster(getString(R.string.err_pick_rcpt_keys));
                        } else {
                            encrypt(new Intent());
                        }
                        break;
                    case 1:// Sign and encrypt
                        if (l_sign_key_id == NO_KEY) {
                            toaster(getString(R.string.err_pick_signing_key));
                        } else if (rcpt_keys == null) {
                            toaster(getString(R.string.err_pick_rcpt_keys));
                        } else {
                            sign_and_encrypt(new Intent());
                        }
                        break;
                    case 2:// Detached sign (of clear text)
                        if (l_sign_key_id == NO_KEY) {
                            toaster(getString(R.string.err_pick_signing_key));
                        } else {
                            detached_sign(new Intent());
                        }
                        break;
                    }
                }
            }
        });

        tv_signing_key.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                select_signing_key();
            }
        });

        tv_recipients_pick.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                select_rcpt_keys();
            }
        });
    }

    private void reset_activity() {
        if (intent_request_code == 91) {
            tv_cipher_text.setText("");
            tv_signature.setText("");
            msg_crypto = "";
            msg_signature = "";
        }
        if (intent_request_code == 92) {
            tv_message.setText("");
        }
    }

    private void select_signing_key() {
        open_pgp_service_connection = new OpenPgpServiceConnection(getApplicationContext(), open_pgp_provider,
                new OpenPgpServiceConnection.OnBound() {

                    @Override
                    public void onBound(IOpenPgpService2 service) {
                        get_sign_key_id(new Intent());
                    }

                    @Override
                    public void onError(Exception e) {
                        Pager.log += e.getMessage() + "\n\n";
                    }
                });
        open_pgp_service_connection.bindToService();
    }

    private void get_sign_key_id(Intent data) {
        data.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);

        OpenPgpApi api = new OpenPgpApi(this, open_pgp_service_connection.getService());
        api.executeApiAsync(data, null, null, new SignKeyCallback(REQUEST_CODE_KEY_PREFERENCE, this));
    }

    private void select_rcpt_keys() {
        open_pgp_service_connection = new OpenPgpServiceConnection(getApplicationContext(), open_pgp_provider,
                new OpenPgpServiceConnection.OnBound() {

                    @Override
                    public void onBound(IOpenPgpService2 service) {
                        get_rcpt_keys(new Intent());
                    }

                    @Override
                    public void onError(Exception e) {
                        Pager.log += e.getMessage() + "\n\n";
                    }
                });
        open_pgp_service_connection.bindToService();
    }

    private void get_rcpt_keys(Intent data) {
        data.setAction(OpenPgpApi.ACTION_GET_KEY_IDS);
        data.putExtra(OpenPgpApi.EXTRA_USER_IDS, rcpt_mailboxes);

        OpenPgpApi api = new OpenPgpApi(this, open_pgp_service_connection.getService());
        api.executeApiAsync(data, null, null, new ResultsCallback(null, REQUEST_CODE_GET_KEY_IDS));
    }

    /**
     * Inline non-mime pgp messages. Not implemented.
     **/
    public void cleartext_sign(Intent data) {
        data.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
        data.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, l_sign_key_id);

        InputStream is = get_input_stream(true, true);
        ByteArrayOutputStream os = new ByteArrayOutputStream();

        OpenPgpApi api = new OpenPgpApi(this, open_pgp_service_connection.getService());
        api.executeApiAsync(data, is, os, new ResultsCallback(os, REQUEST_CODE_CLEARTEXT_SIGN));
    }

    /**
     * Clear text signature PGP/MIME message.
     **/
    public void detached_sign(Intent data) {
        data.setAction(OpenPgpApi.ACTION_DETACHED_SIGN);
        data.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, l_sign_key_id);

        InputStream is = get_input_stream(true, true);

        OpenPgpApi api = new OpenPgpApi(this, open_pgp_service_connection.getService());
        api.executeApiAsync(data, is, null, new ResultsCallback(null, REQUEST_CODE_DETACHED_SIGN));
    }

    private void encrypt(Intent data) {
        data.setAction(OpenPgpApi.ACTION_ENCRYPT);
        if (cb_sign_to_self.isChecked()) {
            long[] ll = new long[rcpt_keys.length + 1];
            for (int ii = 0; ii < ll.length; ++ii) {
                if (ii == rcpt_keys.length) {
                    ll[ii] = l_sign_key_id;
                } else {
                    ll[ii] = rcpt_keys[ii];
                }
            }
            data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, ll);
        } else {
            data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, rcpt_keys);
        }
        //data.putExtra(OpenPgpApi.EXTRA_USER_IDS, rcpt_mailboxes);
        data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);

        InputStream is = get_input_stream(true, false);
        ByteArrayOutputStream os = new ByteArrayOutputStream();

        OpenPgpApi api = new OpenPgpApi(this, open_pgp_service_connection.getService());
        api.executeApiAsync(data, is, os, new ResultsCallback(os, REQUEST_CODE_ENCRYPT));
    }

    public void sign_and_encrypt(Intent data) {
        data.setAction(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
        data.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, l_sign_key_id);
        if (cb_sign_to_self.isChecked()) {
            long[] ll = new long[rcpt_keys.length + 1];
            for (int ii = 0; ii < ll.length; ++ii) {
                if (ii == rcpt_keys.length) {
                    ll[ii] = l_sign_key_id;
                } else {
                    ll[ii] = rcpt_keys[ii];
                }
            }
            data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, ll);
        } else {
            data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, rcpt_keys);
        }
        //data.putExtra(OpenPgpApi.EXTRA_USER_IDS, rcpt_mailboxes);
        data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);

        InputStream is = get_input_stream(true, false);
        ByteArrayOutputStream os = new ByteArrayOutputStream();

        OpenPgpApi api = new OpenPgpApi(this, open_pgp_service_connection.getService());
        api.executeApiAsync(data, is, os, new ResultsCallback(os, REQUEST_CODE_SIGN_AND_ENCRYPT));
    }

    private void decrypt_and_verify(Intent data) {
        data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);

        InputStream is = get_input_stream(false, false);
        ByteArrayOutputStream os = new ByteArrayOutputStream();

        OpenPgpApi api = new OpenPgpApi(this, open_pgp_service_connection.getService());
        api.executeApiAsync(data, is, os, new ResultsCallback(os, REQUEST_CODE_DECRYPT_AND_VERIFY));
    }

    public void decrypt_and_verify_detached(Intent data) {
        data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
        if (msg_signature == null) {
            toaster(getString(R.string.open_pgp_failure));
        } else {
            data.putExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE, msg_signature.getBytes());
            InputStream is = get_input_stream(false, false);

            OpenPgpApi api = new OpenPgpApi(this, open_pgp_service_connection.getService());
            api.executeApiAsync(data, is, null,
                    new ResultsCallback(null, REQUEST_CODE_DECRYPT_AND_VERIFY_DETACHED));
        }
    }

    private void open_connection() {
        open_pgp_service_connection = new OpenPgpServiceConnection(getApplicationContext(), open_pgp_provider,
                new OpenPgpServiceConnection.OnBound() {

                    @Override
                    public void onBound(IOpenPgpService2 service) {
                        if (intent_request_code == 92) {
                            decrypt_and_verify(new Intent());
                        } else if (intent_request_code == 93) {
                            decrypt_and_verify_detached(new Intent());
                        }
                    }

                    @Override
                    public void onError(Exception e) {
                        Pager.log += e.getMessage() + "\n\n";
                        toaster(e.getMessage());
                    }
                });
        open_pgp_service_connection.bindToService();
    }

    /**
     * Converts String to ByteArrayInputStream.
     **/
    private InputStream get_input_stream(boolean mime, boolean clear) {
        InputStream is = null;
        try {
            if (mime) {
                if (clear) {
                    msg_crypto = mime_serialization().replaceAll("\n", "\r\n");
                    is = new ByteArrayInputStream(msg_crypto.getBytes("UTF-8"));
                } else {
                    is = new ByteArrayInputStream(mime_serialization().getBytes("UTF-8"));
                }
            } else {
                if (msg_contents == null)
                    return null;
                is = new ByteArrayInputStream(msg_contents.getBytes("UTF-8"));
            }
        } catch (UnsupportedEncodingException e) {
            Pager.log += e.getMessage() + "\n\n";
            Dialogs.toaster(false, e.getMessage(), this);
        }
        return is;
    }

    /**
     * PGP/MIME serialization.
     **/
    private String mime_serialization() {
        String bounds = Utils.boundary();
        String msg_mime = "Content-type: multipart/mixed;\n boundary=" + "\"" + bounds + "\"\n";

        // Message textual contents
        msg_mime += "\n--" + bounds + "\n";
        msg_mime += "Content-Type: text/plain; charset=\"utf-8\"\n";
        msg_mime += "Content-Transfer-Encoding: 8bit\n\n";
        if (msg_contents != null)
            msg_mime += msg_contents + "\n--" + bounds;
        if (attachment_paths == null) {
            msg_mime += "--\n";
        } else {
            // Message attachments
            for (int i = 0; i < attachment_paths.size(); ++i) {
                File ff = new File(attachment_paths.get(i));
                msg_mime += "\n--" + bounds + "\n";
                if (Utils.all_ascii(ff.getName())) {
                    msg_mime += "Content-Type: application/octet-stream; name=\"" + ff.getName() + "\"\n";
                    msg_mime += "Content-Transfer-Encoding: base64\n";
                    msg_mime += "Content-Disposition: attachment; filename=\"" + ff.getName() + "\"\n";
                } else {
                    msg_mime += "Content-Type: application/octet-stream; name*=\""
                            + Utils.to_base64_utf8(ff.getName()) + "\"\n";
                    msg_mime += "Content-Transfer-Encoding: base64\n";
                    String new_name = Utils.content_disposition_name(true, ff.getName());
                    msg_mime += "Content-Disposition: attachment; filename*=" + new_name + "\n";
                }
                msg_mime += "\n";
                ByteArrayOutputStream b_stream = new ByteArrayOutputStream();
                try {
                    InputStream in_stream = new FileInputStream(ff);
                    byte[] bfr = new byte[(int) ff.length()];
                    if ((int) ff.length() > 0) {
                        int t;
                        while ((t = in_stream.read(bfr)) != -1) {
                            b_stream.write(bfr, 0, t);
                        }
                    }
                } catch (IOException e) {
                    Pager.log += getString(R.string.ex_field) + e.getMessage() + "\n\n";
                    Dialogs.toaster(true, e.getMessage(), this);
                }
                msg_mime += new String(Base64.encode(b_stream.toByteArray(), Base64.DEFAULT));
                if (msg_mime.charAt(msg_mime.length() - 1) == '\n') {
                    msg_mime = msg_mime.substring(0, msg_mime.length() - 1);
                }
            }
            msg_mime += "\n--" + bounds + "--\n";
        }

        // Outer boundary shell
        bounds = Utils.boundary();

        msg_mime = "Content-type: multipart/mixed;\n boundary=" + "\"" + bounds + "\"\n" + "\n--" + bounds + "\n"
                + msg_mime;
        msg_mime += "\n--" + bounds + "--\n";

        return msg_mime;
    }

    private String pgp_mime_serialization() {
        String bounds = Utils.boundary();
        String pgp_mime;
        if (spinner_gpg_action.getSelectedItemPosition() == 2) {
            // Creating signed clear text (+/- attachments) pgp/mime
            pgp_mime = "Content-type: multipart/signed; micalg=" + msg_integrity + ";\n"
                    + " protocol=\"application/pgp-signature\";\n" + " boundary=" + "\"" + bounds + "\"\n";
            pgp_mime += "\n--" + bounds + "\n";
            pgp_mime += msg_crypto;
            pgp_mime += "\n--" + bounds + "\n";
            pgp_mime += "Content-Type: application/pgp-signature\n";
            pgp_mime += "Content-Description: OpenPGP digital signature\n";
            pgp_mime += "Content-Disposition: attachment\n\n";
            pgp_mime += msg_signature;
            pgp_mime += "\n--" + bounds + "--\n";
        } else {
            // Creating signed and/or encrypted pgp/mime
            pgp_mime = "Content-type: multipart/encrypted; protocol=\"application/pgp-encrypted\";" + "\n boundary="
                    + "\"" + bounds + "\"\n";
            pgp_mime += "\n--" + bounds + "\n";
            pgp_mime += "Content-Type: application/pgp-encrypted\n";
            pgp_mime += "Content-Description: PGP/MIME version identification\n\n";
            pgp_mime += "Version: 1\n";
            pgp_mime += "\n--" + bounds + "\n";
            pgp_mime += "Content-Type: application/octet-stream\n";
            pgp_mime += "Content-Description: OpenPGP encrypted message\n";
            pgp_mime += "Content-Disposition: inline\n\n";
            pgp_mime += msg_crypto;
            pgp_mime += "\n--" + bounds + "--\n";
        }

        return pgp_mime;
    }

    private void toaster(final String msg) {
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                Toast.makeText(InboxGPG.this, msg, Toast.LENGTH_SHORT).show();
            }
        });
    }

    private class ResultsCallback implements OpenPgpApi.IOpenPgpCallback {

        int request_code;
        ByteArrayOutputStream os;

        private ResultsCallback(ByteArrayOutputStream bos, int rc) {
            os = bos;
            request_code = rc;
        }

        @Override
        public void onReturn(Intent result) {
            switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
            case OpenPgpApi.RESULT_CODE_SUCCESS: {
                // ENCRYPT|DECRYPT|SIGN|VERIFY
                if (os != null) {
                    try {
                        switch (intent_request_code) {
                        case 91:
                            // If encrypting, sign-encrypting
                            if (spinner_gpg_action.getSelectedItemPosition() != 2) {
                                // Signing and/or encryption
                                msg_crypto = os.toString("UTF-8");
                                if (msg_crypto != null && msg_crypto.length() > 700) {
                                    tv_cipher_text.setText(msg_crypto.substring(0, 700));
                                } else
                                    tv_cipher_text.setText(msg_crypto);
                            }
                            msg_ready = true;
                            break;
                        case 92:
                            // Encrypted and/or signed, decryption
                            if (msg_encrypted) {
                                msg_crypto = os.toString("UTF-8");
                                if (msg_crypto != null && msg_crypto.length() > 700) {
                                    tv_message.setText(msg_crypto.substring(0, 700));
                                } else
                                    tv_message.setText(msg_crypto);
                            }
                            msg_ready = true;
                        case 93:
                            // Signed clear text verification
                            msg_ready = true;
                            break;
                        }
                    } catch (UnsupportedEncodingException e) {
                        Pager.log += e.getMessage() + "\n\n";
                    }
                }

                switch (request_code) {
                case REQUEST_CODE_DECRYPT_AND_VERIFY:
                case REQUEST_CODE_DECRYPT_AND_VERIFY_DETACHED: {
                    //OpenPgpDecryptionResult decryption_res =
                    //result.getParcelableExtra(OpenPgpApi.RESULT_DECRYPTION);
                    OpenPgpSignatureResult signature_res = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
                    if (signature_res.getKeyId() != 0) {
                        msg_signature = signature_res.getPrimaryUserId() + "\n\n"
                                + signature_res.getConfirmedUserIds().toString();
                        tv_signature.setText(msg_signature);
                    } else {
                        tv_signature.setText(getString(R.string.open_pgp_bad_signature));
                    }
                    msg_ready = true;
                    break;
                }
                case REQUEST_CODE_DETACHED_SIGN: {
                    byte[] detached_sig = result.getByteArrayExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE);
                    msg_signature = new String(detached_sig);
                    tv_signature.setText(msg_signature);
                    msg_integrity = result.getStringExtra("signature_micalg");
                    msg_ready = true;
                    break;
                }
                case REQUEST_CODE_GET_KEY_IDS: {
                    rcpt_keys = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS);
                    if (rcpt_keys != null) {
                        tv_recipients_pick.setText(getString(R.string.open_pgp_rcpt_key_check));
                        tv_recipients_count.setText(String.valueOf(rcpt_keys.length));
                    }
                    break;
                }
                }

                break;
            }
            case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
                PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
                try {
                    InboxGPG.this.startIntentSenderFromChild(InboxGPG.this, pi.getIntentSender(), request_code,
                            null, 0, 0, 0);
                } catch (IntentSender.SendIntentException e) {
                    Pager.log += e.getMessage() + "\n\n";
                }
                break;
            }
            case OpenPgpApi.RESULT_CODE_ERROR: {
                toaster(getString(R.string.open_pgp_failure));
                Pager.log += ((OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)).getMessage()
                        + "\n\n";
                msg_ready = false;
                break;
            }
            }
        }
    }

    private class SignKeyCallback implements OpenPgpApi.IOpenPgpCallback {

        int requestCode;
        AppCompatActivity act;

        private SignKeyCallback(int rc, AppCompatActivity ac) {
            requestCode = rc;
            act = ac;
        }

        @Override
        public void onReturn(Intent result) {
            switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
            case OpenPgpApi.RESULT_CODE_SUCCESS: {
                //long keyId = result.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, NO_KEY);
                break;
            }
            case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
                PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
                try {
                    act.startIntentSenderFromChild(act, pi.getIntentSender(), requestCode, null, 0, 0, 0);
                } catch (IntentSender.SendIntentException e) {
                    Pager.log += e.getMessage() + "\n\n";
                }
                break;
            }
            case OpenPgpApi.RESULT_CODE_ERROR: {
                Pager.log += ((OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)).getMessage()
                        + "\n\n";
                break;
            }
            }
        }
    }
}