com.mycelium.wallet.Utils.java Source code

Java tutorial

Introduction

Here is the source code for com.mycelium.wallet.Utils.java

Source

/*
 * Copyright 2013, 2014 Megion Research and Development GmbH
 *
 * Licensed under the Microsoft Reference Source License (MS-RSL)
 *
 * This license governs use of the accompanying software. If you use the software, you accept this license.
 * If you do not accept the license, do not use the software.
 *
 * 1. Definitions
 * The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
 * "You" means the licensee of the software.
 * "Your company" means the company you worked for when you downloaded the software.
 * "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes
 * of debugging your products, maintaining your products, or enhancing the interoperability of your products with the
 * software, and specifically excludes the right to distribute the software outside of your company.
 * "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor
 * under this license.
 *
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive,
 * worldwide, royalty-free copyright license to reproduce the software for reference use.
 * (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive,
 * worldwide, royalty-free patent license under licensed patents for reference use.
 *
 * 3. Limitations
 * (A) No Trademark License- This license does not grant you any rights to use the Licensors name, logo, or trademarks.
 * (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software
 * (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
 * (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties,
 * guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot
 * change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability,
 * fitness for a particular purpose and non-infringement.
 */

package com.mycelium.wallet;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.StringRes;
import android.text.ClipboardManager;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.mrd.bitlib.model.Address;
import com.mrd.bitlib.model.NetworkParameters;
import com.mrd.bitlib.util.CoinUtil;
import com.mycelium.wallet.activity.AdditionalBackupWarningActivity;
import com.mycelium.wallet.activity.BackupWordListActivity;
import com.mycelium.wallet.activity.export.BackupToPdfActivity;
import com.mycelium.wallet.activity.export.ExportAsQrCodeActivity;
import com.mycelium.wallet.coinapult.CoinapultAccount;
import com.mycelium.wallet.persistence.MetadataStorage;
import com.mycelium.wapi.wallet.AesKeyCipher;
import com.mycelium.wapi.wallet.ExportableAccount;
import com.mycelium.wapi.wallet.WalletAccount;
import com.mycelium.wapi.wallet.bip44.Bip44Account;
import com.mycelium.wapi.wallet.bip44.Bip44AccountContext;
import com.mycelium.wapi.wallet.bip44.Bip44AccountExternalSignature;
import com.mycelium.wapi.wallet.bip44.Bip44PubOnlyAccount;
import com.mycelium.wapi.wallet.currency.CurrencyValue;
import com.mycelium.wapi.wallet.currency.BitcoinValue;
import com.mycelium.wapi.wallet.currency.ExactBitcoinValue;
import com.mycelium.wapi.wallet.single.SingleAddressAccount;
import org.ocpsoft.prettytime.PrettyTime;

import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;

@SuppressWarnings("deprecation")
public class Utils {

    private static final DecimalFormat FIAT_FORMAT;

    static {
        FIAT_FORMAT = new DecimalFormat();
        FIAT_FORMAT.setGroupingSize(3);
        FIAT_FORMAT.setGroupingUsed(true);
        FIAT_FORMAT.setMaximumFractionDigits(2);
        FIAT_FORMAT.setMinimumFractionDigits(2);
        DecimalFormatSymbols symbols = FIAT_FORMAT.getDecimalFormatSymbols();
        symbols.setDecimalSeparator('.');
        symbols.setGroupingSeparator(' ');
        FIAT_FORMAT.setDecimalFormatSymbols(symbols);
    }

    public static final Function<AddressBookManager.Entry, Comparable> ENTRY_NAME = new Function<AddressBookManager.Entry, Comparable>() {
        @Override
        public Comparable apply(AddressBookManager.Entry input) {
            return input.getName();
        }
    };

    @SuppressLint(Constants.IGNORE_NEW_API)
    public static void setAlpha(View view, float alpha) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            AlphaAnimation aa = new AlphaAnimation(alpha, alpha);
            aa.setDuration(Long.MAX_VALUE);
            view.startAnimation(aa);
        } else {
            view.setAlpha(alpha);
        }
    }

    public static String loadEnglish(int resId) {
        // complex code messes up stuff, hardcoding two strings
        if (resId == R.string.settings) {
            return "Settings";
        }
        if (resId == R.string.pref_change_language) {
            return "Change Language";
        }
        throw new UnsupportedOperationException("not implemented");

        /*
         * Resources standardResources = getResources(); AssetManager assets =
         * standardResources.getAssets(); DisplayMetrics metrics =
         * standardResources.getDisplayMetrics(); Configuration config = new
         * Configuration(standardResources.getConfiguration()); config.locale =
         * Locale.US; Resources defaultResources = new Resources(assets, metrics,
         * config);
         * 
         * String lang = Locale.getDefault().getLanguage(); String settingsEn =
         * null; if (!lang.equals("en")) { settingsEn =
         * defaultResources.getString(resId); } return settingsEn;
         */
    }

    public static Bitmap getMinimalQRCodeBitmap(String url) {
        Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
        hints.put(EncodeHintType.MARGIN, 5);

        try {
            final BitMatrix result = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, 0, 0, hints);

            final int width = result.getWidth();
            final int height = result.getHeight();
            final int[] pixels = new int[width * height];

            for (int y = 0; y < height; y++) {
                final int offset = y * width;
                for (int x = 0; x < width; x++) {
                    pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
                }
            }

            final Bitmap smallBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            smallBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
            return smallBitmap;
        } catch (final WriterException x) {
            x.printStackTrace();
            return null;
        }
    }

    public static boolean isConnected(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo[] NI = cm.getAllNetworkInfo();
        for (NetworkInfo aNI : NI) {
            if (aNI.isConnected()) {
                return true;
            }
        }
        return false;
    }

    public static void toastConnectionError(Context context) {
        if (isConnected(context)) {
            Toast.makeText(context, R.string.no_server_connection, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(context, R.string.no_network_connection, Toast.LENGTH_LONG).show();
        }
    }

    public static void moveView(View view, int startDeltaX, int startDeltaY, int endDeltaX, int endDeltaY,
            long duration) {
        AnimationSet set = new AnimationSet(true);
        Animation move = new TranslateAnimation(startDeltaX, endDeltaX, startDeltaY, endDeltaY);
        move.setDuration(duration);
        move.setFillAfter(true);
        move.setZAdjustment(Animation.ZORDER_TOP);
        set.addAnimation(move);
        set.setFillAfter(true);
        view.startAnimation(set);
    }

    public static void showSimpleMessageDialog(final Context context, int messageResource) {
        showSimpleMessageDialog(context, messageResource, null);
    }

    public static void showSimpleMessageDialog(final Context context, int messageResource, Runnable postRunner) {
        String message = context.getResources().getString(messageResource);
        showSimpleMessageDialog(context, message, postRunner);
    }

    public static String formatBlockcountAsApproxDuration(final Context context, final int blocks) {
        MbwManager mbwManager = MbwManager.getInstance(context);
        PrettyTime p = new PrettyTime(mbwManager.getLocale());
        return p.formatApproximateDuration(
                new Date((new Date()).getTime() + Math.max((long) blocks, 1L) * 10 * 60 * 1000));
    }

    /**
     * Show a dialog without buttons that displays a message. Click the message
     * or the back button to make it disappear.
     */
    public static void showSimpleMessageDialog(final Context context, String message) {
        showSimpleMessageDialog(context, message, null);
    }

    /**
     * Show a dialog without buttons that displays a message. Click the message
     * or the back button to make it disappear.
     */
    public static void showSimpleMessageDialog(final Context context, String message, final Runnable okayRunner) {
        showSimpleMessageDialog(context, message, okayRunner, null);
    }

    /**
     * Show a dialog without buttons that displays a message. Click the message
     * or the back button to make it disappear.
     */
    public static void showSimpleMessageDialog(final Context context, String message, final Runnable okayRunner,
            final Runnable postRunner) {
        showSimpleMessageDialog(context, message, okayRunner, R.string.ok, postRunner);
    }

    /**
     * Show a dialog with a buttons that displays a message. Click the message
     * or the back button to make it disappear.
     */
    public static void showSimpleMessageDialog(final Context context, String message, final Runnable okayRunner,
            @StringRes int okayButtonText, final Runnable postRunner) {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        final View layout = inflater.inflate(R.layout.simple_message_dialog, null);
        AlertDialog.Builder builder = new AlertDialog.Builder(context).setView(layout);
        final AlertDialog dialog = builder.create();
        TextView tvMessage = ((TextView) layout.findViewById(R.id.tvMessage));
        tvMessage.setText(message);

        TextView okButton = (TextView) layout.findViewById(R.id.btOk);
        okButton.setText(okayButtonText);
        okButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                dialog.dismiss();
                if (okayRunner != null) {
                    okayRunner.run();
                }
            }
        });

        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                if (postRunner != null) {
                    postRunner.run();
                }
            }
        });
        dialog.show();
    }

    /**
     * Show an optional message/
     * <p>
     * The user can check a "never shot this again" check box and the message
     * will never get displayed again.
     *
     * @param context           The context
     * @param messageResourceId The resource ID of the message to show
     */
    public static boolean showOptionalMessage(final Context context, int messageResourceId) {
        return showOptionalMessage(context, messageResourceId, null);
    }

    /**
     * Show an optional message/
     * <p>
     * The user can check a "never shot this again" check box and the message
     * will never get displayed again.
     *
     * @param context           The context
     * @param messageResourceId The resource ID of the message to show
     * @param onOkay            This runnable gets executed either if the user clicks Okay or if he choose to never-see-this-message-again
     */
    public static boolean showOptionalMessage(final Context context, int messageResourceId, final Runnable onOkay) {
        String message = context.getString(messageResourceId);
        final String optionalMessageId = Integer.toString(message.hashCode());
        SharedPreferences settings = context.getSharedPreferences("optionalMessagePreferences",
                Activity.MODE_PRIVATE);
        boolean ignore = settings.getBoolean(optionalMessageId, false);
        // The user has opted never to get this message shown again
        if (ignore) {
            if (onOkay != null) {
                onOkay.run();
            }
            return false;
        }

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        final View layout = inflater.inflate(R.layout.optional_message_dialog, null);
        AlertDialog.Builder builder = new AlertDialog.Builder(context).setView(layout);
        final AlertDialog dialog = builder.create();
        TextView tvMessage = ((TextView) layout.findViewById(R.id.tvMessage));
        tvMessage.setText(message);
        CheckBox cb = (CheckBox) layout.findViewById(R.id.checkbox);
        cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // Persist checked state
                context.getSharedPreferences("optionalMessagePreferences", Activity.MODE_PRIVATE).edit()
                        .putBoolean(optionalMessageId, isChecked).commit();
            }
        });

        layout.findViewById(R.id.btOk).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if (onOkay != null) {
                    onOkay.run();
                }
                dialog.dismiss();
            }
        });
        dialog.show();
        return true;
    }

    /**
     * Chop a string into an array of strings no longer then the specified chop
     * length
     */
    public static String[] stringChopper(String string, int chopLength) {
        return Iterables.toArray(Splitter.fixedLength(chopLength).split(string), String.class);
    }

    public static String stringChopper(String string, int chopLength, String joiner) {
        String[] parts = Iterables.toArray(Splitter.fixedLength(chopLength).split(string), String.class);
        return Joiner.on(joiner).join(parts);
    }

    public static Double getFiatValue(long satoshis, Double oneBtcInFiat) {
        if (oneBtcInFiat == null) {
            return null;
        }
        return (double) satoshis * oneBtcInFiat / Constants.ONE_BTC_IN_SATOSHIS;
    }

    public static String getFiatValueAsString(long satoshis, Double oneBtcInFiat) {
        Double converted = getFiatValue(satoshis, oneBtcInFiat);
        if (converted == null) {
            return null;
        }
        return FIAT_FORMAT.format(converted);
    }

    private static HashMap<Integer, DecimalFormat> formatCache = new HashMap<Integer, DecimalFormat>(2);

    public static String formatFiatValueAsString(BigDecimal fiat) {
        return FIAT_FORMAT.format(fiat);
    }

    public static String getFiatValueAsString(long satoshis, Double oneBtcInFiat, int precision) {

        Double converted = getFiatValue(satoshis, oneBtcInFiat);
        if (converted == null) {
            return null;
        }

        if (!formatCache.containsKey(precision)) {
            DecimalFormat fiatFormat = (DecimalFormat) FIAT_FORMAT.clone();
            fiatFormat.setMaximumFractionDigits(precision);
            formatCache.put(precision, fiatFormat);
        }
        return formatCache.get(precision).format(converted);
    }

    private static final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100L);
    private static final BigDecimal BTC_IN_SATOSHIS = BigDecimal.valueOf(Constants.ONE_BTC_IN_SATOSHIS);

    public static Long getSatoshis(BigDecimal fiatValue, Double oneBtcInFiat) {
        if (fiatValue == null || oneBtcInFiat == null) {
            return null;
        }
        BigDecimal fiatCents = fiatValue.multiply(ONE_HUNDRED);
        BigDecimal oneBtcInFiatCents = BigDecimal.valueOf(oneBtcInFiat).multiply(ONE_HUNDRED);
        return fiatCents.multiply(BTC_IN_SATOSHIS).divide(oneBtcInFiatCents, 0, RoundingMode.HALF_UP).longValue();
    }

    public static void setClipboardString(String string, Context context) {
        try {
            ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
            clipboard.setText(string);
        } catch (NullPointerException ex) {
            MbwManager.getInstance(context).reportIgnoredException(new RuntimeException(ex.getMessage()));
            Toast.makeText(context, context.getString(R.string.unable_to_set_clipboard), Toast.LENGTH_LONG).show();
        }
    }

    public static String getClipboardString(Activity activity) {
        try {
            ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
            CharSequence content = clipboard.getText();
            if (content == null) {
                return "";
            }
            return content.toString();
        } catch (SecurityException ex) {
            //some devices reported java.lang.SecurityException: Permission Denial:
            // reading com.android.providers.media.MediaProvider uri content://media/external/file/6595
            // it appears as if we have a file in clipboard that the system is trying to read. we don't want to do that anyways, so lets ignore it.
            Toast.makeText(activity, activity.getString(R.string.unable_to_get_clipboard), Toast.LENGTH_LONG)
                    .show();
            return "";
        } catch (NullPointerException ex) {
            MbwManager.getInstance(activity).reportIgnoredException(new RuntimeException(ex.getMessage()));
            Toast.makeText(activity, activity.getString(R.string.unable_to_get_clipboard), Toast.LENGTH_LONG)
                    .show();
            return "";
        }
    }

    public static void clearClipboardString(Activity activity) {
        try {
            ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
            clipboard.setText("");
        } catch (NullPointerException ex) {
            MbwManager.getInstance(activity).reportIgnoredException(new RuntimeException(ex.getMessage()));
            Toast.makeText(activity, activity.getString(R.string.unable_to_clear_clipboard), Toast.LENGTH_LONG)
                    .show();
        }
    }

    public static Optional<Address> addressFromString(String someString, NetworkParameters network) {
        if (someString == null) {
            return Optional.absent();
        }
        someString = someString.trim();
        if (someString.matches("[a-zA-Z0-9]*")) {
            // Raw format
            return Optional.fromNullable(Address.fromString(someString, network));
        } else {
            Optional<BitcoinUriWithAddress> b = BitcoinUriWithAddress.parseWithAddress(someString, network);
            if (b.isPresent()) {
                // On URI format
                return Optional.of(b.get().address);
            }
        }
        return Optional.absent();
    }

    /**
     * Truncate and transform a decimal string to a maximum number of digits
     * <p>
     * The string will be truncated and verified to be a valid decimal number
     * with one comma or dot separator. A comma separator will be converted to a
     * dot. The resulting string will have at most the number of decimals
     * specified
     *
     * @param number           the number to truncate
     * @param maxDecimalPlaces the maximum number of decimal places
     * @return a truncated decimal string or null if the input string is not a
     * valid decimal string
     */
    public static String truncateAndConvertDecimalString(String number, int maxDecimalPlaces) {
        if (number == null) {
            return null;
        }
        number = number.trim();
        if (!isValidDecimalNumber(number)) {
            return null;
        }

        // We now have a string with at least one digit before the separator
        // If it has a separator there is only one and it it is a dot or a comma
        // If it has a separator there will be at least one decimal after the
        // separator
        // All characters except the separator are between 0 and 9

        // Replace comma with dot
        number = number.replace(',', '.');

        boolean foundDot = false;
        int decimals = 0;
        char[] chars = number.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];

            // Check for dot
            if (c == '.') {
                if (maxDecimalPlaces == 0) {
                    // We want everything till now except the dot
                    return number.substring(0, i);
                }
                foundDot = true;
                continue;
            }

            // Count decimal places
            if (foundDot) {
                decimals++;
            }

            if (maxDecimalPlaces == decimals) {
                // We want everything till now
                return number.substring(0, i + 1);
            }

        }
        // We want everything;
        return number;
    }

    private static boolean isValidDecimalNumber(String string) {
        if (string == null) {
            return false;
        }
        if (string.length() == 0) {
            return false;
        }
        boolean foundDot = false;
        boolean foundComma = false;
        int digitsBefore = 0;
        int digitsAfter = 0;
        char[] chars = string.toCharArray();
        for (char c : chars) {
            // Check for digits
            if (c == '.') {
                if (foundDot || foundComma) {
                    return false;
                }
                foundDot = true;
                continue;
            }

            // Check for comma
            if (c == ',') {
                if (foundDot || foundComma) {
                    return false;
                }
                foundComma = true;
                continue;
            }

            // Only digits
            if (c < '0' || c > '9') {
                return false;
            }

            // Count decimal places
            if (foundDot || foundComma) {
                digitsAfter++;
            } else {
                digitsBefore++;
            }
        }
        if (digitsBefore == 0) {
            // There must be something before the decimal separator
            return false;
        }
        return !((foundDot || foundComma) && digitsAfter == 0);

    }

    public static void pinProtectedWordlistBackup(final Activity activity) {
        MbwManager manager = MbwManager.getInstance(activity);
        manager.runPinProtectedFunction(activity, new Runnable() {

            @Override
            public void run() {
                Utils.wordlistBackup(activity);
            }
        });
    }

    private static void wordlistBackup(final Activity parent) {
        MbwManager _mbwManager = MbwManager.getInstance(parent);
        if (_mbwManager.getMetadataStorage().firstMasterseedBackupFinished()) {
            // second+ backup
            AdditionalBackupWarningActivity.callMe(parent);
        } else {
            // first backup
            AlertDialog.Builder builder = new AlertDialog.Builder(parent);
            builder.setMessage(R.string.backup_all_warning).setCancelable(true)
                    .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            dialog.dismiss();
                            BackupWordListActivity.callMe(parent);
                        }
                    }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                        }
                    });
            AlertDialog alertDialog = builder.create();
            alertDialog.show();
        }

    }

    public static void pinProtectedBackup(final Activity activity) {
        MbwManager manager = MbwManager.getInstance(activity);
        manager.runPinProtectedFunction(activity, new Runnable() {

            @Override
            public void run() {
                Utils.backup(activity);
            }
        });
    }

    private static void backup(final Activity parent) {
        AlertDialog.Builder builder = new AlertDialog.Builder(parent);
        builder.setMessage(R.string.backup_legacy_warning).setCancelable(true)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                        BackupToPdfActivity.callMe(parent);
                    }
                }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                    }
                });
        AlertDialog alertDialog = builder.create();
        alertDialog.show();
    }

    public static void exportSelectedAccount(final Activity parent) {
        final WalletAccount account = MbwManager.getInstance(parent).getSelectedAccount();
        if (!(account instanceof ExportableAccount)) {
            return;
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(parent);
        builder.setMessage(R.string.export_account_data_warning).setCancelable(true)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.dismiss();
                        Intent intent = ExportAsQrCodeActivity.getIntent(parent,
                                ((ExportableAccount) account).getExportData(AesKeyCipher.defaultKeyCipher()));
                        parent.startActivity(intent);
                    }
                }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                    }
                });
        AlertDialog alertDialog = builder.create();
        alertDialog.show();
    }

    /**
     * Prevent the OS from taking screenshots for the specified activity
     */
    public static void preventScreenshots(Activity activity) {
        // looks like gingerbread devices have this issue more commonly than
        // thought.
        // future: make a setting for this, and somehow gather feedback what
        // works,
        // and a positive list of devices.
        if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.GINGERBREAD
                || android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD_MR1) {
            return;
        }
        activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
                WindowManager.LayoutParams.FLAG_SECURE);
    }

    public static List<WalletAccount> sortAccounts(List<WalletAccount> accounts, final MetadataStorage storage) {
        Ordering<WalletAccount> type = Ordering.natural().onResultOf(new Function<WalletAccount, Integer>() {
            @Nullable
            @Override
            public Integer apply(@Nullable WalletAccount input) {
                if (input instanceof Bip44Account) {
                    return 0;
                }
                if (input instanceof SingleAddressAccount) {
                    return 1;
                }
                if (input instanceof CoinapultAccount) {
                    return 3; //coinapult last
                }
                return 2;
            }
        });
        Ordering<WalletAccount> index = Ordering.natural().onResultOf(new Function<WalletAccount, Integer>() {
            @Nullable
            @Override
            public Integer apply(@Nullable WalletAccount input) {
                if (input instanceof Bip44Account) {
                    Bip44Account bip44Account = (Bip44Account) input;
                    return bip44Account.getAccountIndex();
                }
                return Integer.MAX_VALUE;
            }
        });

        Ordering<WalletAccount> name = Ordering.natural().onResultOf(new Function<WalletAccount, String>() {
            @Nullable
            @Override
            public String apply(@Nullable WalletAccount input) {
                return storage.getLabelByAccount(input.getId());
            }
        });
        return type.compound(index).compound(name).sortedCopy(accounts);
    }

    public static List<Address> sortAddresses(List<Address> addresses) {
        return Ordering.usingToString().sortedCopy(addresses);
    }

    public static List<AddressBookManager.Entry> sortAddressbookEntries(List<AddressBookManager.Entry> entries) {
        return Ordering.natural().onResultOf(ENTRY_NAME).sortedCopy(entries);
    }

    public static Drawable getDrawableForAccount(WalletAccount walletAccount, boolean isSelectedAccount,
            Resources resources) {
        // Watch only
        if (!walletAccount.canSpend()) {
            return null;
        }

        //trezor account
        if (walletAccount instanceof Bip44AccountExternalSignature) {
            int accountType = ((Bip44AccountExternalSignature) walletAccount).getAccountType();
            if (accountType == Bip44AccountContext.ACCOUNT_TYPE_UNRELATED_X_PUB_EXTERNAL_SIG_LEDGER) {
                return resources.getDrawable(R.drawable.ledger_icon);
            } else if (accountType == Bip44AccountContext.ACCOUNT_TYPE_UNRELATED_X_PUB_EXTERNAL_SIG_KEEPKEY) {
                return resources.getDrawable(R.drawable.keepkey_icon);
            } else {
                return resources.getDrawable(R.drawable.trezor_icon_only);
            }

        }
        //regular HD account
        if (walletAccount instanceof Bip44Account) {
            return resources.getDrawable(R.drawable.multikeys_grey);
        }
        if (walletAccount instanceof CoinapultAccount) {
            if (isSelectedAccount) {
                return resources.getDrawable(R.drawable.coinapult);
            } else {
                return resources.getDrawable(R.drawable.coinapultgrey);
            }
        }

        //single key account
        return resources.getDrawable(R.drawable.singlekey_grey);
    }

    public static String getNameForNewAccount(WalletAccount account, Context context) {
        if (account instanceof Bip44AccountExternalSignature) {
            String baseName;
            int accountType = ((Bip44AccountExternalSignature) account).getAccountType();
            if (accountType == Bip44AccountContext.ACCOUNT_TYPE_UNRELATED_X_PUB_EXTERNAL_SIG_LEDGER) {
                baseName = MbwManager.getInstance(context).getLedgerManager().getLabelOrDefault();
            } else if (accountType == Bip44AccountContext.ACCOUNT_TYPE_UNRELATED_X_PUB_EXTERNAL_SIG_KEEPKEY) {
                baseName = MbwManager.getInstance(context).getKeepKeyManager().getLabelOrDefault();
            } else {
                baseName = MbwManager.getInstance(context).getTrezorManager().getLabelOrDefault();
            }
            return baseName + " #" + (((Bip44AccountExternalSignature) account).getAccountIndex() + 1);
        } else if (account instanceof Bip44PubOnlyAccount) {
            return context.getString(R.string.account_prefix_imported);
        } else if (account instanceof Bip44Account) {
            return context.getString(R.string.account) + " " + (((Bip44Account) account).getAccountIndex() + 1);
        } else {
            return DateFormat.getMediumDateFormat(context).format(new Date());
        }
    }

    public static boolean isAllowedForLocalTrader(WalletAccount account) {
        if (account instanceof CoinapultAccount) {
            return false; //we do not support coinapult accs in lt (yet)
        }
        if (!account.getReceivingAddress().isPresent()) {
            return false; // the account has no valid receiving address (should not happen) - dont use it
        }
        return true; //all other account types including trezor accs are fine
    }

    public static String getFormattedDate(Context context, Date date) {
        Locale locale = context.getResources().getConfiguration().locale;
        java.text.DateFormat format;
        Calendar now = Calendar.getInstance(locale);
        Calendar toFormat = Calendar.getInstance(locale);
        toFormat.setTime(date);
        // show the date part if it is not today
        if (toFormat.get(Calendar.YEAR) == now.get(Calendar.YEAR)
                && toFormat.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR)) {
            format = java.text.DateFormat.getTimeInstance(java.text.DateFormat.MEDIUM, locale);
        } else {
            format = java.text.DateFormat.getDateTimeInstance(java.text.DateFormat.SHORT,
                    java.text.DateFormat.MEDIUM, locale);
        }
        return format.format(date);
    }

    public static String getFormattedValue(CurrencyValue value, CoinUtil.Denomination denomination) {
        if (value == null) {
            return "";
        }

        BigDecimal val = value.getValue();
        if (val == null) {
            return "";
        }
        if (value.isBtc()) {
            return CoinUtil.valueString(val, denomination, false);
        } else {

            return FIAT_FORMAT.format(val);
        }
    }

    public static String getFormattedValue(CurrencyValue value, CoinUtil.Denomination denomination, int precision) {
        if (value == null) {
            return "";
        }

        BigDecimal val = value.getValue();
        if (val == null) {
            return "";
        }
        if (value.isBtc()) {
            return CoinUtil.valueString(((BitcoinValue) value).getLongValue(), denomination, precision);
        } else {
            if (!formatCache.containsKey(precision)) {
                DecimalFormat fiatFormat = (DecimalFormat) FIAT_FORMAT.clone();
                fiatFormat.setMaximumFractionDigits(precision);
                formatCache.put(precision, fiatFormat);
            }
            return formatCache.get(precision).format(val);
        }
    }

    public static String getFormattedValueWithUnit(CurrencyValue value, CoinUtil.Denomination denomination) {
        if (value == null) {
            return "";
        }

        if (value.isBtc()) {
            return getFormattedValueWithUnit((BitcoinValue) value, denomination);
        } else {
            BigDecimal val = value.getValue();
            if (val == null) {
                return "";
            }
            return String.format("%s %s", FIAT_FORMAT.format(val), value.getCurrency());
        }
    }

    // prevent ambiguous call for ExactBitcoinValue
    public static String getFormattedValueWithUnit(ExactBitcoinValue value, CoinUtil.Denomination denomination) {
        return getFormattedValueWithUnit((BitcoinValue) value, denomination);
    }

    public static String getFormattedValueWithUnit(BitcoinValue value, CoinUtil.Denomination denomination) {
        BigDecimal val = value.getValue();
        if (val == null) {
            return "";
        }
        return String.format("%s %s", CoinUtil.valueString(val, denomination, false),
                denomination.getUnicodeName());
    }

    public static String getFormattedValueWithUnit(CurrencyValue value, CoinUtil.Denomination denomination,
            int precision) {
        if (value == null) {
            return "";
        }

        BigDecimal val = value.getValue();
        if (val == null) {
            return "";
        }

        if (value.isBtc()) {
            return String.format("%s %s",
                    CoinUtil.valueString(((BitcoinValue) value).getLongValue(), denomination, precision),
                    denomination.getUnicodeName());
        } else {
            if (!formatCache.containsKey(precision)) {
                DecimalFormat fiatFormat = (DecimalFormat) FIAT_FORMAT.clone();
                fiatFormat.setMaximumFractionDigits(precision);
                formatCache.put(precision, fiatFormat);
            }
            return String.format("%s %s", formatCache.get(precision).format(val), value.getCurrency());
        }
    }

    public static boolean isValidEmailAddress(String value) {
        return android.util.Patterns.EMAIL_ADDRESS.matcher(value).matches();
    }

    public static boolean openWebsite(Context context, String uri) {
        Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
        if (browserIntent.resolveActivity(context.getPackageManager()) != null) {
            context.startActivity(browserIntent);
            return true;
        } else {
            return false;
        }
    }
}