/* * * * Copyright (C) 2014 Open Whisper Systems * * 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 org.anhonesteffort.flock; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.stripe.exception.StripeException; import com.stripe.model.Receiver; import de.passsy.holocircularprogressbar.HoloCircularProgressBar; import org.anhonesteffort.flock.util.guava.Optional; import org.anhonesteffort.flock.auth.DavAccount; import org.anhonesteffort.flock.registration.OwsRegistration; import org.anhonesteffort.flock.registration.model.FlockAccount; import java.text.DecimalFormat; import java.util.Date; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; /** * Programmer: rhodey */ public class SendBitcoinActivity extends Activity { private static final String TAG = "org.anhonesteffort.flock.SendBitcoinActivity"; public static final String KEY_DAV_ACCOUNT_BUNDLE = "KEY_DAV_ACCOUNT_BUNDLE"; private final Handler uiHandler = new Handler(); private Timer intervalTimer = new Timer(); private AsyncTask asyncTaskBtc; private DavAccount davAccount; private Optional<Receiver> btcReceiver = Optional.absent(); private Optional<Bitmap> qrCodeBitmap = Optional.absent(); private Optional<String> lastBtcUri = Optional.absent(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); requestWindowFeature(Window.FEATURE_PROGRESS); setContentView(R.layout.activity_send_bitcoin); getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setTitle(R.string.title_send_bitcoin); if (savedInstanceState != null && !savedInstanceState.isEmpty()) { if (!DavAccount.build(savedInstanceState.getBundle(KEY_DAV_ACCOUNT_BUNDLE)).isPresent()) { finish(); return; } davAccount = DavAccount.build(savedInstanceState.getBundle(KEY_DAV_ACCOUNT_BUNDLE)).get(); } else if (getIntent().getExtras() != null) { if (!DavAccount.build(getIntent().getExtras().getBundle(KEY_DAV_ACCOUNT_BUNDLE)).isPresent()) { finish(); return; } davAccount = DavAccount.build(getIntent().getExtras().getBundle(KEY_DAV_ACCOUNT_BUNDLE)).get(); } initButtons(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); break; } return false; } @Override public void onResume() { super.onResume(); handleStartPerpetualRefresh(); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBundle(KEY_DAV_ACCOUNT_BUNDLE, davAccount.toBundle()); super.onSaveInstanceState(savedInstanceState); } @Override public void onPause() { super.onPause(); if (asyncTaskBtc != null && !asyncTaskBtc.isCancelled()) asyncTaskBtc.cancel(true); if (intervalTimer != null) intervalTimer.cancel(); } private boolean handleLaunchBtcWallet(String btcURI) { Log.d(TAG, "handleLaunchBtcWallet()"); Intent btcWalletIntent = new Intent(Intent.ACTION_VIEW); btcWalletIntent.setData(Uri.parse(btcURI)); try { startActivity(btcWalletIntent); Log.d(TAG, "I think something was actually launched"); } catch (ActivityNotFoundException e) { return false; } return true; } private void initButtons() { findViewById(R.id.button_cancel).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); findViewById(R.id.layout_btc_address).setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (!btcReceiver.isPresent()) return false; if (event.getAction() == MotionEvent.ACTION_DOWN && !handleLaunchBtcWallet(btcReceiver.get().getBitcoinUri())) { TextView addressBtcView = (TextView)findViewById(R.id.btc_address); View triangleView = findViewById(R.id.btc_address_triangle); addressBtcView.setBackgroundResource(R.drawable.rounded_thing_orange); triangleView.setBackgroundResource(R.drawable.conversation_item_received_triangle_shape_orange); ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("oioi", btcReceiver.get().getInboundAddress()); clipboard.setPrimaryClip(clip); Toast.makeText(getBaseContext(), R.string.address_copied_to_clipboard, Toast.LENGTH_SHORT).show(); } else if (event.getAction() == MotionEvent.ACTION_UP) { TextView addressBtcView = (TextView) findViewById(R.id.btc_address); View triangleView = findViewById(R.id.btc_address_triangle); addressBtcView.setBackgroundResource(R.drawable.rounded_thing_grey); triangleView.setBackgroundResource(R.drawable.conversation_item_received_triangle_shape_grey); } return true; } }); findViewById(R.id.layout_btc_address).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!btcReceiver.isPresent()) return; TextView addressBtcView = (TextView)findViewById(R.id.btc_address); addressBtcView.setBackgroundResource(R.drawable.rounded_thing_orange); ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("oioi", btcReceiver.get().getInboundAddress()); clipboard.setPrimaryClip(clip); Toast.makeText(getBaseContext(), R.string.address_copied_to_clipboard, Toast.LENGTH_SHORT).show(); } }); } private void handleUpdateUi() { ImageView qrCodeView = (ImageView)findViewById(R.id.image_btc_qr_code); TextView microBtcLargeView = (TextView)findViewById(R.id.mbtc_due_large); TextView microBtcSmallView = (TextView)findViewById(R.id.mbtc_due_small); TextView addressBtcView = (TextView)findViewById(R.id.btc_address); TextView minutesRemainingView = (TextView)findViewById(R.id.btc_minutes_remaining); HoloCircularProgressBar progressBarView = (HoloCircularProgressBar)findViewById(R.id.btc_address_progress); if (!btcReceiver.isPresent()) return; if (!qrCodeBitmap.isPresent() || (lastBtcUri.isPresent() && !lastBtcUri.get().equals(btcReceiver.get().getBitcoinUri()))) { try { qrCodeBitmap = encodeAsBitmap(btcReceiver.get().getBitcoinUri(), BarcodeFormat.AZTEC, qrCodeView.getWidth(), qrCodeView.getHeight()); } catch (WriterException e) { Log.e(TAG, "caught exception while encoding BTC URI", e); } if (qrCodeBitmap.isPresent()) qrCodeView.setImageBitmap(qrCodeBitmap.get()); } else qrCodeView.setImageBitmap(qrCodeBitmap.get()); lastBtcUri = Optional.of(btcReceiver.get().getBitcoinUri()); Double amountMBtc = btcReceiver.get().getBitcoinAmount() / 100000000.0 * 1000.0; int decimal_index = amountMBtc.toString().indexOf("."); String amountMBtcRemainder = null; if (decimal_index + 3 > amountMBtc.toString().length()) amountMBtcRemainder = "0"; else if ((amountMBtc.toString().length() - decimal_index) > 6) amountMBtcRemainder = amountMBtc.toString().substring(decimal_index + 3, decimal_index + 9); else amountMBtcRemainder = amountMBtc.toString().substring(decimal_index + 3); microBtcLargeView.setText(new DecimalFormat("0.00").format(amountMBtc)); microBtcSmallView.setText(amountMBtcRemainder); String addressBtcFormatted = ""; for(int i = 0; i < btcReceiver.get().getInboundAddress().length(); i++) { if (i % 4 == 0 && i != 0) addressBtcFormatted += " "; addressBtcFormatted += btcReceiver.get().getInboundAddress().charAt(i); } addressBtcView.setText(addressBtcFormatted); long secondsRemaining = ((btcReceiver.get().getCreated() * 1000) - new Date().getTime()) / 1000 + ((10 * 60) - 1); Integer minutesRemaining = (int) (secondsRemaining / 60); if ((secondsRemaining - (minutesRemaining * 60)) < 0) { minutesRemainingView.setText("0:00"); progressBarView.setProgress(1.0F); } else if ((secondsRemaining - (minutesRemaining * 60)) < 10) { minutesRemainingView.setText(minutesRemaining + ":0" + (secondsRemaining - (minutesRemaining * 60))); progressBarView.setProgress(1.0F - ((float) secondsRemaining / (10 * 60))); } else { minutesRemainingView.setText(minutesRemaining + ":" + (secondsRemaining - (minutesRemaining * 60))); progressBarView.setProgress(1.0F - ((float) secondsRemaining / (10 * 60))); } } private void handleRefreshBitcoinReceiver() { asyncTaskBtc = new AsyncTask<String, Void, Bundle>() { private Receiver receiver; @Override protected void onPreExecute() { Log.d(TAG, "handleRefreshBitcoinReceiver()"); setProgressBarIndeterminateVisibility(true); setProgressBarVisibility(true); } private void handleExpireStoredBtcReceiver() { String KEY_ID_BTC_RECEIVER = "KEY_ID_BTC_RECEIVER"; SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); String btcReceiverId = preferences.getString(KEY_ID_BTC_RECEIVER, null); if (btcReceiverId != null) preferences.edit().remove(KEY_ID_BTC_RECEIVER).apply(); } private Receiver handleCreateNewBtcReceiver(Double costUsd) throws StripeException { Map<String, Object> receiverParams = new HashMap<String, Object>(); receiverParams.put("currency", "usd"); receiverParams.put("amount", (int) (costUsd * 100)); receiverParams.put("description", "Flock Subscription"); receiverParams.put("email", davAccount.getUserId()); return Receiver.create(receiverParams, OwsRegistration.STRIPE_PUBLIC_KEY); } private Receiver getOrCreateNewBtcReceiver(Double costUsd) throws StripeException { Receiver btcReceiver = null; String KEY_ID_BTC_RECEIVER = "KEY_ID_BTC_RECEIVER"; SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); String btcReceiverId = preferences.getString(KEY_ID_BTC_RECEIVER, null); if (btcReceiverId != null) { btcReceiver = Receiver.retrieve(btcReceiverId, OwsRegistration.STRIPE_PUBLIC_KEY); Long timeCreated = btcReceiver.getCreated() * 1000; if ((timeCreated + (10 * 60 * 1000)) < new Date().getTime()) { Log.d(TAG, "btc receiver expired, creating new"); btcReceiverId = null; } } if (btcReceiverId == null) { btcReceiver = handleCreateNewBtcReceiver(costUsd); preferences.edit().putString(KEY_ID_BTC_RECEIVER, btcReceiver.getId()).apply(); return btcReceiver; } return Receiver.retrieve(btcReceiverId, OwsRegistration.STRIPE_PUBLIC_KEY); } @Override protected Bundle doInBackground(String... params) { Bundle result = new Bundle(); Double costPerYearUsd = (double) getResources().getInteger(R.integer.cost_per_year_usd); try { receiver = getOrCreateNewBtcReceiver(costPerYearUsd); } catch (StripeException e) { Log.e(TAG, "stripe is mad", e); ErrorToaster.handleBundleError(e, result); } return result; } @Override protected void onPostExecute(Bundle result) { Log.d(TAG, "STATUS: " + result.getInt(ErrorToaster.KEY_STATUS_CODE)); asyncTaskBtc = null; setProgressBarIndeterminateVisibility(false); setProgressBarVisibility(false); if (receiver != null) { btcReceiver = Optional.of(receiver); handleUpdateUi(); if (receiver.getFilled()) { handleExpireStoredBtcReceiver(); Toast.makeText(getBaseContext(), R.string.bitcoin_received, Toast.LENGTH_SHORT).show(); finish(); } } else ErrorToaster.handleDisplayToastBundledError(getBaseContext(), result); } }.execute(); } private final Runnable refreshUiRunnable = new Runnable() { @Override public void run() { handleUpdateUi(); } }; private final Runnable refreshBtcRunnable = new Runnable() { @Override public void run() { if (asyncTaskBtc == null || asyncTaskBtc.isCancelled()) handleRefreshBitcoinReceiver(); } }; private void handleStartPerpetualRefresh() { intervalTimer = new Timer(); TimerTask uiTask = new TimerTask() { @Override public void run() { uiHandler.post(refreshUiRunnable); } }; TimerTask btcTask = new TimerTask() { @Override public void run() { uiHandler.post(refreshBtcRunnable); } }; intervalTimer.schedule(uiTask, 0, 1000); intervalTimer.schedule(btcTask, 0, 10000); } Optional<Bitmap> encodeAsBitmap(String dataToEncode, BarcodeFormat format, int bitmap_width, int bitmap_height) throws WriterException { Map<EncodeHintType, Object> hints = new EnumMap<EncodeHintType, Object>(EncodeHintType.class); hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); MultiFormatWriter writer = new MultiFormatWriter(); BitMatrix result; try { result = writer.encode(dataToEncode, format, bitmap_width, bitmap_height, hints); } catch (IllegalArgumentException e) { Log.e(TAG, "caught exception while attempting to encode " + dataToEncode + " as bitmap", e); return Optional.absent(); } int[] pixel_array = new int[result.getWidth() * result.getHeight()]; for (int y = 0; y < result.getHeight(); y++) { for (int x = 0; x < result.getWidth(); x++) pixel_array[(y * result.getWidth()) + x] = result.get(x, y) ? 0xFF000000 : 0xFFFFFFFF; } Bitmap bitmap = Bitmap.createBitmap(result.getWidth(), result.getHeight(), Bitmap.Config.ARGB_8888); bitmap.setPixels(pixel_array, 0, result.getWidth(), 0, 0, result.getWidth(), result.getHeight()); return Optional.of(bitmap); } }