public class EWApplication extends MultiDexApplication {
    private static final String TAG = "EWApplication";
    private EWWalletService ewWalletService;
    private WalletObservable walletObservable;
    private WalletObserver walletObserver;
    private final int SHUTDOWN_AFTER = 30 * 60 * 1000;

    //private Timer timer;
    //private TimedLogStat timedLogStat;

    private LruCache<String, Bitmap> bitmapCache;

    private final ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(final ComponentName className,
                                       final IBinder service) {
            Log.i(TAG,".onServiceConnected()");

            final EWBinder binder = (EWBinder) service;
            ewWalletService = binder.ewWalletService;
            walletObservable = binder.walletObservable;
            walletObserver = new WalletObserver(ewWalletService);
            walletObservable.addObserver(walletObserver);

        }

        @Override
        public void onServiceDisconnected(final ComponentName arg0) {
            Log.i(TAG,".onServiceDisconnected()");
            //walletObservable.deleteObserver(walletObserver);
        }
    };



    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, ".onCreate()");
        PRNGFixes.apply();
        final Intent intent = new Intent(this, EWWalletService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        //startService(intent);

        initLogging();

        /*if (BuildConfig.DEBUG) {
            timer = new Timer();
            timedLogStat = new TimedLogStat(this);
            timer.schedule(timedLogStat, 30000L, 30000L);
        }*/

        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = maxMemory / 8;
        Log.i(TAG, "cache size=" + cacheSize);

        bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // The cache size will be measured in kilobytes rather than
                // number of items.
                return bitmap.getByteCount() / 1024;
            }
        };
    }

    public LruCache<String, Bitmap> getBitmapCache() {
        return bitmapCache;
    }

    public EWWalletService getEwWalletService() {
        return ewWalletService;
    }

    public WalletObservable getWalletObservable() {
        return walletObservable;
    }

    private static boolean TEST = true;
    private void initLogging()
    {
        final File logDir = getDir("log", TEST ? Context.MODE_WORLD_READABLE : MODE_PRIVATE);
        final File logFile = new File(logDir, "wallet.log");

        final LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();

        final PatternLayoutEncoder filePattern = new PatternLayoutEncoder();
        filePattern.setContext(context);
        filePattern.setPattern("%d{HH:mm:ss.SSS} [%thread] %logger{0} - %msg%n");
        filePattern.start();

        final RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<ILoggingEvent>();
        fileAppender.setContext(context);
        fileAppender.setFile(logFile.getAbsolutePath());

        final TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<ILoggingEvent>();
        rollingPolicy.setContext(context);
        rollingPolicy.setParent(fileAppender);
        rollingPolicy.setFileNamePattern(logDir.getAbsolutePath() + "/wallet.%d.log.gz");
        rollingPolicy.setMaxHistory(7);
        rollingPolicy.start();

        fileAppender.setEncoder(filePattern);
        fileAppender.setRollingPolicy(rollingPolicy);
        fileAppender.start();

        final PatternLayoutEncoder logcatTagPattern = new PatternLayoutEncoder();
        logcatTagPattern.setContext(context);
        logcatTagPattern.setPattern("%logger{0}");
        logcatTagPattern.start();

        final PatternLayoutEncoder logcatPattern = new PatternLayoutEncoder();
        logcatPattern.setContext(context);
        logcatPattern.setPattern("[%thread] %msg%n");
        logcatPattern.start();

        final LogcatAppender logcatAppender = new LogcatAppender();
        logcatAppender.setContext(context);
        logcatAppender.setTagEncoder(logcatTagPattern);
        logcatAppender.setEncoder(logcatPattern);
        logcatAppender.start();

        final ch.qos.logback.classic.Logger log = context.getLogger(Logger.ROOT_LOGGER_NAME);
        log.addAppender(fileAppender);
        log.addAppender(logcatAppender);
        log.setLevel(Level.INFO);
    }


    private Timer timer=new Timer();
    private TimerTask timerTask = new TimedStopper(this);
    public void onResume() {
        Log.i(TAG,"onResume");
        timerTask.cancel();
        if(ewWalletService!=null && ewWalletService.getPeerGroup()!=null && !ewWalletService.getPeerGroup().isRunning()) {
            Log.i(TAG,"peerGroupIsNotRunning");
            final Intent intent = new Intent(this, EWWalletService.class);

            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            ewWalletService.startSync();

        }
    }

    public void onPause() {
        Log.i(TAG,"onPause");
        timerTask.cancel();
        timerTask=new TimedStopper(this);

        timer.schedule(timerTask, SHUTDOWN_AFTER);
    }

    public void unbindService() {
        Log.i(TAG,"unbindService");

        if(ewWalletService!=null && ewWalletService.getPeerGroup().isRunning()) {
            Log.i(TAG,"peerGroupIsRunning");
            ewWalletService.stopSync();
            unbindService(mConnection);
            stopService(new Intent(this, EWWalletService.class));
        }

    }

} public class Http {
    private final static Logger log      = Logger.getLogger(Http.class.getName());


    public static Optional<String> get(String urlString) {
        return get(urlString, 10000);
    }
    public static Optional<String> get(String urlString, int timeout) {

        try {
            URL url = new URL(urlString);

            HttpURLConnection connection= (HttpURLConnection) url.openConnection();
            connection.setRequestProperty("User-Agent", String.valueOf( System.currentTimeMillis() ) );
            connection.setRequestProperty("Accept-Encoding", "gzip, deflate");

            connection.setReadTimeout(timeout);
            connection.setConnectTimeout(timeout);

            final String contentEncoding = connection.getContentEncoding();
            final InputStream inputStream = getProperInputStream(connection);
            //System.out.println("connection.getContentEncoding() =" + contentEncoding);

            BufferedReader reader = new BufferedReader(new InputStreamReader( inputStream , Charsets.UTF_8));
            String line;
            StringBuffer sb=new StringBuffer();

            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            reader.close();

            int result=connection.getResponseCode();
            if(result>=200 && result<300) {
                String s = sb.toString();
                if(!"null".equals(s))
                    return Optional.of(s);
            } else {
                log.warning("result is not 2XX : " + result);
            }
        } catch (Exception e) {
            log.warning("Http.get() error " + e.toString() + " " + e.getMessage());
        }
        return Optional.absent();
    }

    private static InputStream getProperInputStream(HttpURLConnection connection) throws IOException {
        String enc = connection.getContentEncoding();
        if("gzip".equals(enc)) {
            return new GZIPInputStream(connection.getInputStream());
        } else if("deflate".equals(enc)) {
            return new InflaterInputStream(connection.getInputStream());
        }
        return connection.getInputStream();
    }

    public static Optional<String> post(String urlString, String postDataString, String contentType) {
        return req("POST", urlString, postDataString, contentType);
    }
    public static Optional<String> put(String urlString, String postDataString, String contentType) {
        return req("PUT", urlString, postDataString, contentType);
    }

    public static Optional<String> patchJson(String urlString, String jsonString) {
        Map<String,String> m = new HashMap<>();
        m.put("X-HTTP-Method-Override","PATCH");
        return req("POST", urlString, jsonString, "application/json", m, null);
    }

    public static Optional<String> postJson(String urlString, String jsonString) {
        return req("POST", urlString, jsonString, "application/json");
    }

    public static Optional<String> postJson(String urlString, String jsonString,Map<String,String> headers ) {
        return req("POST", urlString, jsonString, "application/json",headers, null);
    }

    public static Optional<String> putJson(String urlString, String jsonString) {
        return req("PUT", urlString, jsonString, "application/json");
    }

    public static Optional<String> putForm(String urlString,  Map<String,String> params) {
        return req("PUT", urlString,  urlEncodeUTF8(params) , "application/x-www-form-urlencoded");
    }
    public static Optional<String> postForm(String urlString, Map<String,String> params) {
        return req("POST", urlString, urlEncodeUTF8(params), "application/x-www-form-urlencoded");
    }

    public static Optional<String> postForm(String urlString, Map<String,String> params, Integer timeout) {
        return req("POST", urlString, urlEncodeUTF8(params), "application/x-www-form-urlencoded",null,timeout);
    }

    public static Optional<String> postP8G(String urlString, String data, String contentType, String token ) {
        Map<String,String> m = new HashMap<>();
        m.put("token",token);
        return req("POST", urlString, data, contentType, m, null);
    }

    public static Optional<String> req(String type, String urlString, String dataString, String contentType) {
        return req(type, urlString, dataString, contentType, null, null);
    }

    public static Optional<String> req(String type, String urlString, String dataString, String contentType, Map<String,String> headers, Integer timeout ) {

        try {

            byte[] postData       = dataString.getBytes( Charset.forName("UTF-8"));
            int    postDataLength = postData.length;
            log.info("posting to " + urlString + " data=" + dataString + " postDataLength=" + postDataLength + " contenttype=" + contentType );

            URL url = new URL(urlString);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            if(timeout!=null) {
                connection.setReadTimeout(timeout);
                connection.setConnectTimeout(timeout);
            }
            connection.setDoOutput(true);
            connection.setRequestMethod(type);
            connection.setRequestProperty("Content-Type", contentType);
            connection.setRequestProperty("Content-Length", Integer.toString( postDataLength ));
            connection.setRequestProperty("charset", "utf-8");
            //connection.setRequestProperty("User-Agent", "python-requests/2.4.1 CPython/2.7.8 Darwin/14.3.0");
            connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
            if(headers!=null) {
                for(String current : headers.keySet()) {
                    connection.setRequestProperty( current , headers.get(current) );
                }
            }
            try {
                DataOutputStream writer = new DataOutputStream( connection.getOutputStream() );
                writer.write( postData );
            } catch (Exception e) {log.severe(e.getMessage());}
            int result=connection.getResponseCode();
            if(result>=200 && result<300) {
                final InputStream stream = getProperInputStream(connection);
                String content = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
                String logContent = new String(content);
                Closeables.closeQuietly(stream);
                if (logContent != null) {
                    logContent = logContent.replaceAll("\n", "");
                    if (logContent.length() > 20)
                        logContent = logContent.substring(0, 20);
                }
                log.info("Http." + type + " respCode=" + result + " content=" + logContent);
                return Optional.of(content);
            } else {
                log.warning("result not 200 = " + result);
                try {
                    final InputStream stream = connection.getInputStream();
                    String content = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
                    log.warning(content);
                } catch(Exception e) {
                    log.warning("msg=" + e.getMessage());
                }
            }
        } catch (Exception e) {
            log.warning("Http." + type + " error " + e.getMessage());
        }
        return Optional.absent();
    }

    public static String urlEncodeUTF8(String s) {
        try {
            return URLEncoder.encode(s, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new UnsupportedOperationException(e);
        }
    }
    public static String urlEncodeUTF8(Map<?,?> map) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<?,?> entry : map.entrySet()) {
            if (sb.length() > 0) {
                sb.append("&");
            }
            sb.append(String.format("%s=%s",
                    urlEncodeUTF8(entry.getKey().toString()),
                    urlEncodeUTF8(entry.getValue().toString())
            ));
        }
        return sb.toString();
    }






} public class Message implements Serializable, Comparable<Message> {


    private String txHash;
    private String message;
    private Long timestamp;
    private String messageId;
    private String link;
    private Integer replies;
    private Integer likes;
    private Boolean answer;
    private Integer rank;
    private Integer height;
    private Double value;
    private Integer view;
    private Integer weekView;
    private Integer retweets;
    private String alias;
    private String aliasName;
    private String aliasAndTime;

    public Boolean getAnswer() {
        return answer;
    }

    public void setAnswer(Boolean answer) {
        this.answer = answer;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getMessageId() {
        return messageId;
    }

    public void setMessageId(String messageId) {
        this.messageId = messageId;
    }

    public Integer getReplies() {
        return replies;
    }

    public void setReplies(Integer replies) {
        this.replies = replies;
    }

    public Long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Long timestamp) {
        this.timestamp = timestamp;
    }

    public String getTxHash() {
        return txHash;
    }

    public void setTxHash(String txHash) {
        this.txHash = txHash;
    }

    public Integer getLikes() {
        return likes;
    }

    public void setLikes(Integer likes) {
        this.likes = likes;
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public String getAliasAndTime() {
        return aliasAndTime;
    }

    public void setAliasAndTime(String aliasAndTime) {
        this.aliasAndTime = aliasAndTime;
    }

    public String getAliasName() {
        return aliasName;
    }

    public void setAliasName(String aliasName) {
        this.aliasName = aliasName;
    }

    @Override
    public String toString() {
        return "Message{" +
                ", txHash='" + txHash + '\'' +
                ", message='" + message + '\'' +
                ", timestamp=" + timestamp +
                '}';
    }

    /*
            "message":"Easy to use soft to browse your data on blockchain ... soon !",
            "timestamp":"1440565502",
            "time":"26 agosto 2015 5.05 GMT",
            "cursor":null,
            "height":371549,
            "hash":"9f601f7ff281598b61ebc7247fee5fe4b4eed8ca3af32a0f30286d7f992a89af",
            "replies":null,
            "answer":false
    */

    public static Message buildFromJson(JSONObject jo) throws Exception {

        Message m = new Message();

        try { m.message = StringEscapeUtils.unescapeHtml(jo.getString("message"));} catch (JSONException e) { throw new Exception("Missing mandatory field <message>");} //mandatory
        try { m.timestamp = jo.getLong("timestamp")*1000;} catch (JSONException e) { throw new Exception("Missing mandatory field <timestamp>");} //mandatory

        try { m.messageId = jo.getString("messageId");} catch (JSONException e) {} //optional
        try { m.txHash = jo.getString("hash");} catch (JSONException e) {} //optional
        try { m.replies = jo.getInt("replies");} catch (JSONException e) { m.replies = 0; } //optional
        try { m.answer = jo.getBoolean("answer");} catch (JSONException e) { m.answer = false; } //optional
        try { m.likes = jo.getInt("likes");} catch (JSONException e) { m.likes = 0; } //optional
        try { m.rank = jo.getInt("rank");} catch (JSONException e) { m.rank = 0; } //optional
        try { m.height = jo.getInt("height");} catch (JSONException e) { m.height = 0; } //optional
        try { m.value = jo.getDouble("value");} catch (JSONException e) { m.value = new Double(0); } //optional
        try { m.view = jo.getInt("view");} catch (JSONException e) { m.view = 0; } //optional
        try { m.weekView = jo.getInt("weekView");} catch (JSONException e) { m.weekView = 0; } //optional
        try { m.retweets = jo.getInt("retweets");} catch (JSONException e) { m.retweets = 0; } //optional
        try { m.link = jo.getString("link");} catch (JSONException e) { m.link = null; } //optional

        try { m.alias = jo.getString("alias");} catch (JSONException e) {  } //optional
        try { m.aliasName = jo.getString("aliasName");} catch (JSONException e) {  } //optional
        try { m.aliasAndTime = jo.getString("aliasAndTime");} catch (JSONException e) {  } //optional

        return m;
    }

    @Override
    public boolean equals(Object o) {
        return o != null && o instanceof Message && ((Message) o).txHash.equals(this.txHash);
    }

    @Override
    public int compareTo(Message another) {
        return -this.timestamp.compareTo(another.getTimestamp());
    }

    public Integer getRank() {
        return rank;
    }

    public void setRank(Integer rank) {
        this.rank = rank;
    }

    public Integer getHeight() {
        return height;
    }

    public void setHeight(Integer height) {
        this.height = height;
    }

    public Double getValue() {
        return value;
    }

    public void setValue(Double value) {
        this.value = value;
    }

    public Integer getView() {
        return view;
    }

    public void setView(Integer view) {
        this.view = view;
    }

    public Integer getWeekView() {
        return weekView;
    }

    public void setWeekView(Integer weekView) {
        this.weekView = weekView;
    }

    public Integer getRetweets() {
        return retweets;
    }

    public void setRetweets(Integer retweets) {
        this.retweets = retweets;
    }


    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }
} public class Preferences {

    public static final String NODES      = "nodes";
    public static final String PASSPHRASE = "passphrase";
    public static final String PIN        = "pin";
    public static final String EMAIL      = "email";
    public static final String CHK_ONE    = "ckone";
    public static final String CHK_TWO    = "cktwo";
    public static final String DONATION   = "donation";
    public static final String TO_NOTIFY  = "tonotify";
    public static final String ALIAS_NAME = "aliasname";
} public class MessageView  extends LinearLayout {

    private View view;
    TextView txtMessage,txtStatus,txtDate,txtHeader;
    ImageView identicon;

    public MessageView(Context context) {
        super(context);
        init(context);
    }

    public MessageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public MessageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }
    private void init(Context context) {
        LayoutInflater mInflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view=mInflater.inflate(R.layout.item_message, this);

        txtMessage = (TextView) view.findViewById(R.id.txtMessage);
        txtStatus = (TextView) view.findViewById(R.id.txtStatus);
        txtDate = (TextView) view.findViewById(R.id.txtDate);
        txtHeader = (TextView) view.findViewById(R.id.txtHeader);
        identicon = (ImageView) view.findViewById(R.id.identicon);
    }


    public void setTextMessage( int resource){
        txtMessage.setTextAppearance(getContext(), resource);
        txtMessage.setTextColor(getResources().getColor(R.color.black));
    }

    public void set(final Message m){
        // message
        String text = m.getMessage();
        if (m.getLink() != null) {
            String link = m.getLink();
            String linkreplace = "";

            if (link.startsWith("@"))
                linkreplace = "<a href='http://twitter.com/" + link + "'>" + link + "</a>";
            else if (link.contains("http"))
                linkreplace = "<a href='" + link + "'>" + link + "</a>";
            else if (link.contains("https"))
                linkreplace = "<a href='" + link + "'>" + link + "</a>";
            else
                linkreplace = "<a href='http://" + link + "'>" + link + "</a>";
            text = text.replace(link, linkreplace);
        }
        txtMessage.setText(Html.fromHtml(text));
        if (m.getRank() == 1) {
            txtMessage.setTextAppearance(getContext(), android.R.style.TextAppearance_Large);
        } else if (m.getRank() == 2) {
            txtMessage.setTextAppearance(getContext(), android.R.style.TextAppearance_Medium);
        } else if (m.getRank() == 3) {
            txtMessage.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
        }
        txtMessage.setTextColor(getContext().getResources().getColor(android.R.color.black));

        // status
        txtStatus.setVisibility(View.GONE);
        txtStatus.setText("");
        String strStatus = "";
        if (m.getReplies() > 0) {
            strStatus += String.valueOf(m.getReplies()) + (m.getReplies() == 1 ? " reply " : " replies ");
        }
        if (m.getAnswer() == true) {
            if (strStatus.length() > 0)
                strStatus += "- ";
            strStatus += "answer";
        }
        if (m.getLikes() > 0) {
            if (strStatus.length() > 0)
                strStatus += "- ";
            strStatus += String.valueOf(m.getLikes()) + (m.getLikes() == 1 ? " like " : " likes ");
        }
        if (strStatus.length() > 0) {
            txtStatus.setText(strStatus);
            txtStatus.setVisibility(View.VISIBLE);
        }

        // date
        String dateFormatted = new SimpleDateFormat("dd MMM yyyy HH.mm").format(new Date(m.getTimestamp()));
        if (m.getAliasName() != null) {
            txtDate.setText(m.getAliasName() + " - " + dateFormatted);
        } else {
            txtDate.setText(dateFormatted);
        }

        //identicon
        if (m.getAlias() != null) {
            Bitmap bitmap = IdenticonGenerator.generate(m.getAlias());
            identicon.setImageBitmap(bitmap);
            identicon.setVisibility(View.VISIBLE);
            identicon.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(v.getContext(), ProfileActivity.class);
                    intent.putExtra("accountId", String.valueOf(m.getAlias()));
                    v.getContext().startActivity(intent);
                }
            });
        } else {
            identicon.setVisibility(View.GONE);
        }

        txtHeader.setVisibility(View.GONE);


        //onclick
        // add click listener

        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(v.getContext(), DetailActivity.class);
                intent.putExtra("hash", String.valueOf(m.getTxHash()));
                v.getContext().startActivity(intent);
            }
        });
    }

} public class PinAlertDialogFragment extends DialogFragment {

    public static PinAlertDialogFragment newInstance(int title) {
        PinAlertDialogFragment frag = new PinAlertDialogFragment();
        Bundle args = new Bundle();
        args.putInt("title", title);
        frag.setArguments(args);
        return frag;
    }

    //View view;
    Runnable mRunnable=null;
    View view=null;

    public void setPositive(Runnable runnable){
        this.mRunnable=runnable;
    }

    public String getPin(){
        if(view==null)
            return null;
        else
            return ((EditText)view.findViewById(R.id.editText)).getText().toString();
    }


    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        int title = getArguments().getInt("title");
        view = getActivity().getLayoutInflater().inflate(R.layout.dlg_pin, null);

        return new AlertDialog.Builder(getActivity())
                // Set Dialog Title
                .setTitle(title)
                // Set Dialog Message : custom view
                .setView(view)
                // Positive button
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // Do something
                        final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getActivity());
                        String pin = sharedPref.getString(Preferences.PIN,null);
                        if (pin!=null && getPin().equals(pin)){
                            // SHOW
                                if (mRunnable!=null)
                                    mRunnable.run();
                        }else {
                            new AlertDialog.Builder(getActivity())
                                    .setTitle("Attention")
                                    .setMessage("Invalid PIN. Retry...")
                                    .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog, int which) {
                                            // do nothing
                                        }
                                    }).show();
                        }
                    }
                })
                // Negative Button
                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,	int which) {
                        // Do nothing
                    }
                }).create();
    }


} public class EWWalletService extends Service implements Runnable {
    private static final String TAG = "EWWalletService";

    private static String REGISTER_ALIAS_TAG = "EWA ";
    private static String EW_MESSAGE_TAG     = "EW ";

    private final static NetworkParameters PARAMS= BitcoinNetwork.getInstance().get().getParams();
    private final static int EPOCH     = 1447891200;  //19 Novembre 2015 00:00 first EWA 5f362444d23dd258ae1c2b60b1d79cb2c5231fc50df50713a415e13502fc1da9
    private final static int PER_CHUNK = 100;
    public static final Long DUST      = 5000L ;
    public static final Long DONATION  = 10000L ;
    public static final Long FEE       = 11000L ;
    public static final String BLOCKCHAIN_FILE = "blockchain";
    public static final String WALLET_FILE     = "wallet";
    public static final String BITCOIN_PATH    = "bitcoin";

    private Set<Address> used     = new HashSet<>();
    private Integer nextMessageId = 0 ;
    private Integer nextChange    = 0 ;

    private Set<Address> all       = new HashSet<>();
    private List<ECKey> messagesId = new ArrayList<>();
    private List<ECKey> changes    = new ArrayList<>();
    private EWDerivation ewDerivation;

    private MyBlockchainListener chainListener;
    private MyDownloadListener downloadListener;
    private StoredBlock chainHead;
    private BlockStore blockStore;
    private BlockChain blockChain;
    private PeerGroup peerGroup;
    private Wallet wallet;
    private boolean running=false;

    private WalletObservable walletObservable = new WalletObservable();
    private EWBinder mBinder= new EWBinder(this, walletObservable);

    public boolean isRunning() {
        return running;
    }


    public Integer getNextMessageId() {
        return nextMessageId;
    }

    public Integer getNextChange() {
        return nextChange;
    }

    private Address getAlias() {  //watch observable
        return changes.get( 0 ).toAddress(PARAMS);
    }

    private Address getCurrent() throws NotSyncedException, NotEnoughAddressException {  //watch observable
        if(!walletObservable.isSynced() || nextChange==0 || nextChange>99) { //TODO manage nextChange>99
            if(nextChange>99)
                throw new NotEnoughAddressException();
            throw new NotSyncedException("notSynced " + walletObservable.isSynced() + " " + nextChange);
        }
        return changes.get( nextChange-1 ).toAddress(PARAMS);
    }

    private Address getNext() throws NotSyncedException, NotEnoughAddressException  {  //watch observable
        if(!walletObservable.isSynced() || nextChange>99) { //TODO manage nextChange>99
            if(nextChange>99)
                throw new NotEnoughAddressException();
            throw new NotSyncedException("notSynced " + walletObservable.isSynced() + " " + nextChange);
        }
        return changes.get( nextChange ).toAddress(PARAMS);
    }

    private EWMessageData getNextMessageData() throws NotSyncedException, NotEnoughAddressException  {
        if(!walletObservable.isSynced() || nextChange ==0 || nextChange>99) { //TODO manage nextChange>99
            final String msg = "getNextMessageData returning null " + walletObservable.isSynced() + " " + nextChange;
            Log.i(TAG, msg);
            if(nextChange>99)
                throw new NotEnoughAddressException();
            throw new NotSyncedException(msg);
        }
        EWMessageData ewMessageData = new EWMessageData();
        ewMessageData.setMessageId(messagesId.get(nextMessageId).toAddress(PARAMS));
        ewMessageData.setChange(changes.get(nextChange).toAddress(PARAMS));
        ewMessageData.setInput(changes.get(nextChange - 1));
        nextChange++;
        nextMessageId++;
        return ewMessageData;
    }

    public Transaction createMessageTx(String message, String answerTo) throws NotSyncedException, NotEnoughAddressException, InsufficientMoneyException, AddressFormatException {
        Log.i(TAG,"createMessageTx " +message + " " + answerTo );

        if(!walletObservable.isSynced() || nextChange==0){
            String msg = "sendMessage returning null " + walletObservable.isSynced() + " " + nextChange + " " + nextMessageId;
            Log.i(TAG, msg);   //TODO manage nextChange>99 or nextMessageId>0
            throw new NotSyncedException(msg);
        } else if (nextChange>99 || nextMessageId>99) {
            String msg = "sendMessage returning null " + walletObservable.isSynced() + " " + nextChange + " " + nextMessageId;
            Log.i(TAG, msg);   //TODO manage nextChange>99 or nextMessageId>0
            throw new NotEnoughAddressException();
        }
        EWMessageData ewMessageData= getNextMessageData();
        final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        final boolean donation = sharedPref.getBoolean(Preferences.DONATION, true);

        final ECKey input = ewMessageData.getInput();
        Address inputAddress = input.toAddress(PARAMS);
        Transaction newTx = new Transaction(PARAMS);

        List<TransactionOutput> transactionOutputList = getMines(inputAddress);
        Long totalAvailable = 0L;
        for (TransactionOutput to  : transactionOutputList) {
            //0.00001
            long value = to.getValue().getValue();
            Log.i(TAG,to.getParentTransactionHash().toString() + ":" + to.getIndex() + " outputvalue:" + value);
            totalAvailable += value;
            newTx.addInput(to);

        }

        Log.i(TAG,"total available " + totalAvailable);
        Long toSend = totalAvailable-FEE-DUST;
        if(donation)
            toSend = toSend-DONATION;

        message = EW_MESSAGE_TAG + message;
        final byte[] toWrite = message.getBytes();

        newTx.addOutput(Coin.valueOf(DUST), ewMessageData.getMessageId());

        if(answerTo!=null) {
                try {
                    final Address addressTo = new Address(PARAMS, answerTo);
                    newTx.addOutput(Coin.valueOf(DUST), addressTo);
                    toSend-=DUST;
                } catch (AddressFormatException e) {
                    String msg = "answerTo is not a valid bitcoin address" + e.getMessage();
                    Log.e(TAG, msg);
                    throw new AddressFormatException(msg);
                }
        }

        if(toSend < DUST) {
            String msg = "toSend is less than dust";
            Log.i(TAG, msg);
            throw new InsufficientMoneyException(Coin.valueOf(toSend));
        }

        newTx.addOutput(Coin.ZERO,
                new ScriptBuilder()
                        .op(ScriptOpCodes.OP_RETURN)
                        .data(toWrite)
                        .build());
        newTx.addOutput(Coin.valueOf(toSend), ewMessageData.getChange());

        if(donation) {
            Log.i(TAG, "Has donation, adding output");
            DeterministicKey todayDonation = ewDerivation.getTodayDonation();
            newTx.addOutput(Coin.valueOf(DONATION), todayDonation.toAddress(PARAMS));
        }

        Wallet.SendRequest req = Wallet.SendRequest.forTx(newTx);
        wallet.signTransaction(req);
        final String newTxHex = Bitcoin.transactionToHex(newTx);

        Log.i(TAG, "newTxHash: " + newTx.getHash());
        Log.i(TAG, "newTxHex: " + newTxHex);
        return newTx;
    }

    public TransactionBroadcast broadcastTransaction(Transaction tx) {
        Log.i(TAG,"sendTransaction " +tx );
        TransactionBroadcast transactionBroadcast = peerGroup.broadcastTransaction(tx);
        walletObservable.setState(WalletObservable.State.PENDING);
        walletObservable.notifyObservers();
        return transactionBroadcast;
    }

    public Transaction createExitTransaction(String bitcoinAddress) throws NotSyncedException, NotEnoughAddressException, InsufficientMoneyException, AddressFormatException {
        Log.i(TAG,"createExitTransaction to " +bitcoinAddress );

        if(!walletObservable.isSynced() || nextChange==0 || nextChange>99 ) {
            final String msg = "createExitTransaction returning null " + walletObservable.isSynced() + " " + nextChange;
            Log.w(TAG, msg);   //TODO manage nextChange>99 or nextMessageId>0
            if(nextChange>99)
                throw new NotEnoughAddressException();
            throw new NotSyncedException(msg);
        }

        Address destination = new Address(PARAMS, bitcoinAddress);

        final Transaction newTx = new Transaction(PARAMS);

        Long totalAvailable = 0L;
        Map<Sha256Hash, Transaction> unspent = wallet.getTransactionPool(WalletTransaction.Pool.UNSPENT);
        for (Map.Entry<Sha256Hash, Transaction> entry : unspent.entrySet()) {
            Transaction current = entry.getValue();
            List<TransactionOutput> outputs = current.getOutputs();

            for (TransactionOutput to : outputs) {
                long value = to.getValue().getValue();

                if(to.isAvailableForSpending() && value>0 && to.isMine(wallet)) {
                    Log.i(TAG, "adding spendable output " + to);
                    newTx.addInput(to);
                    totalAvailable += value;
                }
            }

        }

        Log.i(TAG,"total available " + totalAvailable);
        Long toSend = totalAvailable-FEE;
        if(toSend < DUST) {
            final String msg = "toSend is less than dust, exiting " + toSend;
            Log.i(TAG, msg);
            throw new InsufficientMoneyException( Coin.valueOf(DUST-toSend) );
        }

        newTx.addOutput(Coin.valueOf(toSend), destination );

        Wallet.SendRequest req = Wallet.SendRequest.forTx(newTx);
        wallet.signTransaction(req);
        final String newTxHex = Bitcoin.transactionToHex(newTx);

        Log.i(TAG, "new tx hash: " + newTx.getHash());
        Log.i(TAG, "new tx hex: " + newTxHex);
        return newTx;
    }

    public Transaction registerAlias(String aliasName) throws NotSyncedException, InsufficientMoneyException {
        if(!walletObservable.isSynced() || nextChange != 1) {
            final String msg = "not synced or next change different from zero " + walletObservable.isSynced() + " " + nextChange;
            Log.i(TAG, msg);
            throw new NotSyncedException(msg);
        }

        ECKey aliasKey = changes.get(0);
        ECKey firstChange = changes.get(1);
        nextChange=2;

        Address aliasAddress = aliasKey.toAddress(PARAMS);
        Log.i(TAG,"aliasAddress is = " + aliasAddress.toString() );
        Transaction newTx = new Transaction(PARAMS);

        List<TransactionOutput> transactionOutputList = getMines(aliasAddress);  //adfgsdg
        Long totalAvailable = 0L;
        for (TransactionOutput to  : transactionOutputList) {
            totalAvailable += to.getValue().getValue();
            newTx.addInput(to);
        }
        Long toSend = totalAvailable-FEE-DUST;
        if(toSend < DUST) {
            final String msg = "toSend is less than dust " + toSend;
            Log.i(TAG, msg);
            throw new InsufficientMoneyException( Coin.valueOf(DUST-toSend) );
        }

        final Address change = firstChange.toAddress(PARAMS);
        Log.i(TAG, "Sending to change " + change.toString() + " " + toSend + " satoshis");

        aliasName = REGISTER_ALIAS_TAG + aliasName;

        final byte[] toWrite = aliasName.getBytes();
        newTx.addOutput(Coin.ZERO,
                new ScriptBuilder()
                        .op(ScriptOpCodes.OP_RETURN)
                        .data(toWrite)
                        .build());
        newTx.addOutput(Coin.valueOf(toSend), change);

        Wallet.SendRequest req = Wallet.SendRequest.forTx(newTx);
        wallet.signTransaction(req);

        final String newTxHex = Bitcoin.transactionToHex(newTx);

        Log.i(TAG, newTxHex);

        return  newTx;
    }

    private List<TransactionOutput> getMines(Address address) {

        Map<Sha256Hash, Transaction> unspent = wallet.getTransactionPool(WalletTransaction.Pool.UNSPENT);
        List<TransactionOutput> transactionOutputList= new ArrayList<>();
        for (Map.Entry<Sha256Hash, Transaction> entry : unspent.entrySet()) {
            Transaction current = entry.getValue();
            List<TransactionOutput> outputs = current.getOutputs();
            for (TransactionOutput o : outputs) {
                if(o.isAvailableForSpending()) {
                    Address a = o.getAddressFromP2PKHScript(PARAMS);
                    if(address.equals(a)) {
                        transactionOutputList.add(o);
                    }
                }
            }
        }
        return transactionOutputList;
    }

    @Override
    public IBinder onBind(final Intent intent) {
        Log.i(TAG, ".onBind() " + intent);
        return mBinder;
    }

    @Override
    public boolean onUnbind(final Intent intent) {
        Log.i(TAG, ".onUnbind()");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        Log.i(TAG, ".onCreate()");
        startSync();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, ".onDestroy()");
    }

    public void startSync() {
        new Thread(this).start();
    }



    @Override
    public void run() {
        try {
            running=true;
            Log.i(TAG,"my network is " + BitcoinNetwork.getInstance().get().getParams().getId() );
            final boolean isRegtest = BitcoinNetwork.getInstance().get().getParams().getId().equals(RegTestParams.get().getId());

            org.bitcoinj.core.Context.getOrCreate(PARAMS);
            walletObservable.setState(WalletObservable.State.STARTED);
            walletObservable.notifyObservers();

            Log.i(TAG,".run()");
            final Context context = getApplicationContext();
            final SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
            final String passphrase = sharedPref.getString(Preferences.PASSPHRASE, null);
            if(passphrase==null) {
                Log.i(TAG,"PassphraseIsNull, cannot sync");
                walletObservable.setState(WalletObservable.State.NULL_PASSPHRASE);
                walletObservable.notifyObservers();
                return;
            }
            final byte[] seed = Bitcoin.getEntropyFromPassphrase(passphrase);
            ewDerivation = new EWDerivation(seed);
            final String alias = Bitcoin.keyToStringAddress( ewDerivation.getAlias() );
            String aliasNamePref = sharedPref.getString(Preferences.ALIAS_NAME, null);
            if(aliasNamePref!=null) {
                Log.i(TAG,"aliasNamePref=" + aliasNamePref);
                walletObservable.setAliasName(aliasNamePref);
            }
            walletObservable.setAlias(alias);
            walletObservable.notifyObservers();

            File path;
            if(context!=null)
                path= context.getDir(BITCOIN_PATH, Context.MODE_PRIVATE);
            else
                path = new File("./data/");
            final File blockFile = new File(path, BLOCKCHAIN_FILE );
            final File walletFile = new File(path, WALLET_FILE );

            long start = System.currentTimeMillis();
            for (int i = 0; i < PER_CHUNK; i++) {
                final ECKey messageKey = ECKey.fromPrivate(ewDerivation.getMessages(i).getPrivKey());
                final ECKey changeKey  = ECKey.fromPrivate(ewDerivation.getChanges(i).getPrivKey());
                messagesId.add(messageKey);
                changes.add(changeKey);
                all.add(messageKey.toAddress(PARAMS));
                all.add(changeKey.toAddress(PARAMS));
            }


            if(walletFile.exists()) {
                Log.i(TAG, "Wallet exist, loading from file");
                wallet = Wallet.loadFromFile(walletFile);
                Log.i(TAG,"WalletHeightAtStart " + wallet.getLastBlockSeenHeight() );
                Log.i(TAG, "WalletTxNumberAtStart " + wallet.getTransactionsByTime());
            }
            else {
                Log.i(TAG, "Wallet not exist, creating new");
                List<ECKey> ecKeyList = new ArrayList<>();
                ecKeyList.addAll(messagesId);
                ecKeyList.addAll(changes);
                wallet = Wallet.fromKeys(PARAMS,ecKeyList);
            }

            Log.i(TAG, "wallet key size " + wallet.getKeychainSize());
            Log.i(TAG, "wallet bloom " + wallet.getBloomFilter(1E-5));
            wallet.autosaveToFile(walletFile, 30, TimeUnit.SECONDS, new WalletSaveListener());
            wallet.setAcceptRiskyTransactions(true);
            //wallet.cleanup();
            final long l = System.currentTimeMillis() - start;
            Log.i(TAG, "My messages id are " + messagesId);
            Log.i(TAG, "My changes are " + changes);
            Log.i(TAG, "Derivation takes " + l);

            blockStore = new SPVBlockStore(PARAMS, blockFile);
            chainHead = blockStore.getChainHead();
            Log.i(TAG, "BlockStoreAtStartHeight " + chainHead.getHeight());

            if ( chainHead.getHeight() == 0 && !isRegtest ) {  //first run
                Log.i(TAG, "First run");
                CheckpointManager.checkpoint(PARAMS, BitcoinNetwork.getInstance().getCheckPointsStream() , blockStore, EPOCH);
            }
            wallet.addEventListener(new EWWalletEventListener(walletObservable), Threading.SAME_THREAD);
            //wallet.setRiskAnalyzer(new DefaultRiskAnalysis.Analyzer());

            blockChain = new BlockChain(PARAMS, wallet, blockStore);
            chainListener = new MyBlockchainListener( all, (EWApplication) getApplication() );
            blockChain.addListener(chainListener);
            peerGroup = new PeerGroup(PARAMS, blockChain);
            peerGroup.addWallet(wallet);  //Unconfirmed wasn't seen because there wasn't this line -> a day spent on this line. Fuck
            //peerGroup.setMaxConnections(40);

            if(isRegtest) {
                Log.i(TAG,"I am in regtest");
                peerGroup.addAddress( InetAddress.getByName( "192.168.1.233" ) );
            } else {
                final Set<String> stringSet = sharedPref.getStringSet(Preferences.NODES, new HashSet<String>());
                if(stringSet.size()>0) {
                    Log.i(TAG,"There are personal nodes " + stringSet.size() );
                    for (String current : stringSet) {
                        boolean portOpen = isPortOpen(current, 8333, 1000);
                        if(portOpen) {
                            Log.i(TAG,"port is reachable! " + current + ":8333" );
                            peerGroup.addAddress(InetAddress.getByName(current));
                        } else {
                            Log.i(TAG,"port is unreachable! " + current + ":8333" );
                        }
                    }
                } else {
                    Log.i(TAG,"There are no personal nodes");
                }
                peerGroup.addPeerDiscovery(new DnsDiscovery(PARAMS));
                peerGroup.setFastCatchupTimeSecs(EPOCH);

            }

            //peerGroup.setMaxConnections(1);
            //peerGroup.addWallet(wallet);
            peerGroup.addPeerFilterProvider(new MyPeerFilterProvider(changes, messagesId));
            peerGroup.setDownloadTxDependencies(false);
            downloadListener = new MyDownloadListener(walletObservable);
            peerGroup.startAsync();
            walletObservable.setState(WalletObservable.State.SYNCING);

            walletObservable.notifyObservers();

            Log.i(TAG, "starting download");
            peerGroup.startBlockChainDownload(downloadListener);
            Log.i(TAG, "download started");

        } catch (Exception e) {
            Log.e(TAG, "" + e.getMessage());
            e.printStackTrace();
        }

    }


    public static boolean isPortOpen(final String ip, final int port, final int timeout) {
        try {
            Socket socket = new Socket();
            socket.connect(new InetSocketAddress(ip, port), timeout);
            socket.close();
            return true;
        }

        catch(ConnectException ce){
            ce.printStackTrace();
            return false;
        }

        catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    public void onSynced() {

        refreshNextsAndAlias();

        Log.i(TAG, "peerGroup.getConnectedPeers()=" + peerGroup.getConnectedPeers());
        Log.i(TAG, "chain.getBestChainHeight()=" + blockChain.getBestChainHeight());
        Log.i(TAG, "chainHeadHeightAtBeginning=" + chainHead.getHeight());
        Log.i(TAG, "bloom matches=" + chainListener.getBloomMatches());
        Log.i(TAG, "downloaded size=" + downloadListener.getSize() + " bytes");
        Log.i(TAG, "used address=" + used);
        Log.i(TAG, "nextMessageId=" + nextMessageId);
        Log.i(TAG, "nextChange=" + nextChange);

        //Log.i(TAG, "wallet=" + wallet);
        walletObservable.setState(WalletObservable.State.SYNCED);
        Log.i(TAG, "Changed state");
        walletObservable.setWalletBalance(wallet.getBalance());
        walletObservable.setWalletUnconfirmedBalance(wallet.getBalance(Wallet.BalanceType.ESTIMATED));
        Log.i(TAG, "Changed wallet balance");
        Address current;
        try {
            current = nextChange == 0 ? getAlias() : getCurrent();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        walletObservable.setCurrent(current);

        //walletObservable.setAliasName("Test");  //TODO DEBUG

        Log.i(TAG, "Changed current");

        Log.i(TAG, "Notifying");
        walletObservable.notifyObservers();
        Log.i(TAG, "Ending...");

    }

    private Set<Integer> processed = new HashSet<>();
    public synchronized void refreshNextsAndAlias() {
        List<Transaction> allTx = wallet.getTransactionsByTime();
        final int size = allTx.size();
        if(processed.contains(size))
            return;
        Log.i(TAG, "refreshNextsAndAlias allTx.size()=" + size);

        for (Transaction tx : allTx) {
            //Log.i(TAG,"tx=" + tx);
            final List<TransactionInput> inputs = tx.getInputs();
            for (TransactionInput input : inputs) {
                try {
                    Address current = input.getScriptSig().getFromAddress(PARAMS);
                    if (all.contains(current)) {
                        used.add(current);
                    }
                } catch (ScriptException e) {   //needed for p2sh input, eg:
                    Log.i(TAG,"p2sh input");

                }
            }

            final List<TransactionOutput> outputs = tx.getOutputs();
            for (TransactionOutput output : outputs) {
                Address current = output.getAddressFromP2PKHScript(PARAMS);
                if(all.contains(current)) {
                    used.add(current);
                }
            }

            String aliasName = checkAlias(tx,walletObservable);
            if(aliasName!=null) {
                SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
                sharedPref.edit().putString(Preferences.ALIAS_NAME,aliasName).apply();
            }
        }

        nextMessageId=0;
        for (ECKey current : messagesId) {
            Address a = current.toAddress(PARAMS);
            if (used.contains(a))
                nextMessageId++;
            else
                break;
        }
        nextChange=0;
        for (ECKey current : changes) {
            Address a = current.toAddress(PARAMS);
            if (used.contains(a))
                nextChange++;
            else
                break;
        }
        processed.add(size);
    }


    public static boolean isEWMessage(Transaction tx) {
        List<TransactionOutput> outputs = tx.getOutputs();
        for (TransactionOutput out : outputs) {
            byte[] scriptBytes = out.getScriptBytes();
            if(scriptBytes.length>0) {
                String hexString = Hex.toHexString(scriptBytes);
                if(hexString.startsWith("6a")) {
                    hexString=hexString.substring(2);
                    if( hexString.startsWith("455720") || hexString.substring(2).startsWith("455720") ) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private static String EWA_PREFIX = "455741";
    public String checkAlias(Transaction tx, WalletObservable walletObservable) {
        final List<TransactionOutput> outputs = tx.getOutputs();
        for (TransactionOutput to : outputs) {
            byte[] script = to.getScriptBytes();
            if(script.length>0) {
                String hexString = Hex.toHexString(script);
                Log.i(TAG, "outputHEX: " + hexString);
                if (hexString.startsWith("6a")) {
                    Log.i(TAG, "outputHEX is OP_RETURN! ");

                    hexString = hexString.substring(2);
                    String aliasNameHex=null;
                    if(hexString.startsWith(EWA_PREFIX)  ) {  //NON_STANDARD
                        Log.i(TAG, "outputHEX is EWA NON STANDARD");
                        aliasNameHex = hexString.substring(6);
                    } else if (hexString.length()>2 && hexString.substring(2).startsWith(EWA_PREFIX)) {
                        Log.i(TAG, "outputHEX is EWA WITH LENGTH");
                        aliasNameHex = hexString.substring(8);
                    }
                    if(aliasNameHex!=null) {
                        String aliasName = new String( Bitcoin.fromHex(aliasNameHex) , Charset.forName("utf-8") );

                        if(aliasName!=null && aliasName.length()>0) {
                            aliasName= aliasName.trim();
                            if(aliasName.length()>20)
                                aliasName=aliasName.substring(0,20);
                            Log.i(TAG, "Found alias name!! " + aliasName);

                            if(tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING ) {
                                Log.i(TAG, "alias tx is pending");
                                walletObservable.setUnconfirmedAliasName(aliasName);
                            } else {
                                walletObservable.setUnconfirmedAliasName(null);
                                walletObservable.setAliasName(aliasName);
                                Log.i(TAG, "alias tx is confirmed!");
                            }
                            walletObservable.notifyObservers();

                            return aliasName;
                        }
                    }
                }
            }
        }

        return null;
    }

    public BlockStore getBlockStore() {
        return blockStore;
    }

    public StoredBlock getChainHead() {
        return chainHead;
    }

    public PeerGroup getPeerGroup() {
        return peerGroup;
    }

    public BlockChain getBlockChain() {
        return blockChain;
    }

    public Wallet getWallet() {
        return wallet;
    }

    public void stopSync() {
        Log.i(TAG, "stopSync");

        if (peerGroup!=null)
            if (peerGroup.isRunning())
                peerGroup.stop();
        if (wallet!=null)
            wallet.shutdownAutosaveAndWait();
        running=false;
    }


    public void stopAndDelete() {
        if (blockChain!=null) {
            blockChain.removeListener(chainListener);
            blockChain.removeWallet(wallet);
        }
        if (peerGroup!=null) {
            if (peerGroup.isRunning())
                peerGroup.stop();
            peerGroup.removeWallet(wallet);
        }
        if (wallet!=null) {
            wallet.cleanup();
            wallet.reset();
            wallet.clearTransactions(0);
        }
        if (walletObservable!=null) {
            walletObservable.reset();
            walletObservable.notifyObservers();
        }

        // Clear global variables
        if(all!=null)
            all.clear();
        if (messagesId!=null)
            messagesId.clear();
        if(changes!=null)
            changes.clear();
        ewDerivation=null;
        used     = new HashSet<>();
        nextMessageId = 0 ;
        nextChange    = 0 ;

        // Clear files
        final Context context = getApplicationContext();
        File path;
        if(context!=null)
            path= context.getDir(BITCOIN_PATH, Context.MODE_PRIVATE);
        else
            path = new File("./data/");
        final File blockFile = new File(path, BLOCKCHAIN_FILE );
        final File walletFile = new File(path, WALLET_FILE );
        boolean blockDeleted  = blockFile.delete();
        boolean walletDeleted =  walletFile.delete();
        Log.i(TAG, "blockDeleted: "  + blockDeleted);
        Log.i(TAG, "walletDeleted: " + walletDeleted);

        try {
            blockDeleted  = blockFile.getCanonicalFile().delete();
            walletDeleted =  walletFile.getCanonicalFile().delete();
            Log.i(TAG, "blockDeleted: "+ blockDeleted+" - length: "+ blockFile.getCanonicalFile().length() );
            Log.i(TAG, "walletDeleted: " + walletDeleted+" - length: "+ walletFile.getCanonicalFile().length() );
        } catch (IOException e) {
            e.printStackTrace();
        }
        running=false;
    }

    public void removePasshrase() {
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        sharedPref.edit().remove(Preferences.PASSPHRASE).remove(Preferences.PIN).remove(Preferences.TO_NOTIFY).remove(Preferences.ALIAS_NAME).commit();
    }

} public class NotEnoughAddressException extends Exception {

} public class NotSyncedException extends Exception {
    public NotSyncedException(String msg) {
        super(msg);
    }
} public class WalletObservable extends Observable {
    private Coin walletBalance = Coin.ZERO;
    private Coin walletUnconfirmedBalance = Coin.ZERO;
    private State state= State.NOT_STARTED;
    private Address current;
    private Integer percSync;
    private String alias;
    private String aliasName;
    private String unconfirmedAliasName;
    private Bitmap currentQrCode;
    private Bitmap currentIdenticon;
    private Integer messagePending=0;

    private String currentQrCodeSource;     //which data has been used to create the qrcode bitmap
    private String currentIdenticonSource;  //which data has been used to create the identicon bitmap

    public boolean isSyncedOrPending() {
        return isSynced() || isPending();
    }

    public boolean isSynced() {
        return state==State.SYNCED ;
    }

    public boolean isPending() {
        return state==State.PENDING ;
    }

    public enum State
    {
        NOT_STARTED, STARTED, NULL_PASSPHRASE, SYNCING, DOWNLOADED, SYNCED, PENDING
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        if(!alias.equals(this.alias)) {
            this.alias = alias;
            setChanged();
        }
    }

    public Integer getMessagePending() {
        return messagePending;
    }

    public void setMessagePending(Integer messagePending) {
        if( messagePending!=null && !messagePending.equals(this.messagePending) ) {
            this.messagePending = messagePending;
            setChanged();
        }
    }

    public String getAliasName() {
        return aliasName;
    }

    public void setAliasName(String aliasName) {
        if(!aliasName.equals(this.aliasName)) {
            this.aliasName = aliasName;
            setChanged();
        }
    }

    public String getUnconfirmedAliasName() {
        return unconfirmedAliasName;
    }

    public void setUnconfirmedAliasName(String unconfirmedAliasName) {
        if (unconfirmedAliasName==null && this.unconfirmedAliasName!=null) {
            this.unconfirmedAliasName = null;
            setChanged();
        } else if (unconfirmedAliasName!=null && !unconfirmedAliasName.equals(this.unconfirmedAliasName)) {
            this.unconfirmedAliasName = unconfirmedAliasName;
            setChanged();
        }
    }

    public Address getCurrent() {
        return current;
    }

    public void setCurrent(Address current) {
        if(!current.equals(this.current)) {
            this.current = current;
            setChanged();
        }
    }

    public Bitmap getCurrentIdenticon() {
        return currentIdenticon;
    }

    public void setCurrentIdenticon(Bitmap currentIdenticon) {
        if(!currentIdenticon.equals(this.currentIdenticon)) {
            this.currentIdenticon = currentIdenticon;
            setChanged();
        }

    }

    public String getCurrentIdenticonSource() {
        return currentIdenticonSource;
    }

    public void setCurrentIdenticonSource(String currentIdenticonSource) {
        if(!currentIdenticonSource.equals(this.currentIdenticonSource)) {
            this.currentIdenticonSource = currentIdenticonSource;
            setChanged();
        }
    }

    public Bitmap getCurrentQrCode() {
        return currentQrCode;
    }

    public void setCurrentQrCode(Bitmap currentQrCode) {
        if(!currentQrCode.equals(this.currentQrCode)) {
            this.currentQrCode = currentQrCode;
            setChanged();
        }
    }

    public String getCurrentQrCodeSource() {
        return currentQrCodeSource;
    }

    public void setCurrentQrCodeSource(String currentQrCodeSource) {
        if(!currentQrCodeSource.equals(this.currentQrCodeSource)) {
            this.currentQrCodeSource = currentQrCodeSource;
            setChanged();
        }
    }

    public Integer getPercSync() {
        return percSync;
    }

    public void setPercSync(Integer percSync) {
        if(!percSync.equals(this.percSync)) {
            this.percSync = percSync;
            setChanged();
        }
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        if(!state.equals(this.state)) {
            this.state = state;
            setChanged();
        }
    }

    public Coin getWalletBalance() {
        return walletBalance;
    }

    public void setWalletBalance(Coin walletBalance) {
        if(!walletBalance.equals(this.walletBalance) ) {
            this.walletBalance = walletBalance;
            setChanged();
        }
    }

    public Coin getWalletUnconfirmedBalance() {
        return walletUnconfirmedBalance;
    }

    public void setWalletUnconfirmedBalance(Coin walletUnconfirmedBalance) {
        if(!walletUnconfirmedBalance.equals(this.walletUnconfirmedBalance) ) {
            this.walletUnconfirmedBalance = walletUnconfirmedBalance;
            setChanged();
        }
    }

    public void reset() {
        walletUnconfirmedBalance=Coin.ZERO;
        walletBalance=Coin.ZERO;
        current=null;
        percSync=null;
        alias=null;
        aliasName=null;
        unconfirmedAliasName=null;
        currentQrCode=null;
        currentIdenticon=null;
        currentIdenticonSource=null;
        currentQrCodeSource=null;
        messagePending=0;
        state=WalletObservable.State.NOT_STARTED;
        setChanged();
    }

    @Override
    public String toString() {
        return "WalletObservable{" +
                "alias='" + alias + '\'' +
                ", walletBalance=" + walletBalance +
                ", walletUnconfirmedBalance=" + walletUnconfirmedBalance +
                ", state=" + state +
                ", current=" + current +
                ", messagePending=" + messagePending +
                ", percSync=" + percSync +
                ", aliasName='" + aliasName + '\'' +
                ", unconfirmedAliasName='" + unconfirmedAliasName + '\'' +
                ", currentQrCodeSource='" + currentQrCodeSource + '\'' +
                ", currentIdenticonSource='" + currentIdenticonSource + '\'' +
                '}';
    }
}
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v7.app.ActionBarActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.base.Optional;
import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.fonts.FontAwesomeModule;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionBroadcast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import it.eternitywall.eternitywall.EWApplication;
import it.eternitywall.eternitywall.Http;
import it.eternitywall.eternitywall.Message;
import it.eternitywall.eternitywall.Preferences;
import it.eternitywall.eternitywall.R;
import it.eternitywall.eternitywall.components.MessageView;
import it.eternitywall.eternitywall.dialogfragments.PinAlertDialogFragment;
import it.eternitywall.eternitywall.wallet.EWWalletService;
import it.eternitywall.eternitywall.wallet.NotEnoughAddressException;
import it.eternitywall.eternitywall.wallet.NotSyncedException;
import it.eternitywall.eternitywall.wallet.WalletObservable;
package it.eternitywall.eternitywall.activity;







public class WriteActivity extends ActionBarActivity {

    private static final Integer MAX_LENGTH = 72;

    private static final String TAG = "WriteActivity";
    private static final int REQ_CODE = 100;
    private static String ANONYMOUS = "Anonymous";


    private EditText txtMessage;
    private TextView txtReplyto;
    private TextView txtCounter;
    private ProgressBar progress;
    private Spinner spnrSender;
    private LinearLayout lytSender;

    private String curmsg = "";
    private Button btnSend;

    private String address=null;
    private String replyFrom=null;
    private String aliasName;


    private MessageView answerMessageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_write);
        Iconify.with(new FontAwesomeModule());

        txtMessage = (EditText) findViewById(R.id.etMessage);
        txtCounter = (TextView) findViewById(R.id.txtCounter);
        txtReplyto = (TextView) findViewById(R.id.txtReplyto);
        if(getIntent().getExtras() != null && getIntent().getStringExtra("sharedText") != null) {
            txtMessage.setText(getIntent().getStringExtra("sharedText"));
            curmsg = txtMessage.getText().toString();
            if(calcRemainingBytes() < 0) {
                txtMessage.setText(curmsg);
                txtMessage.setSelection(curmsg.length());
                txtCounter.setText(Math.abs(calcRemainingBytes()) +" "+"characters exceeds");
            }else {
                txtCounter.setText(calcRemainingBytes() + " " + "characters available");
            }
        }
        if(getIntent().getExtras() != null && getIntent().getExtras().getString(android.content.Intent.EXTRA_TEXT) != null) {
            txtMessage.setText(getIntent().getExtras().getString(android.content.Intent.EXTRA_TEXT));
            curmsg = txtMessage.getText().toString();
            if(calcRemainingBytes() < 0) {
                txtMessage.setText(curmsg);
                txtMessage.setSelection(curmsg.length());
                txtCounter.setText(Math.abs(calcRemainingBytes()) +" "+"characters exceeds");
            }else {
                txtCounter.setText(calcRemainingBytes() + " " + "characters available");
            }
        }


        // message view component for answer message
        answerMessageView = (MessageView)findViewById(R.id.answerMessageView);
        if(getIntent().getExtras() != null && getIntent().getStringExtra("replyFrom") != null) {
            replyFrom=getIntent().getStringExtra("replyFrom");
            txtReplyto.setVisibility(View.GONE);
            String hashTransaction=getIntent().getStringExtra("hashTransaction");
            loadMessage(hashTransaction);
            // answer message
            curmsg = txtMessage.getText().toString();
            if(calcRemainingBytes() < 0) {
                txtMessage.setText(curmsg);
                txtMessage.setSelection(curmsg.length());
                txtCounter.setText(Math.abs(calcRemainingBytes()) +" "+"characters exceeds");
            }else {
                txtCounter.setText(calcRemainingBytes() + " " + "characters available");
            }
        }

        spnrSender = (Spinner) findViewById(R.id.spnrSender);
        lytSender = (LinearLayout) findViewById(R.id.lytSender);

        final WalletObservable walletObservable = ((EWApplication) getApplication()).getWalletObservable();