/* * Copyright 2011-2013 the original author or authors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.schildbach.wallet.digitalcoin.util; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileReader; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Method; import java.math.BigInteger; import java.text.DateFormat; import java.text.ParseException; import java.util.Date; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Typeface; import android.text.Editable; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.format.DateUtils; import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; import com.google.digitalcoin.core.Address; import com.google.digitalcoin.core.AddressFormatException; import com.google.digitalcoin.core.DumpedPrivateKey; import com.google.digitalcoin.core.ECKey; import com.google.digitalcoin.core.ScriptException; import com.google.digitalcoin.core.Sha256Hash; import com.google.digitalcoin.core.Transaction; import com.google.digitalcoin.core.TransactionInput; import com.google.digitalcoin.core.TransactionOutput; import com.google.digitalcoin.core.Utils; 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 de.schildbach.wallet.digitalcoin.Constants; /** * @author Andreas Schildbach */ public class WalletUtils { public final static QRCodeWriter QR_CODE_WRITER = new QRCodeWriter(); public static Bitmap getQRCodeBitmap(final String url, final int size) { try { final Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>(); hints.put(EncodeHintType.MARGIN, 0); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); final BitMatrix result = QR_CODE_WRITER.encode(url, BarcodeFormat.QR_CODE, size, size, 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.TRANSPARENT; } } final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bitmap.setPixels(pixels, 0, width, 0, 0, width, height); return bitmap; } catch (final WriterException x) { x.printStackTrace(); return null; } } public static Editable formatAddress(final Address address, final int groupSize, final int lineSize) { return formatHash(address.toString(), groupSize, lineSize); } public static Editable formatAddress(final String prefix, final Address address, final int groupSize, final int lineSize) { return formatHash(prefix, address.toString(), groupSize, lineSize, Constants.CHAR_THIN_SPACE); } public static Editable formatHash(final String address, final int groupSize, final int lineSize) { return formatHash(null, address, groupSize, lineSize, Constants.CHAR_THIN_SPACE); } public static long longHash(final Sha256Hash hash) { final byte[] bytes = hash.getBytes(); return (bytes[31] & 0xFFl) | ((bytes[30] & 0xFFl) << 8) | ((bytes[29] & 0xFFl) << 16) | ((bytes[28] & 0xFFl) << 24) | ((bytes[27] & 0xFFl) << 32) | ((bytes[26] & 0xFFl) << 40) | ((bytes[25] & 0xFFl) << 48) | ((bytes[23] & 0xFFl) << 56); } public static Editable formatHash(final String prefix, final String address, final int groupSize, final int lineSize, final char groupSeparator) { final SpannableStringBuilder builder = prefix != null ? new SpannableStringBuilder(prefix) : new SpannableStringBuilder(); final int len = address.length(); for (int i = 0; i < len; i += groupSize) { final int end = i + groupSize; final String part = address.substring(i, end < len ? end : len); builder.append(part); builder.setSpan(new TypefaceSpan("monospace"), builder.length() - part.length(), builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); if (end < len) { final boolean endOfLine = lineSize > 0 && end % lineSize == 0; builder.append(endOfLine ? '\n' : groupSeparator); } } return builder; } public static String formatValue(final BigInteger value, final int precision) { return formatValue(value, "", "-", precision); } public static String formatValue(final BigInteger value, final String plusSign, final String minusSign, final int precision) { final boolean negative = value.compareTo(BigInteger.ZERO) < 0; final BigInteger absValue = value.abs(); final String sign = negative ? minusSign : plusSign; final int coins = absValue.divide(Utils.COIN).intValue(); final int cents = absValue.remainder(Utils.COIN).intValue(); if (cents % 1000000 == 0 || precision <= 2) return String.format(Locale.US, "%s%d.%02d", sign, coins, cents / 1000000 + cents % 1000000 / 500000); else if (cents % 10000 == 0 || precision <= 4) return String.format(Locale.US, "%s%d.%04d", sign, coins, cents / 10000 + cents % 10000 / 5000); else if (precision <= 6) return String.format(Locale.US, "%s%d.%06d", sign, coins, cents / 100 + cents % 100 / 50); else return String.format(Locale.US, "%s%d.%08d", sign, coins, cents); } private static final Pattern P_SIGNIFICANT = Pattern.compile("^([-+]" + Constants.CHAR_THIN_SPACE + ")?\\d*(\\.\\d{0,2})?"); private static final Object SIGNIFICANT_SPAN = new StyleSpan(Typeface.BOLD); public static final RelativeSizeSpan SMALLER_SPAN = new RelativeSizeSpan(0.85f); public static void formatSignificant(final Editable s, final RelativeSizeSpan insignificantRelativeSizeSpan) { s.removeSpan(SIGNIFICANT_SPAN); if (insignificantRelativeSizeSpan != null) s.removeSpan(insignificantRelativeSizeSpan); final Matcher m = P_SIGNIFICANT.matcher(s); if (m.find()) { final int pivot = m.group().length(); s.setSpan(SIGNIFICANT_SPAN, 0, pivot, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); if (s.length() > pivot && insignificantRelativeSizeSpan != null) s.setSpan(insignificantRelativeSizeSpan, pivot, s.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } public static BigInteger localValue(final BigInteger ltcValue, final BigInteger rate) { return ltcValue.multiply(rate).divide(Utils.COIN); } public static BigInteger ltcValue(final BigInteger localValue, final BigInteger rate) { return localValue.multiply(Utils.COIN).divide(rate); } public static Address getFromAddress(final Transaction tx) { try { for (final TransactionInput input : tx.getInputs()) { return input.getFromAddress(); } throw new IllegalStateException(); } catch (final ScriptException x) { // this will happen on inputs connected to coinbase transactions return null; } } public static Address getToAddress(final Transaction tx) { try { for (final TransactionOutput output : tx.getOutputs()) { return output.getScriptPubKey().getToAddress(); } throw new IllegalStateException(); } catch (final ScriptException x) { return null; } } public static void writeKeys(final Writer out, final List<ECKey> keys) throws IOException { final DateFormat format = Iso8601Format.newDateTimeFormatT(); out.write("# KEEP YOUR PRIVATE KEYS SAFE! Anyone who can read this can spend your digitalcoins.\n"); for (final ECKey key : keys) { out.write(key.getPrivateKeyEncoded(Constants.NETWORK_PARAMETERS).toString()); if (key.getCreationTimeSeconds() != 0) { out.write(' '); out.write(format.format(new Date(key.getCreationTimeSeconds() * DateUtils.SECOND_IN_MILLIS))); } out.write('\n'); } } public static List<ECKey> readKeys(final BufferedReader in) throws IOException { try { final DateFormat format = Iso8601Format.newDateTimeFormatT(); final List<ECKey> keys = new LinkedList<ECKey>(); while (true) { final String line = in.readLine(); if (line == null) break; // eof if (line.trim().length() == 0 || line.charAt(0) == '#') continue; // skip comment final String[] parts = line.split(" "); final ECKey key = new DumpedPrivateKey(Constants.NETWORK_PARAMETERS, parts[0]).getKey(); key.setCreationTimeSeconds(parts.length >= 2 ? format.parse(parts[1]).getTime() / DateUtils.SECOND_IN_MILLIS : 0); keys.add(key); } return keys; } catch (final AddressFormatException x) { throw new IOException("cannot read keys: " + x); } catch (final ParseException x) { throw new IOException("cannot read keys: " + x); } } public static final FileFilter KEYS_FILE_FILTER = new FileFilter() { public boolean accept(final File file) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file)); WalletUtils.readKeys(reader); return true; } catch (final IOException x) { return false; } finally { if (reader != null) { try { reader.close(); } catch (final IOException x) { x.printStackTrace(); } } } } }; @SuppressWarnings({ "rawtypes", "unchecked" }) public static void chmod(final File path, final int mode) { try { final Class fileUtils = Class.forName("android.os.FileUtils"); final Method setPermissions = fileUtils.getMethod("setPermissions", String.class, int.class, int.class, int.class); setPermissions.invoke(null, path.getAbsolutePath(), mode, -1, -1); } catch (final Exception x) { x.printStackTrace(); } } }