public class ZBarScannerActivity extends Activity implements Camera.PreviewCallback, ZBarConstants {

	private static final String TAG = "ZBarScannerActivity";
	private CameraPreview mPreview;
	private Camera mCamera;
	private ImageScanner mScanner;
	private Handler mAutoFocusHandler;
	private boolean mPreviewing = true;

	static {
		System.loadLibrary("iconv");
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		if(!isCameraAvailable()) {
			// Cancel request if there is no rear-facing camera.
			cancelRequest();
			return;
		}

		// Hide the window title.
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

		mAutoFocusHandler = new Handler();

		// Create and configure the ImageScanner;
		setupScanner();

		// Create a RelativeLayout container that will hold a SurfaceView,
		// and set it as the content of our activity.
		mPreview = new CameraPreview(this, this, autoFocusCB);
		setContentView(mPreview);
	}

	public void setupScanner() {
		mScanner = new ImageScanner();
		mScanner.setConfig(0, Config.X_DENSITY, 3);
		mScanner.setConfig(0, Config.Y_DENSITY, 3);

		int[] symbols = getIntent().getIntArrayExtra(SCAN_MODES);
		if (symbols != null) {
			mScanner.setConfig(Symbol.NONE, Config.ENABLE, 0);
			for (int symbol : symbols) {
				mScanner.setConfig(symbol, Config.ENABLE, 1);
			}
		}
	}

	private Camera openFrontFacingCameraGingerbread() {
		int cameraCount = 0;
		Camera cam = null;
		Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
		cameraCount = Camera.getNumberOfCameras();
		
		//Try and open a back camera
		for ( int camIdx = 0; camIdx < cameraCount; camIdx++ ) {
			Camera.getCameraInfo( camIdx, cameraInfo );
			if ( cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK  ) {
				try {
					cam = Camera.open( camIdx );
					break;
				} catch (RuntimeException e) {
					e.printStackTrace();
				}
			}
		}

		//Try and open a front camera
		if (cam == null) {
			for ( int camIdx = 0; camIdx < cameraCount; camIdx++ ) {
				Camera.getCameraInfo( camIdx, cameraInfo );
				if ( cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT  ) {
					try {
						cam = Camera.open( camIdx );
						break;
					} catch (RuntimeException e) {
						e.printStackTrace();
					}
				}
			}
		}

		return cam;
	}

	@Override
	protected void onResume() {
		super.onResume();

		// Open the default i.e. the first rear facing camera.
		mCamera = openFrontFacingCameraGingerbread();
		if(mCamera == null) {
			// Cancel request if mCamera is null.
			cancelRequest();
			return;
		}

		mPreview.setCamera(mCamera);
		mPreview.showSurfaceView();

		mPreviewing = true;
	}

	@Override
	protected void onPause() {
		super.onPause();

		// Because the Camera object is a shared resource, it's very
		// important to release it when the activity is paused.
		if (mCamera != null) {
			mPreview.setCamera(null);
			mCamera.cancelAutoFocus();
			mCamera.setPreviewCallback(null);
			mCamera.stopPreview();
			mCamera.release();

			// According to Jason Kuang on http://stackoverflow.com/questions/6519120/how-to-recover-camera-preview-from-sleep,
			// there might be surface recreation problems when the device goes to sleep. So lets just hide it and
			// recreate on resume
			mPreview.hideSurfaceView();

			mPreviewing = false;
			mCamera = null;
		}
	}

	public boolean isCameraAvailable() {
		PackageManager pm = getPackageManager();
		return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
	}

	public void cancelRequest() {
		Intent dataIntent = new Intent();
		dataIntent.putExtra(ERROR_INFO, "Camera unavailable");
		setResult(Activity.RESULT_CANCELED, dataIntent);
		finish();
	}

	public void onPreviewFrame(byte[] data, Camera camera) {
		Camera.Parameters parameters = camera.getParameters();
		Camera.Size size = parameters.getPreviewSize();

		Image barcode = new Image(size.width, size.height, "Y800");
		barcode.setData(data);

		int result = mScanner.scanImage(barcode);

		if (result != 0) {
			mCamera.cancelAutoFocus();
			mCamera.setPreviewCallback(null);
			mCamera.stopPreview();
			mPreviewing = false;
			SymbolSet syms = mScanner.getResults();
			for (Symbol sym : syms) {
				String symData = sym.getData();
				if (!TextUtils.isEmpty(symData)) {
					Intent dataIntent = new Intent();
					dataIntent.putExtra(SCAN_RESULT, symData);
					dataIntent.putExtra(SCAN_RESULT_TYPE, sym.getType());
					setResult(Activity.RESULT_OK, dataIntent);
					finish();
					break;
				}
			}
		}
	}
	private Runnable doAutoFocus = new Runnable() {
		public void run() {
			if(mCamera != null && mPreviewing) {
				mCamera.autoFocus(autoFocusCB);
			}
		}
	};

	// Mimic continuous auto-focusing
	Camera.AutoFocusCallback autoFocusCB = new Camera.AutoFocusCallback() {
		public void onAutoFocus(boolean success, Camera camera) {
			mAutoFocusHandler.postDelayed(doAutoFocus, 1000);
		}
	};
} public class AccessFactory	{

    public static final int MIN_PIN_LENGTH = 5;
    public static final int MAX_PIN_LENGTH = 8;

    private static boolean isLoggedIn = false;

    private static Context context = null;
    private static AccessFactory instance = null;

    private AccessFactory()	{ ; }

    public static AccessFactory getInstance(Context ctx) {
    	
    	context = ctx;

        if (instance == null) {
            instance = new AccessFactory();
        }

        return instance;
    }

    public void setIsLoggedIn(boolean logged) {
        isLoggedIn = logged;
    }

} public class APIFactory	{

    private static long xpub_balance = 0L;
    private static HashMap<String, Long> xpub_amounts = null;
    private static HashMap<String,List<Tx>> xpub_txs = null;
    private static HashMap<String,Integer> unspentAccounts = null;
    private static HashMap<String,Integer> unspentBIP49 = null;
    private static HashMap<String,Integer> unspentBIP84 = null;
    private static HashMap<String,String> unspentPaths = null;
    private static HashMap<String,UTXO> utxos = null;

    private static APIFactory instance = null;
    private static Context context = null;

    private APIFactory()	{ ; }

    public static APIFactory getInstance(Context ctx) {

        context = ctx;

        if(instance == null) {
            xpub_amounts = new HashMap<String, Long>();
            xpub_txs = new HashMap<String,List<Tx>>();
            xpub_balance = 0L;
            unspentPaths = new HashMap<String, String>();
            unspentAccounts = new HashMap<String, Integer>();
            unspentBIP49 = new HashMap<String, Integer>();
            unspentBIP84 = new HashMap<String, Integer>();
            utxos = new HashMap<String, UTXO>();
            instance = new APIFactory();
        }

        return instance;
    }

    public void wipe() {
        instance = null;
    }

    public JSONObject getXPUB(String[] xpubs) {

        String _url =WebUtil.getAPIUrl(context );

        JSONObject jsonObject  = null;
        xpub_amounts.clear();

        for(int i = 0; i < xpubs.length; i++)   {
            try {
                StringBuilder url = new StringBuilder(_url);
                url.append("multiaddr?active=");
                url.append(xpubs[i]);
//                Log.i("APIFactory", "XPUB:" + url.toString());
                String response = WebUtil.getInstance(context).getURL(url.toString());
                Log.i("APIFactory", "XPUB response:" + response);
                try {
                    jsonObject = new JSONObject(response);
                    xpub_txs.put(xpubs[i], new ArrayList<Tx>());
                    parseXPUB(jsonObject, true);
                }
                catch(JSONException je) {
                    je.printStackTrace();
                    jsonObject = null;
                }
            }
            catch(Exception e) {
                jsonObject = null;
                e.printStackTrace();
            }
        }

        long total_amount = 0L;
        for(String addr : xpub_amounts.keySet())   {
            total_amount += xpub_amounts.get(addr);
        }
        xpub_balance = total_amount;

        return jsonObject;
    }

    public void parseXPUB(JSONObject jsonObject, boolean complete) throws JSONException  {

        if(jsonObject != null)  {

            long latest_block = 0L;

            if(jsonObject.has("info"))  {
                JSONObject infoObj = (JSONObject)jsonObject.get("info");
                if(infoObj.has("latest_block"))  {
                    JSONObject blockObj = (JSONObject)infoObj.get("latest_block");
                    if(blockObj.has("height"))  {
                        latest_block = blockObj.getLong("height");
//                        Log.i("APIFactory", "latest_block " + latest_block);
                    }
                }
            }

            if(jsonObject.has("addresses"))  {

                JSONArray addressesArray = (JSONArray)jsonObject.get("addresses");
                JSONObject addrObj = null;
                for(int i = 0; i < addressesArray.length(); i++)  {
                    addrObj = (JSONObject)addressesArray.get(i);
                    if(addrObj.has("final_balance") && addrObj.has("address"))  {
                        xpub_amounts.put((String)addrObj.get("address"), addrObj.getLong("final_balance"));
                        if(((String)addrObj.get("address")).startsWith("xpub") || ((String)addrObj.get("address")).startsWith("ypub") || ((String)addrObj.get("address")).startsWith("zpub") || ((String)addrObj.get("address")).startsWith("tpub") || ((String)addrObj.get("address")).startsWith("upub") || ((String)addrObj.get("address")).startsWith("vpub"))    {
                            if(AddressFactory.getInstance().xpub2account().containsKey((String) addrObj.get("address")))    {
                                if(addrObj.has("account_index"))    {
                                    AddressFactory.getInstance().setHighestTxReceiveIdx(AddressFactory.getInstance().xpub2account().get((String) addrObj.get("address")), addrObj.getInt("account_index"));
                                }
                                else    {
                                    AddressFactory.getInstance().setHighestTxReceiveIdx(AddressFactory.getInstance().xpub2account().get((String) addrObj.get("address")), 0);
                                }
                                if(addrObj.has("change_index"))    {
                                    AddressFactory.getInstance().setHighestTxChangeIdx(AddressFactory.getInstance().xpub2account().get((String) addrObj.get("address")), addrObj.getInt("change_index"));
                                }
                                else    {
                                    AddressFactory.getInstance().setHighestTxReceiveIdx(AddressFactory.getInstance().xpub2account().get((String) addrObj.get("address")), 0);
                                }
                            }
                        }
                    }
                }

                if(!complete)    {
                    return;
                }
            }

            if(jsonObject.has("txs"))  {

                JSONArray txArray = (JSONArray)jsonObject.get("txs");
                JSONObject txObj = null;
                for(int i = 0; i < txArray.length(); i++)  {

                    txObj = (JSONObject)txArray.get(i);
                    long height = 0L;
                    long amount = 0L;
                    long ts = 0L;
                    String hash = null;
                    String addr = null;
                    String _addr = null;

                    if(txObj.has("block_height"))  {
                        height = txObj.getLong("block_height");
//                        Log.i("APIFactory", "height " + height);
                    }
                    else  {
                        height = -1L;  // 0 confirmations
                    }
                    if(txObj.has("hash"))  {
                        hash = (String)txObj.get("hash");
                    }
                    if(txObj.has("result"))  {
                        amount = txObj.getLong("result");
                    }
                    if(txObj.has("time"))  {
                        ts = txObj.getLong("time");
                    }

                    if(txObj.has("inputs"))  {
                        JSONArray inputArray = (JSONArray)txObj.get("inputs");
                        JSONObject inputObj = null;
                        for(int j = 0; j < inputArray.length(); j++)  {
                            inputObj = (JSONObject)inputArray.get(j);
                            if(inputObj.has("prev_out"))  {
                                JSONObject prevOutObj = (JSONObject)inputObj.get("prev_out");
                                if(prevOutObj.has("xpub"))  {
                                    JSONObject xpubObj = (JSONObject)prevOutObj.get("xpub");
                                    addr = (String)xpubObj.get("m");
                                }
                                else  {
                                    if(SamouraiSentinel.getInstance(context).getLegacy().containsKey((String)prevOutObj.get("addr")))    {
//                                        Log.i("APIFactory:", "legacy tx " + (String)prevOutObj.get("addr"));
                                        addr = (String)prevOutObj.get("addr");
                                    }
                                    else    {
                                        _addr = (String)prevOutObj.get("addr");
                                    }
                                }
                            }
                        }
                    }

                    if(txObj.has("out"))  {
                        JSONArray outArray = (JSONArray)txObj.get("out");
                        JSONObject outObj = null;
                        for(int j = 0; j < outArray.length(); j++)  {
                            outObj = (JSONObject)outArray.get(j);
                            if(outObj.has("xpub"))  {
                                JSONObject xpubObj = (JSONObject)outObj.get("xpub");
                                addr = (String)xpubObj.get("m");
                            }
                            else  {
                                if(SamouraiSentinel.getInstance(context).getLegacy().containsKey((String)outObj.get("addr")))    {
//                                    Log.i("APIFactory:", "legacy tx " + (String)outObj.get("addr"));
                                    addr = (String)outObj.get("addr");
                                }
                                else    {
                                    _addr = (String)outObj.get("addr");
                                }
                            }
                        }
                    }

                    if(addr != null)  {

                        Tx tx = new Tx(hash, _addr, amount, ts, (latest_block > 0L && height > 0L) ? (latest_block - height) + 1 : 0);

                        if(!xpub_txs.containsKey(addr))  {
                            xpub_txs.put(addr, new ArrayList<Tx>());
                        }
                        xpub_txs.get(addr).add(tx);

                    }
                }

            }

        }

    }

    public synchronized JSONObject getAddressInfo(String addr) {

        return getXPUB(new String[] { addr });

    }

    public long getXpubBalance()  {
        return xpub_balance;
    }

    public void setXpubBalance(long value)  {
        xpub_balance = value;
    }

    public HashMap<String,Long> getXpubAmounts()  {
        return xpub_amounts;
    }

    public HashMap<String,List<Tx>> getXpubTxs()  {
        return xpub_txs;
    }

    public List<Tx> getAllXpubTxs()  {

        List<Tx> ret = new ArrayList<Tx>();
        for(String key : xpub_txs.keySet())  {
            List<Tx> txs = xpub_txs.get(key);
            ret.addAll(txs);
        }

        Collections.sort(ret, new TxMostRecentDateComparator());

        return ret;
    }

    public synchronized JSONObject getUnspentOutputs(String[] xpubs) {

        String _url = WebUtil.getAPIUrl(context);

        JSONObject jsonObject  = null;

        try {

            String response = null;


            FormBody body    = new FormBody.Builder()
                    .add("active", StringUtils.join(xpubs, URLEncoder.encode("|", "UTF-8")))
                    .build();



            response = WebUtil.getInstance(context).postURL(_url + "unspent?", body);
            Log.d("APIFactory", "UTXO:" + response);

            parseUnspentOutputs(response);

        }
        catch(Exception e) {
            jsonObject = null;
            e.printStackTrace();
        }

        return jsonObject;
    }

    private synchronized boolean parseUnspentOutputs(String unspents)   {

        if(unspents != null)    {

            try {
                JSONObject jsonObj = new JSONObject(unspents);

                if(jsonObj == null || !jsonObj.has("unspent_outputs"))    {
                    return false;
                }
                JSONArray utxoArray = jsonObj.getJSONArray("unspent_outputs");
                if(utxoArray == null || utxoArray.length() == 0) {
                    return false;
                }

                for (int i = 0; i < utxoArray.length(); i++) {

                    JSONObject outDict = utxoArray.getJSONObject(i);

                    byte[] hashBytes = Hex.decode((String)outDict.get("tx_hash"));
                    Sha256Hash txHash = Sha256Hash.wrap(hashBytes);
                    int txOutputN = ((Number)outDict.get("tx_output_n")).intValue();
                    BigInteger value = BigInteger.valueOf(((Number)outDict.get("value")).longValue());
                    String script = (String)outDict.get("script");
                    byte[] scriptBytes = Hex.decode(script);
                    int confirmations = ((Number)outDict.get("confirmations")).intValue();

                    try {
                        String address = null;
                        if(Bech32Util.getInstance().isBech32Script(script))    {
                            address = Bech32Util.getInstance().getAddressFromScript(script);
                        }
                        else    {
                            address = new Script(scriptBytes).getToAddress(SamouraiSentinel.getInstance().getCurrentNetworkParams()).toString();
                        }

                        if(outDict.has("xpub"))    {
                            JSONObject xpubObj = (JSONObject)outDict.get("xpub");
                            String path = (String)xpubObj.get("path");
                            String m = (String)xpubObj.get("m");
                            unspentPaths.put(address, path);
                            if(SamouraiSentinel.getInstance().getBIP49().containsKey(m))    {
                                unspentBIP49.put(address, 0);   // assume account 0
                            }
                            else if(SamouraiSentinel.getInstance().getBIP84().containsKey(m))    {
                                unspentBIP84.put(address, 0);   // assume account 0
                            }
                            else    {
                                unspentAccounts.put(address, AddressFactory.getInstance(context).xpub2account().get(m));
                            }
                        }
                        else    {
                            ;
                        }

                        // Construct the output
                        MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes, address);
                        outPoint.setConfirmations(confirmations);

                        if(utxos.containsKey(script))    {
                            utxos.get(script).getOutpoints().add(outPoint);
                        }
                        else    {
                            UTXO utxo = new UTXO();
                            utxo.getOutpoints().add(outPoint);
                            utxos.put(script, utxo);
                        }

                    }
                    catch(Exception e) {
                        ;
                    }

                }

                return true;

            }
            catch(JSONException je) {
                ;
            }

        }

        return false;

    }

    public synchronized UTXO getUnspentOutputsForSweep(String address) {

        String _url = WebUtil.getAPIUrl(context);

        try {

            String response = null;

            FormBody body    = new FormBody.Builder()
                    .add("active", address)
                    .build();



            response = WebUtil.getInstance(context).postURL(_url + "unspent?",body);

            return parseUnspentOutputsForSweep(response);

        }
        catch(Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    private synchronized UTXO parseUnspentOutputsForSweep(String unspents)   {

        Log.d("APIFactory", "unspents:" + unspents);

        UTXO utxo = null;

        if(unspents != null)    {

            try {
                JSONObject jsonObj = new JSONObject(unspents);

                if(jsonObj == null || !jsonObj.has("unspent_outputs"))    {
                    return null;
                }
                JSONArray utxoArray = jsonObj.getJSONArray("unspent_outputs");
                if(utxoArray == null || utxoArray.length() == 0) {
                    return null;
                }

//            Log.d("APIFactory", "unspents found:" + outputsRoot.size());

                for (int i = 0; i < utxoArray.length(); i++) {

                    JSONObject outDict = utxoArray.getJSONObject(i);

                    byte[] hashBytes = Hex.decode((String)outDict.get("tx_hash"));
                    Sha256Hash txHash = Sha256Hash.wrap(hashBytes);
                    int txOutputN = ((Number)outDict.get("tx_output_n")).intValue();
                    BigInteger value = BigInteger.valueOf(((Number)outDict.get("value")).longValue());
                    String script = (String)outDict.get("script");
                    byte[] scriptBytes = Hex.decode(script);
                    int confirmations = ((Number)outDict.get("confirmations")).intValue();

                    try {
                        String address = null;
                        if(Bech32Util.getInstance().isBech32Script(script))    {
                            address = Bech32Util.getInstance().getAddressFromScript(script);
                            Log.d("address parsed:", address);
                        }
                        else    {
                            address = new Script(scriptBytes).getToAddress(SamouraiSentinel.getInstance().getCurrentNetworkParams()).toString();
                        }

                        // Construct the output
                        MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes, address);
                        outPoint.setConfirmations(confirmations);
                        if(utxo == null)    {
                            utxo = new UTXO();
                        }
                        utxo.getOutpoints().add(outPoint);

                    }
                    catch(Exception e) {
                        ;
                    }

                }

            }
            catch(JSONException je) {
                ;
            }

        }

        return utxo;

    }

    private class TxMostRecentDateComparator implements Comparator<Tx> {

        public int compare(Tx t1, Tx t2) {

            final int BEFORE = -1;
            final int EQUAL = 0;
            final int AFTER = 1;

            int ret = 0;

            if(t1 == null || t2 == null)    {
                return EQUAL;
            }

            if(t1.getTS() > t2.getTS()) {
                ret = BEFORE;
            }
            else if(t1.getTS() < t2.getTS()) {
                ret = AFTER;
            }
            else    {
                ret = EQUAL;
            }

            return ret;
        }

    }

    public synchronized JSONObject getDynamicFees() {

        JSONObject jsonObject  = null;

        try {
            String url =  WebUtil.getAPIUrl(context).concat("fees");
//            Log.i("APIFactory", "Dynamic fees:" + url.toString());
            String response = WebUtil.getInstance(context).getURL(url);
//            Log.i("APIFactory", "Dynamic fees response:" + response);
            try {
                jsonObject = new JSONObject(response);
                parseDynamicFees_bitcoind(jsonObject);
            }
            catch(JSONException je) {
                je.printStackTrace();
                jsonObject = null;
            }
        }
        catch(Exception e) {
            jsonObject = null;
            e.printStackTrace();
        }

        return jsonObject;
    }

    private synchronized boolean parseDynamicFees_bitcoind(JSONObject jsonObject) throws JSONException  {

        if(jsonObject != null)  {

            //
            // bitcoind
            //
            List<SuggestedFee> suggestedFees = new ArrayList<SuggestedFee>();

            if(jsonObject.has("2"))    {
                long fee = jsonObject.getInt("2");
                SuggestedFee suggestedFee = new SuggestedFee();
                suggestedFee.setDefaultPerKB(BigInteger.valueOf(fee * 1000L));
                suggestedFee.setStressed(false);
                suggestedFee.setOK(true);
                suggestedFees.add(suggestedFee);
            }

            if(jsonObject.has("6"))    {
                long fee = jsonObject.getInt("6");
                SuggestedFee suggestedFee = new SuggestedFee();
                suggestedFee.setDefaultPerKB(BigInteger.valueOf(fee * 1000L));
                suggestedFee.setStressed(false);
                suggestedFee.setOK(true);
                suggestedFees.add(suggestedFee);
            }

            if(jsonObject.has("24"))    {
                long fee = jsonObject.getInt("24");
                SuggestedFee suggestedFee = new SuggestedFee();
                suggestedFee.setDefaultPerKB(BigInteger.valueOf(fee * 1000L));
                suggestedFee.setStressed(false);
                suggestedFee.setOK(true);
                suggestedFees.add(suggestedFee);
            }

            if(suggestedFees.size() > 0)    {
                FeeUtil.getInstance().setEstimatedFees(suggestedFees);

                Log.d("APIFactory", "high fee:" + FeeUtil.getInstance().getHighFee().getDefaultPerKB().toString());
                Log.d("APIFactory", "suggested fee:" + FeeUtil.getInstance().getSuggestedFee().getDefaultPerKB().toString());
                Log.d("APIFactory", "low fee:" + FeeUtil.getInstance().getLowFee().getDefaultPerKB().toString());
            }

            return true;

        }

        return false;

    }

} public class Tx {

    private String strHash = null;
    private String strNote = null;
    private String strDirection = null;
    private String strAddress = null;
    private double amount = 0.0;
    private long confirmations = 0L;
    private long ts = 0L;
    private Map<Integer,String> tags = null;

    public Tx(String hash, String address, double amount, long date, long confirmations) {
        strHash = hash;
        strAddress = address;
        this.amount = amount;
        ts = date;
        this.confirmations = confirmations;
    }
/*
    public Tx(String hash, String note, String direction, double amount, long date, Map<Integer,String> tags) {
        strHash = hash;
        strNote = note;
        strDirection = direction;
        this.amount = amount;
        ts = date;
        this.tags = tags;
    }

    public Tx(String note, String direction, double amount, long date, Map<Integer,String> tags) {
        strNote = note;
        strDirection = direction;
        this.amount = amount;
        ts = date;
        this.tags = tags;
    }
*/

    public String getAddress() {
        return strAddress;
    }

    public void setAddress(String address) {
        strAddress = address;
    }

    public String getHash() {
        return strHash;
    }

    public void setHash(String hash) {
        strHash = hash;
    }

    public String getNote() {
        return strNote;
    }

    public void setNote(String note) {
        strNote = note;
    }

    public String getDirection() {
        return strDirection;
    }

    public void setDirection(String direction) {
        strDirection = direction;
    }

    public long getTS() {
        return ts;
    }

    public void setTS(long ts) {
        this.ts = ts;
    }

    public long getConfirmations() {
        return confirmations;
    }

    public void setConfirmations(long confirmations) {
        this.confirmations = confirmations;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public Map<Integer,String> getTags() {
        return this.tags;
    }

    public void setTags(Map<Integer,String> tags) {
        this.tags = tags;
    }

} public class HD_Account {

    private DeterministicKey aKey = null;
    private String strLabel = null;
    private int	mAID;
    private boolean isArchived = false;

    private HD_Chain mReceive = null;
    private HD_Chain mChange = null;

    private String strXPUB = null;

    private NetworkParameters mParams = null;

    private HD_Account() { ; }

    public HD_Account(NetworkParameters params, String xpub, String label, int child) throws AddressFormatException {

        mParams = params;
        strLabel = label;
        mAID = child;

        // assign master key to account key
        aKey = createMasterPubKeyFromXPub(xpub);

        strXPUB = xpub;

        mReceive = new HD_Chain(mParams, aKey, true, 1);
        mChange = new HD_Chain(mParams, aKey, false, 1);

    }

    private DeterministicKey createMasterPubKeyFromXPub(String xpubstr) throws AddressFormatException {

        byte[] xpubBytes = Base58.decodeChecked(xpubstr);

        ByteBuffer bb = ByteBuffer.wrap(xpubBytes);
        int magic = bb.getInt();
        if(magic != FormatsUtil.MAGIC_XPUB && magic != FormatsUtil.MAGIC_YPUB && magic != FormatsUtil.MAGIC_ZPUB &&
                magic != FormatsUtil.MAGIC_TPUB && magic != FormatsUtil.MAGIC_UPUB && magic != FormatsUtil.MAGIC_VPUB)   {
            throw new AddressFormatException("invalid xpub version");
        }

        byte[] chain = new byte[32];
        byte[] pub = new byte[33];
        // depth:
        bb.get();
        // parent fingerprint:
        bb.getInt();
        // child no.
        bb.getInt();
        bb.get(chain);
        bb.get(pub);

        return HDKeyDerivation.createMasterPubKeyFromBytes(pub, chain);
    }

    public String xpubstr() {

        return strXPUB;

    }

    public int getId() {
        return mAID;
    }

    public HD_Chain getReceive() {
        return mReceive;
    }

    public HD_Chain getChange() {
        return mChange;
    }

    public HD_Chain getChain(int idx) {
        return (idx == 0) ? mReceive : mChange;
    }

    public int size() {
        return mReceive.length() + mChange.length();
    }

    public JSONObject toJSON() {
        try {
            JSONObject obj = new JSONObject();

            obj.put("xpub", xpubstr());

            return obj;
        }
        catch(JSONException ex) {
            throw new RuntimeException(ex);
        }
    }
} public class HD_Wallet {

    private ArrayList<HD_Account> mAccounts = null;

    private NetworkParameters mParams = null;

    private HD_Wallet() { ; }

    /*
    create from account xpub key(s)
     */
    public HD_Wallet(NetworkParameters params, String[] xpub) throws AddressFormatException {

        mParams = params;
        DeterministicKey aKey = null;
        mAccounts = new ArrayList<HD_Account>();

        List<String> seen_xpub = new ArrayList<String>();
        for(int i = 0; i < xpub.length; i++) {
            if(seen_xpub.contains(xpub[i]))    {
                continue;
            }
            mAccounts.add(new HD_Account(mParams, xpub[i], "", i));
//            Log.i("HD_Wallet", xpub[i]);
            seen_xpub.add(xpub[i]);
        }

    }

    public List<HD_Account> getAccounts() {
        return mAccounts;
    }

    public HD_Account getAccount(int accountId) {
        return mAccounts.get(accountId);
    }

    public int size() {

        int sz = 0;
        for(HD_Account acct : mAccounts) {
            sz += acct.size();
        }

        return sz;
    }

    public JSONObject toJSON() {
        try {
            JSONObject obj = new JSONObject();

            JSONArray accts = new JSONArray();
            for(HD_Account acct : mAccounts) {
                accts.put(acct.toJSON());
            }
            obj.put("accounts", accts);

            obj.put("receiveIdx", mAccounts.get(0).getReceive().getAddrIdx());
            obj.put("changeIdx", mAccounts.get(0).getChange().getAddrIdx());

//            obj.put("prev_balance", APIFactory.getInstance().getXpubBalance());

            return obj;
        }
        catch(JSONException ex) {
            throw new RuntimeException(ex);
        }
    }

} public class HD_WalletFactory	{

    private static HD_WalletFactory instance = null;
    private static List<HD_Wallet> wallets = null;

    public static String strJSONFilePath = null;

    private static Context context = null;

    private HD_WalletFactory()	{ ; }

    public static HD_WalletFactory getInstance(Context ctx) {

    	context = ctx;

        if (instance == null) {
            wallets = new ArrayList<HD_Wallet>();
            instance = new HD_WalletFactory();
        }

        return instance;
    }

    public static HD_WalletFactory getInstance(Context ctx, String path) {

    	context = ctx;
        strJSONFilePath = path;

        if (instance == null) {
            wallets = new ArrayList<HD_Wallet>();
            instance = new HD_WalletFactory();
        }

        return instance;
    }

    public HD_Wallet restoreWallet(String data, String passphrase, int nbAccounts) throws AddressFormatException, DecoderException  {

        HD_Wallet hdw = null;

        if(passphrase == null) {
            passphrase = "";
        }

        NetworkParameters params = SamouraiSentinel.getInstance().getCurrentNetworkParams();

        if(data.startsWith("xpub") || data.startsWith("ypub") || data.startsWith("zpub") || data.startsWith("tpub") || data.startsWith("upub") || data.startsWith("vpub")) {
            String[] xpub = data.split(":");
//            Log.i("HD_WalletFactory", "xpubs:" + xpub.length);
            hdw = new HD_Wallet(params, xpub);
        }

        if(hdw == null) {
            PrefsUtil.getInstance(context).clear();
            return null;
        }

        wallets.clear();
        wallets.add(hdw);

        return hdw;
    }

    public HD_Wallet get() throws IOException, MnemonicException.MnemonicLengthException {

        if(wallets.size() < 1) {
            // if wallets list is empty, create 12-word wallet without passphrase and 2 accounts
//            wallets.add(0, newWallet(12, "", 2));
            /*
            wallets.clear();
            wallets.add(newWallet(12, "", 2));
            */
            return null;
        }

        return wallets.get(0);
    }

} public class PermissionsUtil {

    public static final int CAMERA_PERMISSION_CODE = 3;

    private static PermissionsUtil instance = null;
    private static Context context = null;

    private PermissionsUtil()   { ; }

    public static PermissionsUtil getInstance(Context ctx) {

        context = ctx;

        if(instance == null)    {
            instance = new PermissionsUtil();
        }

        return instance;
    }

    public boolean hasPermission(String permission) {
        return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
    }

    public void showRequestPermissionsInfoAlertDialog(final int code) {

        String title = "";
        String message = "";

        switch(code)    {
            case CAMERA_PERMISSION_CODE:
                title = context.getString(R.string.permission_alert_dialog_title_camera);
                message = context.getString(R.string.permission_dialog_message_camera);
                break;
            default:
                break;
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setCancelable(false);
        builder.setMessage(message);
        builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

                switch(code)    {
                    case CAMERA_PERMISSION_CODE:
                        requestPermissions(new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE);
                        break;
                    default:
                        break;
                }

                dialog.dismiss();

            }
        });
        builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });

        if(!((Activity)context).isFinishing())    {
            builder.show();
        }

    }

    private void requestPermissions(String[] permissions, int code) {

        for(int i = 0; i < permissions.length; i++)   {
            if (ActivityCompat.shouldShowRequestPermissionRationale((Activity)context, permissions[i])) {
                Log.d("PermissionsUtil", "shouldShowRequestPermissionRationale(), no permission requested");
            }
            else    {
                ActivityCompat.requestPermissions((Activity)context, permissions, code);
                break;
            }
        }

    }

} public class WebSocketService extends Service {

    private Context context = null;

    private Timer timer = new Timer();
    private static final long checkIfNotConnectedDelay = 15000L;
    private WebSocketHandler webSocketHandler = null;
    private final Handler handler = new Handler();
    private String[] addrs = null;

    public static List<String> addrSubs = null;

    @Override
    public void onCreate() {

        super.onCreate();

        //
        context = this.getApplicationContext();

        List<String> addrSubs = SamouraiSentinel.getInstance(WebSocketService.this).getAllAddrsSorted();
        addrs = addrSubs.toArray(new String[addrSubs.size()]);

        if(addrs.length == 0)    {
            return;
        }

        webSocketHandler = new WebSocketHandler(WebSocketService.this, addrs);
        connectToWebsocketIfNotConnected();

        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        connectToWebsocketIfNotConnected();
                    }
                });
            }
        }, 5000, checkIfNotConnectedDelay);

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void connectToWebsocketIfNotConnected()
    {
        try {
            if(!webSocketHandler.isConnected()) {
                webSocketHandler.start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void stop() {
        try {
            if(webSocketHandler != null)    {
                webSocketHandler.stop();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onDestroy()
    {
        stop();
        super.onDestroy();
    }

} public class PrivKeyReader {

    public final static String BASE58 = "base58";
    public final static String BASE64 = "base64";
    public final static String BIP38 = "bip38";
    public final static String HEX_UNCOMPRESSED = "hex_u";
    //    public final static String HEX_COMPRESSED = "hex_c";
    public final static String MINI = "mini";
    public final static String WIF_COMPRESSED = "wif_c";
    public final static String WIF_UNCOMPRESSED = "wif_u";

    private CharSequenceX strPrivKey = null;
    private CharSequenceX strPassword = null;

    private PrivKeyReader()	 {
        ;
    }

    public PrivKeyReader(CharSequenceX strPrivKey)	 {
        this.strPrivKey = strPrivKey;
    }

    public PrivKeyReader(CharSequenceX strPrivKey, CharSequenceX strPassword)	 {
        this.strPrivKey = strPrivKey;
        this.strPassword = strPassword;
    }

    public String getFormat() throws Exception {

        if(strPrivKey == null)    {
            return null;
        }

        // 52 characters, always starts with 'c'
        if(SamouraiSentinel.getInstance().isTestNet() && strPrivKey.toString().matches("^[c][1-9A-HJ-NP-Za-km-z]{51}$")) {
            return WIF_COMPRESSED;
        }
        // 51 characters base58, always starts with a '9'
        else if(SamouraiSentinel.getInstance().isTestNet() && strPrivKey.toString().matches("^9[1-9A-HJ-NP-Za-km-z]{50}$")) {
            return WIF_UNCOMPRESSED;
        }
        // 52 characters, always starts with 'K' or 'L'
        else if(!SamouraiSentinel.getInstance().isTestNet() && strPrivKey.toString().matches("^[LK][1-9A-HJ-NP-Za-km-z]{51}$")) {
            return WIF_COMPRESSED;
        }
        // 51 characters base58, always starts with a '5'
        else if(!SamouraiSentinel.getInstance().isTestNet() && strPrivKey.toString().matches("^5[1-9A-HJ-NP-Za-km-z]{50}$")) {
            return WIF_UNCOMPRESSED;
        }
        else if(strPrivKey.toString().matches("^[1-9A-HJ-NP-Za-km-z]{44}$") || strPrivKey.toString().matches("^[1-9A-HJ-NP-Za-km-z]{43}$")) {
            return BASE58;
        }
        // assume uncompressed for hex (secret exponent)
        else if(strPrivKey.toString().matches("^[A-Fa-f0-9]{64}$")) {
            return HEX_UNCOMPRESSED;
        }
        else if(strPrivKey.toString().matches("^[A-Za-z0-9/=+]{44}$")) {
            return BASE64;
        }
        else if(strPrivKey.toString().matches("^6P[1-9A-HJ-NP-Za-km-z]{56}$")) {
            return BIP38;
        }
        else if(
//                strPrivKey.toString().matches("^S[1-9A-HJ-NP-Za-km-z]{21}$") ||
                strPrivKey.toString().matches("^S[1-9A-HJ-NP-Za-km-z]{22}$") ||
//                strPrivKey.toString().matches("^S[1-9A-HJ-NP-Za-km-z]{25}$") ||
//                strPrivKey.toString().matches("^S[1-9A-HJ-NP-Za-km-z]{29}$") ||
                        strPrivKey.toString().matches("^S[1-9A-HJ-NP-Za-km-z]{30}$")) {

            byte[] testBytes = null;
            String data = strPrivKey.toString() + "?";
            try {
                testBytes = Sha256Hash.hash(data.getBytes("UTF-8"));
            }
            catch (Exception e) {
                e.printStackTrace();
                return null;
            }

            if(testBytes[0] == 0x00) {
                return MINI;
            }
            else {
                return null;
            }
        }
        else {
            return null;
        }
    }

    public ECKey getKey() throws Exception {

        String format = getFormat();

        if(format == null) {
            return null;
        }

        if(format.equals(WIF_COMPRESSED) || format.equals(WIF_UNCOMPRESSED)) {
            DumpedPrivateKey pk = DumpedPrivateKey.fromBase58(SamouraiSentinel.getInstance().getCurrentNetworkParams(), strPrivKey.toString());
            return pk.getKey();
        }
        else if(format.equals(BASE58)) {
            return decodeBase58PK(strPrivKey.toString());
        }
        else if(format.equals(BASE64)) {
            return decodeBase64PK(strPrivKey.toString());
        }
        else if(format.equals(HEX_UNCOMPRESSED)) {
            return decodeHexPK(strPrivKey.toString(), false);
        }
        else if(format.equals(BIP38)) {
            return parseBIP38(strPrivKey.toString(), strPassword);
        }
        else if(format.equals(MINI)) {

            try {
                byte[] hash = Sha256Hash.hash(strPrivKey.toString().getBytes("UTF-8"));
                // assume uncompressed
                return decodeHexPK(Hex.toHexString(hash), false);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }

        }
        else {
            return null;
        }
    }

    public void setPassword(CharSequenceX strPassword) {
        this.strPassword = strPassword;
    }

    private ECKey decodeBase58PK(String base58Priv) throws Exception {
        byte[] privBytes = Base58.decode(base58Priv);
        // Prepend a zero byte to make the biginteger unsigned
        byte[] prependZeroByte = ArrayUtils.addAll(new byte[1], privBytes);
        ECKey ecKey = new ECKey(new BigInteger(prependZeroByte), null, base58Priv.charAt(0) == '5' ? false : true);
        return ecKey;
    }

    private ECKey decodeBase64PK(String base64Priv) throws Exception {
        byte[] privBytes = Base64.decode(base64Priv, Base64.NO_PADDING);
        byte[] prependZeroByte = ArrayUtils.addAll(new byte[1], privBytes);
        // assume uncompressed
        ECKey ecKey = new ECKey(new BigInteger(prependZeroByte), null, false);
        return ecKey;
    }

    private ECKey decodeHexPK(String hex, boolean compressed) throws Exception {
        byte[] privBytes = Hex.decode(hex);
        byte[] prependZeroByte = ArrayUtils.addAll(new byte[1], privBytes);
        ECKey ecKey = new ECKey(new BigInteger(prependZeroByte), null, compressed);
        return ecKey;
    }

    private ECKey parseBIP38(String encryptedKey, CharSequenceX password)  {

        if(password == null)    {
            return null;
        }

        try {
            BIP38PrivateKey bip38 = new BIP38PrivateKey(SamouraiSentinel.getInstance().getCurrentNetworkParams(), encryptedKey);
            final ECKey ecKey = bip38.decrypt(password.toString());
            if(ecKey != null && ecKey.hasPrivKey()) {
                return ecKey;
            }
        }
        catch(Exception e) {
            ;
        }

        return null;
    }

} public class SweepUtil  {

    public static int TYPE_P2PKH = 0;
    public static int TYPE_P2SH_P2WPKH = 1;
    public static int TYPE_P2WPKH = 2;

    private static Context context = null;
    private static SweepUtil instance = null;

    private static UTXO utxoP2PKH = null;
    private static UTXO utxoP2SH_P2WPKH = null;
    private static UTXO utxoP2WPKH = null;

    private static String addressP2PKH = null;
    private static String addressP2SH_P2WPKH = null;
    private static String addressP2WPKH = null;

    private SweepUtil() { ; }

    public static SweepUtil getInstance(Context ctx) {

        context = ctx;

        if(instance == null)    {
            instance = new SweepUtil();
        }

        return instance;
    }

    public void sweep(final PrivKeyReader privKeyReader, final String strReceiveAddress, final int type)  {

        new Thread(new Runnable() {
            @Override
            public void run() {

                Looper.prepare();

                try {

                    if(privKeyReader == null || privKeyReader.getKey() == null || !privKeyReader.getKey().hasPrivKey())    {
                        Toast.makeText(context, R.string.cannot_recognize_privkey, Toast.LENGTH_SHORT).show();
                        return;
                    }

                    String address = null;
                    UTXO utxo = null;

                    if(type == TYPE_P2SH_P2WPKH)    {
                        utxo = utxoP2SH_P2WPKH;
                        address = addressP2SH_P2WPKH;
                    }
                    else if(type == TYPE_P2WPKH)    {
                        utxo = utxoP2WPKH;
                        address = addressP2WPKH;
                    }
                    else    {
                        addressP2PKH = privKeyReader.getKey().toAddress(SamouraiSentinel.getInstance().getCurrentNetworkParams()).toString();
                        Log.d("SweepUtil", "address derived P2PKH:" + addressP2PKH);
                        addressP2SH_P2WPKH = new P2SH_P2WPKH(privKeyReader.getKey(), SamouraiSentinel.getInstance().getCurrentNetworkParams()).getAddressAsString();
                        Log.d("SweepUtil", "address derived P2SH_P2WPKH:" + addressP2SH_P2WPKH);
                        addressP2WPKH = new SegwitAddress(privKeyReader.getKey(), SamouraiSentinel.getInstance().getCurrentNetworkParams()).getBech32AsString();
                        Log.d("SweepUtil", "address derived P2WPKH:" + addressP2WPKH);

                        utxoP2PKH = APIFactory.getInstance(context).getUnspentOutputsForSweep(addressP2PKH);
                        utxoP2SH_P2WPKH = APIFactory.getInstance(context).getUnspentOutputsForSweep(addressP2SH_P2WPKH);
                        utxoP2WPKH = APIFactory.getInstance(context).getUnspentOutputsForSweep(addressP2WPKH);

                        utxo = utxoP2PKH;
                        address = addressP2PKH;
                    }

                    if(utxo != null && utxo.getOutpoints().size() > 0)    {

                        long total_value = 0L;
                        final List<MyTransactionOutPoint> outpoints = utxo.getOutpoints();
                        for(MyTransactionOutPoint outpoint : outpoints)   {
                            total_value += outpoint.getValue().longValue();
                        }

                        FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getNormalFee());
                        if(FeeUtil.getInstance().getSuggestedFee().getDefaultPerKB().longValue() <= 1000L)    {
                            SuggestedFee suggestedFee = new SuggestedFee();
                            suggestedFee.setDefaultPerKB(BigInteger.valueOf(1100L));
                            Log.d("SendActivity", "adjusted fee:" + suggestedFee.getDefaultPerKB().longValue());
                            FeeUtil.getInstance().setSuggestedFee(suggestedFee);
                        }
                        final BigInteger fee;
                        if(type == TYPE_P2SH_P2WPKH)    {
                            fee = FeeUtil.getInstance().estimatedFeeSegwit(0, outpoints.size(), 1);
                        }
                        else if(type == TYPE_P2WPKH)    {
                            fee = FeeUtil.getInstance().estimatedFeeSegwit(0, 0, outpoints.size(), 1);
                        }
                        else    {
                            fee = FeeUtil.getInstance().estimatedFee(outpoints.size(), 1);
                        }

                        final long amount = total_value - fee.longValue();
                        Log.d("SweepUtil", "Total value:" + total_value);
                        Log.d("SweepUtil", "Amount:" + amount);
                        Log.d("SweepUtil", "Fee:" + fee.toString());
                        Log.d("SweepUtil", "Receive address:" + strReceiveAddress);

                        String message = "Sweep " + Coin.valueOf(amount).toPlainString() + " from " + address + " (fee:" + Coin.valueOf(fee.longValue()).toPlainString() + ")?";

                        new AlertDialog.Builder(context)
                        .setTitle(R.string.app_name)
                        .setMessage(message)
                        .setCancelable(false)
                        .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                            public void onClick(final DialogInterface dialog, int whichButton) {

                                Log.d("SweepUtil", "start sweep");

                                final HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
                                receivers.put(strReceiveAddress, BigInteger.valueOf(amount));
                                Transaction tx = SendFactory.getInstance(context).makeTransaction(0, outpoints, receivers);

                                Log.d("SweepUtil", "tx is " + ((tx == null) ? "null" : "not null"));

                                tx = SendFactory.getInstance(context).signTransactionForSweep(tx, privKeyReader);
                                final String hexTx = new String(Hex.encode(tx.bitcoinSerialize()));
                                Log.d("SweepUtil", hexTx);
//
                                String response = null;
                                try {
                                    response = PushTx.getInstance(context).samourai(hexTx);

                                    if(response != null)    {
                                        JSONObject jsonObject = new org.json.JSONObject(response);
                                        if(jsonObject.has("status"))    {
                                            if(jsonObject.getString("status").equals("ok"))    {
                                                Toast.makeText(context, R.string.tx_ok, Toast.LENGTH_SHORT).show();
                                            }
                                        }
                                    }
                                    else    {
                                        Toast.makeText(context, R.string.pushtx_returns_null, Toast.LENGTH_SHORT).show();
                                    }
                                }
                                catch(JSONException je) {
                                    Toast.makeText(context, "pushTx:" + je.getMessage(), Toast.LENGTH_SHORT).show();
                                }

                                dialog.dismiss();

                            }
                        })
                        .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                            public void onClick(final DialogInterface dialog, int whichButton) {
                                dialog.dismiss();
                            }
                        }).show();

                    }
                    else if(type == TYPE_P2SH_P2WPKH)    {
                        sweep(privKeyReader, strReceiveAddress, TYPE_P2WPKH);
                    }
                    else if(type == TYPE_P2PKH)    {
                        sweep(privKeyReader, strReceiveAddress, TYPE_P2SH_P2WPKH);
                    }
                    else if(type == TYPE_P2WPKH)    {
                        ;
                    }

                }
                catch(Exception e) {
                    Log.d("SweepUtil", e.getMessage());
                    Toast.makeText(context, context.getText(R.string.cannot_sweep_privkey) + ", " + e.getMessage() , Toast.LENGTH_SHORT).show();
                }

                Looper.loop();

            }
        }).start();

    }

} public class AddressFactory {

    public static final int LOOKAHEAD_GAP = 20;

    public static final int RECEIVE_CHAIN = 0;
    public static final int CHANGE_CHAIN = 1;

    private static Context context = null;
    private static AddressFactory instance = null;

    private static HashMap<Integer,Integer> highestTxReceiveIdx = null;
    private static HashMap<Integer,Integer> highestTxChangeIdx = null;

    private static HashMap<String,Integer> xpub2account = null;
    private static HashMap<Integer,String> account2xpub = null;

    private AddressFactory() { ; }

    public static AddressFactory getInstance(Context ctx) {

        context = ctx;

        if(instance == null) {
            instance = new AddressFactory();

            highestTxReceiveIdx = new HashMap<Integer,Integer>();
            highestTxChangeIdx = new HashMap<Integer,Integer>();
            xpub2account = new HashMap<String,Integer>();
            account2xpub = new HashMap<Integer,String>();
        }

        return instance;
    }

    public static AddressFactory getInstance() {

        if(instance == null) {
            instance = new AddressFactory();

            highestTxReceiveIdx = new HashMap<Integer,Integer>();
            highestTxChangeIdx = new HashMap<Integer,Integer>();
            xpub2account = new HashMap<String,Integer>();
            account2xpub = new HashMap<Integer,String>();
        }

        return instance;
    }

    public String get(int chain, int accountIdx)	{

        int idx = 0;
        HD_Address addr = null;

        try	{
            idx = HD_WalletFactory.getInstance(context).get().getAccount(accountIdx).getChain(chain).getAddrIdx();
            addr = HD_WalletFactory.getInstance(context).get().getAccount(accountIdx).getChain(chain).getAddressAt(idx);
            if(chain == RECEIVE_CHAIN && canIncReceiveAddress(accountIdx))	{
                HD_WalletFactory.getInstance(context).get().getAccount(accountIdx).getChain(chain).incAddrIdx();
            }
        }
        catch(IOException ioe)	{
            ioe.printStackTrace();
            Toast.makeText(context, "HD wallet error", Toast.LENGTH_SHORT).show();
        }
        catch(MnemonicException.MnemonicLengthException mle)	{
            mle.printStackTrace();
            Toast.makeText(context, "HD wallet error", Toast.LENGTH_SHORT).show();
        }

        return addr.getAddressString();

    }

    public ECKey getECKey(int chain, int accountIdx)	{

        int idx = 0;
        HD_Address addr = null;

        try	{
            idx = HD_WalletFactory.getInstance(context).get().getAccount(accountIdx).getChain(chain).getAddrIdx();
            addr = HD_WalletFactory.getInstance(context).get().getAccount(accountIdx).getChain(chain).getAddressAt(idx);
            if(chain == RECEIVE_CHAIN && canIncReceiveAddress(accountIdx))	{
                HD_WalletFactory.getInstance(context).get().getAccount(accountIdx).getChain(chain).incAddrIdx();
            }
        }
        catch(IOException ioe)	{
            ioe.printStackTrace();
            Toast.makeText(context, "HD wallet error", Toast.LENGTH_SHORT).show();
        }
        catch(MnemonicException.MnemonicLengthException mle)	{
            mle.printStackTrace();
            Toast.makeText(context, "HD wallet error", Toast.LENGTH_SHORT).show();
        }

        return addr.getECKey();

    }

    public HD_Address get(int accountIdx, int chain, int idx)	{

        HD_Address addr = null;

        try	{
            addr = HD_WalletFactory.getInstance(context).get().getAccount(accountIdx).getChain(chain).getAddressAt(idx);
        }
        catch(IOException ioe)	{
            ioe.printStackTrace();
            Toast.makeText(context, "HD wallet error", Toast.LENGTH_SHORT).show();
        }
        catch(MnemonicException.MnemonicLengthException mle)	{
            mle.printStackTrace();
            Toast.makeText(context, "HD wallet error", Toast.LENGTH_SHORT).show();
        }

        return addr;
    }

    public int getHighestTxReceiveIdx(int account)  {
        if(highestTxReceiveIdx.get(account) != null)  {
            return highestTxReceiveIdx.get(account);
        }
        else  {
            return -1;
        }
    }

    public void setHighestTxReceiveIdx(int account, int idx) {
        //       Log.i("AddressFactory", "setting highestTxReceiveIdx to " + idx);
        highestTxReceiveIdx.put(account, idx);
    }

    public int getHighestTxChangeIdx(int account) {
        if(highestTxChangeIdx.get(account) != null)  {
            return highestTxChangeIdx.get(account);
        }
        else  {
            return -1;
        }
    }

    public void setHighestTxChangeIdx(int account, int idx) {
        //       Log.i("AddressFactory", "setting highestTxChangeIdx to " + idx);
        highestTxChangeIdx.put(account, idx);
    }

    public boolean canIncReceiveAddress(int account, int idx) {
        if(highestTxReceiveIdx.get(account) != null) {
            return ((idx - highestTxReceiveIdx.get(account)) < (LOOKAHEAD_GAP - 1));
        }
        else {
            return false;
        }
    }

    public boolean canIncReceiveAddress(int account) {
        try {
            return canIncReceiveAddress(account, HD_WalletFactory.getInstance(context).get().getAccount(account).getReceive().getAddrIdx());
        } catch (Exception e) {
            return false;
        }
    }

    public HashMap<String,Integer> xpub2account()   {
        return xpub2account;
    }

    public HashMap<Integer,String> account2xpub()   {
        return account2xpub;
    }

} public class AppUtil {

    private ProgressDialog progress = null;

    public static final int MIN_BACKUP_PW_LENGTH = 6;
    public static final int MAX_BACKUP_PW_LENGTH = 255;

    private boolean isInForeground = false;
	
	private static AppUtil instance = null;
	private static Context context = null;

    private static String strReceiveQRFilename = null;
	
	private AppUtil() { ; }

	public static AppUtil getInstance(Context ctx) {
		
		context = ctx;
		
		if(instance == null) {
            strReceiveQRFilename = context.getExternalCacheDir() + File.separator + "qr.png";
			instance = new AppUtil();
		}
		
		return instance;
	}

    public void restartApp() {
        Intent intent = new Intent(context, MainActivity2.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }

    public void restartApp(boolean verified) {
        Intent intent = new Intent(context, MainActivity2.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra("verified", verified);
        context.startActivity(intent);
    }

    public void setIsInForeground(boolean foreground) {
        isInForeground = foreground;
    }

    public boolean isServiceRunning(Class<?> serviceClass) {

        ActivityManager manager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                Log.d("AppUtil", "service class name:" + serviceClass.getName() + " is running");
                return true;
            }
        }

        Log.d("AppUtil", "service class name:" + serviceClass.getName() + " is not running");
        return false;
    }

    public String getReceiveQRFilename(){
        return strReceiveQRFilename;
    }

    public void deleteQR(){
        String strFileName = strReceiveQRFilename;
        File file = new File(strFileName);
        if(file.exists()) {
            file.delete();
        }
    }

    public void doBackup() {

        final EditText password = new EditText(context);
        password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
        password.setHint(R.string.password);

        new AlertDialog.Builder(context)
                .setTitle(R.string.app_name)
                .setMessage(R.string.options_export2)
                .setView(password)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {

                        final String pw = password.getText().toString();
                        if (pw != null && pw.length() >= AppUtil.MIN_BACKUP_PW_LENGTH && pw.length() <= AppUtil.MAX_BACKUP_PW_LENGTH) {

                            final EditText password2 = new EditText(context);
                            password2.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
                            password2.setHint(R.string.confirm_password);

                            new AlertDialog.Builder(context)
                                    .setTitle(R.string.app_name)
                                    .setMessage(R.string.options_export2)
                                    .setView(password2)
                                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog, int whichButton) {

                                            final String pw2 = password2.getText().toString();

                                            if (pw2 != null && pw2.equals(pw)) {

                                                final String[] export_methods = new String[2];
                                                export_methods[0] = context.getString(R.string.export_to_clipboard);
                                                export_methods[1] = context.getString(R.string.export_to_email);

                                                new AlertDialog.Builder(context)
                                                        .setTitle(R.string.options_export)
                                                        .setSingleChoiceItems(export_methods, 0, new DialogInterface.OnClickListener() {
                                                                    public void onClick(DialogInterface dialog, int which) {

                                                                        String encrypted = null;
                                                                        try {
                                                                            encrypted = AESUtil.encrypt(SamouraiSentinel.getInstance(context).toJSON().toString(), new CharSequenceX(pw), AESUtil.DefaultPBKDF2Iterations);
                                                                        } catch (Exception e) {
                                                                            Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show();
                                                                        } finally {
                                                                            if (encrypted == null) {
                                                                                Toast.makeText(context, R.string.encryption_error, Toast.LENGTH_SHORT).show();
                                                                                return;
                                                                            }
                                                                            else    {

                                                                                try {
                                                                                    JSONObject jsonObj = new JSONObject();
                                                                                    jsonObj.put("version", 1);
                                                                                    jsonObj.put("payload", encrypted);
                                                                                    encrypted = jsonObj.toString();
                                                                                }
                                                                                catch(JSONException je) {
                                                                                    ;
                                                                                }

                                                                            }

                                                                        }

                                                                        if (which == 0) {
                                                                            android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(android.content.Context.CLIPBOARD_SERVICE);
                                                                            android.content.ClipData clip = null;
                                                                            clip = android.content.ClipData.newPlainText("Wallet backup", encrypted);
                                                                            clipboard.setPrimaryClip(clip);
                                                                            Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
                                                                        } else {
                                                                            Intent email = new Intent(Intent.ACTION_SEND);
                                                                            email.putExtra(Intent.EXTRA_SUBJECT, "Sentinel backup");
                                                                            email.putExtra(Intent.EXTRA_TEXT, encrypted);
                                                                            email.setType("message/rfc822");
                                                                            context.startActivity(Intent.createChooser(email, context.getText(R.string.choose_email_client)));
                                                                        }

                                                                        dialog.dismiss();
                                                                    }
                                                                }
                                                        ).show();

                                            } else {
                                                Toast.makeText(context, R.string.error_password, Toast.LENGTH_SHORT).show();
                                            }

                                        }
                                    })
                                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog, int whichButton) {
                                            ;
                                        }
                                    }).show();

                        } else {
                            Toast.makeText(context, R.string.invalid_password, Toast.LENGTH_SHORT).show();
                        }

                    }
                })
                .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        ;
                    }
                }).show();

    }

    public void doRestore() {

        final EditText password = new EditText(context);
        password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
        password.setHint(R.string.password);

        AlertDialog.Builder dlg = new AlertDialog.Builder(context)
                .setTitle(R.string.app_name)
                .setView(password)
                .setMessage(R.string.restore_wallet_from_backup)
                .setCancelable(false)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {

                        final String pw = password.getText().toString();
                        if (pw == null || pw.length() < 1) {
                            Toast.makeText(context, R.string.invalid_password, Toast.LENGTH_SHORT).show();
                            AppUtil.getInstance(context).restartApp();
                        }

                        final EditText edBackup = new EditText(context);
                        edBackup.setSingleLine(false);
                        edBackup.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
                        edBackup.setLines(5);
                        edBackup.setHint(R.string.encrypted_backup);
                        edBackup.setGravity(Gravity.START);

                        AlertDialog.Builder dlg = new AlertDialog.Builder(context)
                                .setTitle(R.string.app_name)
                                .setView(edBackup)
                                .setMessage(R.string.restore_wallet_from_backup)
                                .setCancelable(false)
                                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog, int whichButton) {

                                        String encrypted = edBackup.getText().toString();
                                        if (encrypted == null || encrypted.length() < 1) {
                                            Toast.makeText(context, R.string.decryption_error, Toast.LENGTH_SHORT).show();
                                            AppUtil.getInstance(context).restartApp();
                                        }

                                        try {
                                            JSONObject jsonObject = new JSONObject(encrypted);
                                            if(jsonObject != null && jsonObject.has("payload"))    {
                                                encrypted = jsonObject.getString("payload");
                                            }
                                        }
                                        catch(JSONException je) {
                                            ;
                                        }

                                        String decrypted = null;
                                        try {
                                            decrypted = AESUtil.decrypt(encrypted, new CharSequenceX(pw), AESUtil.DefaultPBKDF2Iterations);
                                        } catch (Exception e) {
                                            Toast.makeText(context, R.string.decryption_error, Toast.LENGTH_SHORT).show();
                                        } finally {
                                            if (decrypted == null || decrypted.length() < 1) {
                                                Toast.makeText(context, R.string.decryption_error, Toast.LENGTH_SHORT).show();
                                                AppUtil.getInstance(context).restartApp();
                                            }
                                        }

                                        final String decryptedPayload = decrypted;
                                        if (progress != null && progress.isShowing()) {
                                            progress.dismiss();
                                            progress = null;
                                        }

                                        progress = new ProgressDialog(context);
                                        progress.setCancelable(false);
                                        progress.setTitle(R.string.app_name);
                                        progress.setMessage(context.getString(R.string.please_wait));
                                        progress.show();

                                        new Thread(new Runnable() {
                                            @Override
                                            public void run() {
                                                Looper.prepare();

                                                try {

                                                    JSONObject json = new JSONObject(decryptedPayload);
                                                    if(json != null && (json.has("xpubs") || json.has("legacy")))    {
                                                        SamouraiSentinel.getInstance(context).parseJSON(json);

                                                        try {
                                                            SamouraiSentinel.getInstance(context).serialize(SamouraiSentinel.getInstance(context).toJSON(), null);
                                                        } catch (IOException ioe) {
                                                            ;
                                                        } catch (JSONException je) {
                                                            ;
                                                        }

                                                        AppUtil.getInstance(context).restartApp();
                                                    }

                                                }
                                                catch(JSONException je) {
                                                    je.printStackTrace();
                                                    Toast.makeText(context, R.string.decryption_error, Toast.LENGTH_SHORT).show();
                                                }
                                                finally {
                                                    if (progress != null && progress.isShowing()) {
                                                        progress.dismiss();
                                                        progress = null;
                                                    }
                                                    AppUtil.getInstance(context).restartApp();
                                                }

                                                Looper.loop();

                                            }
                                        }).start();

                                    }
                                }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                                    public void onClick(DialogInterface dialog, int whichButton) {

                                        AppUtil.getInstance(context).restartApp();

                                    }
                                });

                        dlg.show();

                    }
                }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {

                        AppUtil.getInstance(context).restartApp();

                    }
                });

        dlg.show();

    }

    public void checkTimeOut()   {
        if(TimeOutUtil.getInstance().isTimedOut())    {
            AppUtil.getInstance(context).restartApp();
        }
        else    {
            TimeOutUtil.getInstance().updatePin();
        }
    }

    public boolean isSideLoaded() {
        List<String> validInstallers = new ArrayList<>(Arrays.asList("com.android.vending", "com.google.android.feedback"));
        final String installer = context.getPackageManager().getInstallerPackageName(context.getPackageName());
        return installer == null || !validInstallers.contains(installer);
    }

} public class BlockExplorerUtil {

    private static CharSequence[] blockExplorers = { "Smartbit", "Blockchain Reader (Yogh)", "OXT" };
    private static CharSequence[] blockExplorerUrls = { "https://www.smartbit.com.au/tx/", "http://srv1.yogh.io/#tx:id:", "https://m.oxt.me/transaction/" };

    private static BlockExplorerUtil instance = null;

    private BlockExplorerUtil() { ; }

    public static BlockExplorerUtil getInstance() {

        if(instance == null) {
            instance = new BlockExplorerUtil();
        }

        return instance;
    }

    public CharSequence[] getBlockExplorers() {
        return blockExplorers;
    }

    public CharSequence[] getBlockExplorerUrls() {
        return blockExplorerUrls;
    }

} public class CharSequenceX implements CharSequence {

    private char[] chars;
    private int rounds = 100;

    private CharSequenceX(CharSequence charSequence, int start, int end) {
        zap();
        int len = end - start;
        chars = new char[len];
        for(int i = start; i < end; i++) {
            chars[i - start] = charSequence.charAt(i);
        }
    }

    public CharSequenceX(int len) {
        chars = new char[len];
    }

    public CharSequenceX(CharSequence charSequence) {
        this(charSequence, 0, charSequence.length());
    }

    public CharSequenceX(char[] chars) {
        zap();
        this.chars = chars;
    }

    public void zap() {
        if(chars != null) {
            for(int i = 0; i < rounds; i++) {
                fill('0');
                rfill();
                fill('0');
            }
        }
    }

    public void setRounds(int rounds) {
        if(rounds < 100) {
            this.rounds = 100;
        }
        else {
            this.rounds = rounds;
        }
    }

    @Override
    public char charAt(int index) {
        if(chars != null) {
            return chars[index];
        }
        else {
            return 0;
        }
    }

    @Override
    public int length() {
        if(chars != null) {
            return chars.length;
        }
        else {
            return 0;
        }
    }

    @Override
    public String toString() {
        return new String(chars);
    }

    @Override
    public boolean equals(Object o) {
        if(o instanceof CharSequenceX) {
            return Arrays.equals(chars, ((CharSequenceX) o).chars);
        }
        else {
            return false;
        }
    }

    @Override
    public CharSequence subSequence(int start, int end) {
        CharSequenceX s = new CharSequenceX(this, start, end);
        return s;
    }

    protected void finalize() {
        zap();
    }

    private void fill(char c) {
        for(int i = 0; i < chars.length; i++) {
            chars[i] = c;
        }
    }

    private void rfill() {
        SecureRandom r = new SecureRandom();
        byte[] b = new byte[chars.length];
        r.nextBytes(b);
        for(int i = 0; i < chars.length; i++) {
            chars[i] = (char)b[i];
        }
    }

} public class DateUtil {

    private static DateUtil instance = null;
    private static Date now = null;
    private static Context context = null;

    private DateUtil() { ; }

    public static DateUtil getInstance(Context ctx) {

        now = new Date();
        context = ctx;

        if(instance == null) {
            instance = new DateUtil();
        }

        return instance;
    }

    public String formatted(long ts) {
        String ret = null;

        Date localTime = new Date(ts);
        long date = localTime.getTime();

        long hours24 = 60L * 60L * 24;
        long now = System.currentTimeMillis() / 1000L;

        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date(now * 1000L));
        int nowYear = cal.get(Calendar.YEAR);
        int nowDay = cal.get(Calendar.DAY_OF_MONTH);

        cal.setTime(new Date(date * 1000L));
        int thenYear = cal.get(Calendar.YEAR);
        int thenDay = cal.get(Calendar.DAY_OF_MONTH);

        // within 24h
        if(now - date < hours24) {
            if(thenDay < nowDay) {
                SimpleDateFormat sd = new SimpleDateFormat("HH:mm");
                ret = group(date) + " " + sd.format(date * 1000L);
            }
            else {
                SimpleDateFormat sd = new SimpleDateFormat("HH:mm");
                ret = group(date) + " " + sd.format(date * 1000L);
            }
        }
        // within 48h
        else if(now - date < (hours24 * 2)) {
            SimpleDateFormat sd = new SimpleDateFormat("E dd MMM HH:mm");
            ret = sd.format(date * 1000L);
        }
        else {
            if(thenYear < nowYear) {
                SimpleDateFormat sd = new SimpleDateFormat("dd MMM yyyy");
                ret = sd.format(date * 1000L);
            }
            else {
                SimpleDateFormat sd = new SimpleDateFormat("E dd MMM HH:mm");
                ret = sd.format(date * 1000L);
            }
        }

        return ret;
    }

    public String group(long date) {
        String ret = null;

        long hours24 = 60L * 60L * 24;
        long now = System.currentTimeMillis() / 1000L;

        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date(now * 1000L));
        int nowYear = cal.get(Calendar.YEAR);
        int nowDay = cal.get(Calendar.DAY_OF_MONTH);

        cal.setTime(new Date(date * 1000L));
        int thenYear = cal.get(Calendar.YEAR);
        int thenDay = cal.get(Calendar.DAY_OF_MONTH);

        // within 24h
        if(now - date < hours24) {
            if(thenDay < nowDay) {
                ret = context.getString(R.string.timeline_yesterday);
            }
            else {
                ret = context.getString(R.string.timeline_today);
            }
        }
        else {
            ret = context.getString(R.string.timeline_older);
        }

        return ret;
    }

} public class ExchangeRateFactory	{

    private static Context context = null;

    private static String strDataLBC = null;
    private static String strDataBFX = null;

    private static HashMap<String,Double> fxRatesLBC = null;
    private static HashMap<String,Double> fxRatesBFX = null;

    private static ExchangeRateFactory instance = null;

    private static String[] currencies = {
            "CNY", "EUR", "GBP", "RUB", "USD"
    };

    private static String[] currencyLabels = {
            "United States Dollar - USD",
            "Euro - EUR",
            "British Pound Sterling - GBP",
            "Chinese Yuan - CNY",
            "Russian Rouble - RUB"
    };

    private static String[] exchangeLabels = {
            "LocalBitcoins.com",
            "Bitfinex",
    };

    private ExchangeRateFactory()	 { ; }

    public static ExchangeRateFactory getInstance(Context ctx)	 {

        context = ctx;

        if(instance == null)	 {
            fxRatesLBC = new HashMap<String,Double>();
            fxRatesBFX = new HashMap<String,Double>();

            instance = new ExchangeRateFactory();
        }

        return instance;
    }

    public double getAvgPrice(String currency)	 {
        int fxSel = PrefsUtil.getInstance(context).getValue(PrefsUtil.CURRENT_EXCHANGE_SEL, 0);
        HashMap<String,Double> fxRates = null;

        if(fxSel == 1)	 {
            fxRates = fxRatesBFX;
        }
        else	 {
            fxRates = fxRatesLBC;
        }

        if(fxRates.get(currency) != null && fxRates.get(currency) > 0.0)	 {
            PrefsUtil.getInstance(context).setValue("CANNED_" + currency, Double.toString(fxRates.get(currency)));
            return fxRates.get(currency);
        }
        else	 {
            return Double.parseDouble(PrefsUtil.getInstance(context).getValue("CANNED_" + currency, "0.0"));
        }
    }

    public String[] getCurrencies()	 {
        return currencies;
    }

    public String[] getCurrencyLabels()	 {
        return currencyLabels;
    }

    public String[] getExchangeLabels()	 {
        return exchangeLabels;
    }

    public void setDataLBC(String data)	 {
        strDataLBC = data;
    }

    public void setDataBFX(String data)	 {
        strDataBFX = data;
    }

    public void parseLBC()	 {
        for(int i = 0; i < currencies.length; i++)	 {
            getLBC(currencies[i]);
        }
    }

    public void parseBFX()	 {
        for(int i = 0; i < currencies.length; i++)	 {
            if(currencies[i].equals("USD"))	 {
                getBFX("USD");
            }
            else	 {
                continue;
            }
        }
    }

    private void getLBC(String currency)	 {
        try {
            JSONObject jsonObject = new JSONObject(strDataLBC);
            if(jsonObject != null)	{
                JSONObject jsonCurr = jsonObject.getJSONObject(currency);
                if(jsonCurr != null)	{
                    double avg_price = 0.0;
                    if(jsonCurr.has("avg_12h"))	{
                        avg_price = jsonCurr.getDouble("avg_12h");
                    }
                    else if(jsonCurr.has("avg_24h"))	{
                        avg_price = jsonCurr.getDouble("avg_24h");
                    }
                    fxRatesLBC.put(currency, Double.valueOf(avg_price));
//                    Log.i("ExchangeRateFactory", "LBC:" + currency + " " + Double.valueOf(avg_price));
                }
            }
        } catch (JSONException je) {
            fxRatesLBC.put(currency, Double.valueOf(-1.0));
//            fxSymbols.put(currency, null);
        }
    }

    private void getBFX(String currency)	 {
        try {
            JSONObject jsonObject = new JSONObject(strDataBFX);
            if(jsonObject != null && jsonObject.has("last_price"))	{
                String strLastPrice = jsonObject.getString("last_price");
                double avg_price = Double.parseDouble(strLastPrice);
                fxRatesBFX.put(currency, Double.valueOf(avg_price));
//                    Log.i("ExchangeRateFactory", "BFX:" + currency + " " + Double.valueOf(avg_price));
            }
        }
        catch (JSONException je) {
            fxRatesBFX.put(currency, Double.valueOf(-1.0));
//            fxSymbols.put(currency, null);
        }
        catch (NumberFormatException nfe) {
            fxRatesBFX.put(currency, Double.valueOf(-1.0));
//            fxSymbols.put(currency, null);
        }
    }

} public class MonetaryUtil {

    private static MonetaryUtil instance = null;
	private static NumberFormat btcFormat = null;
	private static NumberFormat fiatFormat = null;

	private MonetaryUtil() { ; }

	public static MonetaryUtil getInstance() {
		
		if(instance == null) {
        	fiatFormat = NumberFormat.getInstance(Locale.US);
        	fiatFormat.setMaximumFractionDigits(2);
        	fiatFormat.setMinimumFractionDigits(2);

        	btcFormat = NumberFormat.getInstance(Locale.US);
        	btcFormat.setMaximumFractionDigits(8);
        	btcFormat.setMinimumFractionDigits(1);

			instance = new MonetaryUtil();
		}
		
		return instance;
	}

	public NumberFormat getBTCFormat() {
		return btcFormat;
	}

	public NumberFormat getFiatFormat(String fiat) {
    	fiatFormat.setCurrency(Currency.getInstance(fiat));
		return fiatFormat;
	}

    public String getBTCUnits() {
        return "BTC";
    }

} public class PrefsUtil {

    public static final String CURRENT_FIAT = "currentFiat";
    public static final String CURRENT_FIAT_SEL = "currentFiatSel";
	public static final String CURRENT_EXCHANGE = "currentExchange";
	public static final String CURRENT_EXCHANGE_SEL = "currentExchangeSel";
    public static final String BLOCK_EXPLORER = "blockExplorer";
    public static final String FIRST_RUN = "1stRun";
    public static final String SIM_IMSI = "IMSI";
    public static final String ENABLE_TOR = "ENABLE_TOR";
    public static final String PIN_HASH = "pinHash";
    public static final String XPUB = "xpub";
    public static final String SCRAMBLE_PIN = "scramblePin";
	public static final String HAPTIC_PIN = "hapticPin";
	public static final String TESTNET = "testnet";

	private static Context context = null;
	private static PrefsUtil instance = null;

	private PrefsUtil() { ; }

	public static PrefsUtil getInstance(Context ctx) {

		context = ctx;
		
		if(instance == null) {
			instance = new PrefsUtil();
		}
		
		return instance;
	}
	
	public String getValue(String name, String value) {
	    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
	    return prefs.getString(name, (value == null || value.length() < 1) ? "" : value);
	}

	public boolean setValue(String name, String value) {
		Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
		editor.putString(name, (value == null || value.length() < 1) ? "" : value);
		return editor.commit();
	}

	public int getValue(String name, int value) {
	    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
	    return prefs.getInt(name, 0);
	}

	public boolean setValue(String name, int value) {
		Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
		editor.putInt(name, (value < 0) ? 0 : value);
		return editor.commit();
	}

	public boolean getValue(String name, boolean value) {
	    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
	    return prefs.getBoolean(name, value);
	}

	public boolean setValue(String name, boolean value) {
		Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
		editor.putBoolean(name, value);
		return editor.commit();
	}

	public boolean removeValue(String name) {
		Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
		editor.remove(name);
		return editor.commit();
	}

	public boolean clear() {
		Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
		editor.clear();
		return editor.commit();
	}

} public class TimeOutUtil {

    private static long TIMEOUT_DELAY = 1000 * 60 * 15;

    private static long lastPin = 0L;
    private static TimeOutUtil instance = null;

    private TimeOutUtil() { ; }

    public static TimeOutUtil getInstance() {

        if(instance == null) {
            instance = new TimeOutUtil();
        }

        return instance;
    }

    public void updatePin() {
        lastPin = System.currentTimeMillis();
    }

    public boolean isTimedOut() {
        return (System.currentTimeMillis() - lastPin) > TIMEOUT_DELAY;
    }

    public void reset() { lastPin = 0L; }

} public class TypefaceUtil {

    public static int awesome_arrow_down = 0xf063;
    public static int awesome_arrow_up = 0xf062;

    private static Typeface awesome_font = null;

    private static TypefaceUtil instance = null;

    private TypefaceUtil() { ; }

    public static TypefaceUtil getInstance(Context ctx) {

        if(instance == null) {
            instance = new TypefaceUtil();
            awesome_font = Typeface.createFromAsset(ctx.getAssets(), "fontawesome-webfont.ttf");
        }

        return instance;
    }

    public Typeface getAwesomeTypeface() {
        return awesome_font;
    }

}
import android.Manifest;
import android.animation.ObjectAnimator;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.InputType;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AnticipateInterpolator;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.lang3.StringUtils;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.crypto.BIP38PrivateKey;
import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.core.Coin;
import org.json.JSONException;
import com.dm.zbar.android.scanner.ZBarConstants;
import com.dm.zbar.android.scanner.ZBarScannerActivity;
import com.samourai.sentinel.access.AccessFactory;
import com.samourai.sentinel.api.APIFactory;
import com.samourai.sentinel.api.Tx;
import com.samourai.sentinel.hd.HD_Account;
import com.samourai.sentinel.hd.HD_Wallet;
import com.samourai.sentinel.hd.HD_WalletFactory;
import com.samourai.sentinel.permissions.PermissionsUtil;
import com.samourai.sentinel.service.WebSocketService;
import com.samourai.sentinel.sweep.PrivKeyReader;
import com.samourai.sentinel.sweep.SweepUtil;
import com.samourai.sentinel.util.AddressFactory;
import com.samourai.sentinel.util.AppUtil;
import com.samourai.sentinel.util.BlockExplorerUtil;
import com.samourai.sentinel.util.CharSequenceX;
import com.samourai.sentinel.util.DateUtil;
import com.samourai.sentinel.util.ExchangeRateFactory;
import com.samourai.sentinel.util.MonetaryUtil;
import com.samourai.sentinel.util.PrefsUtil;
import com.samourai.sentinel.util.TimeOutUtil;
import com.samourai.sentinel.util.TypefaceUtil;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import net.i2p.android.ext.floatingactionbutton.FloatingActionButton;
import net.i2p.android.ext.floatingactionbutton.FloatingActionsMenu;
import net.sourceforge.zbar.Symbol;
        @Override
        public int getCount() {
            if(txs == null) {
                txs = new ArrayList<Tx>();
                txStates = new HashMap<String, Boolean>();
            }
            return txs.size() + 1;
        }

        @Override
        public String getItem(int position) {
            if(txs == null) {
                txs = new ArrayList<Tx>();
                txStates = new HashMap<String, Boolean>();
            }
            if(position == 0) {
                return "";
            }
            return txs.get(position - 1).toString();
        }

        @Override
        public long getItemId(int position) {
            return position - 1;
        }

        @Override
        public int getItemViewType(int position) {
            return position == 0 ? TYPE_BALANCE : TYPE_ITEM;
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public View getView(final int position, View convertView, final ViewGroup parent) {

            View view = null;

            int type = getItemViewType(position);
            if(convertView == null) {
                if(type == TYPE_BALANCE) {
                    view = tvBalanceBar;
                }
                else {
                    view = inflater.inflate(R.layout.tx_layout_simple, parent, false);
                }
            } else {
                view = convertView;
            }

            if(type == TYPE_BALANCE) {
                ;
            }
            else {
                view.findViewById(R.id.TransactionStatus).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        ((ListView)parent).performItemClick(v, position, 0);
                    }
                });

                view.findViewById(R.id.ConfirmationCount).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        ((ListView)parent).performItemClick(v, position, 0);
                    }
                });

                Tx tx = txs.get(position - 1);

                TextView tvTodayLabel = (TextView)view.findViewById(R.id.TodayLabel);
                String strDateGroup = DateUtil.getInstance(BalanceActivity.this).group(tx.getTS());
                if(position == 1) {
                    tvTodayLabel.setText(strDateGroup);
                    tvTodayLabel.setVisibility(View.VISIBLE);
                }
                else {
                    Tx prevTx = txs.get(position - 2);
                    String strPrevDateGroup = DateUtil.getInstance(BalanceActivity.this).group(prevTx.getTS());

                    if(strPrevDateGroup.equals(strDateGroup)) {
                        tvTodayLabel.setVisibility(View.GONE);
                    }
                    else {
                        tvTodayLabel.setText(strDateGroup);
                        tvTodayLabel.setVisibility(View.VISIBLE);
                    }
                }

                String strDetails = null;
                String strTS = DateUtil.getInstance(BalanceActivity.this).formatted(tx.getTS());
                long _amount = 0L;
                if(tx.getAmount() < 0.0) {
                    _amount = Math.abs((long)tx.getAmount());
                    strDetails = BalanceActivity.this.getString(R.string.you_sent);
                }
                else {
                    _amount = (long)tx.getAmount();
                    strDetails = BalanceActivity.this.getString(R.string.you_received);
                }
                String strAmount = null;
                String strUnits = null;
                if(isBTC)    {
                    strAmount = getBTCDisplayAmount(_amount);
                    strUnits = getBTCDisplayUnits();
                }
                else    {
                    strAmount = getFiatDisplayAmount(_amount);
                    strUnits = getFiatDisplayUnits();
                }

                TextView tvDirection = (TextView)view.findViewById(R.id.TransactionDirection);
                TextView tvDirection2 = (TextView)view.findViewById(R.id.TransactionDirection2);
                TextView tvDetails = (TextView)view.findViewById(R.id.TransactionDetails);
                ImageView ivTxStatus = (ImageView)view.findViewById(R.id.TransactionStatus);
                TextView tvConfirmationCount = (TextView)view.findViewById(R.id.ConfirmationCount);

                tvDirection.setTypeface(TypefaceUtil.getInstance(BalanceActivity.this).getAwesomeTypeface());