diff --git a/app/app.iml b/app/app.iml index 25bc19b..b6bdf79 100644 --- a/app/app.iml +++ b/app/app.iml @@ -89,7 +89,6 @@ - diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index 336c827..9d92711 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -58,6 +58,10 @@ public class WalletActivity extends AppCompatActivity protected void onStart() { super.onStart(); Log.d(TAG, "onStart()"); + this.synced = false; // init syncing logic + } + + private void startWalletService() { acquireWakeLock(); Bundle extras = getIntent().getExtras(); if (extras != null) { @@ -67,6 +71,8 @@ public class WalletActivity extends AppCompatActivity } else { throw new IllegalStateException("No extras passed! Panic!"); } + + onProgress(getString(R.string.status_wallet_loading)); showProgress(); final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @@ -75,7 +81,11 @@ public class WalletActivity extends AppCompatActivity onProgress(10); // look like we are working! } }, 250); - //Log.d(TAG, "onStart() done."); + } + + private void stopWalletService() { + releaseWakeLock(); + disconnectWalletService(); } private String title = null; @@ -95,15 +105,13 @@ public class WalletActivity extends AppCompatActivity @Override protected void onStop() { Log.d(TAG, "onStop()"); - releaseWakeLock(); - disconnectWalletService(); - this.synced = false; super.onStop(); } @Override protected void onDestroy() { Log.d(TAG, "onDestroy()"); + stopWalletService(); super.onDestroy(); } @@ -129,6 +137,7 @@ public class WalletActivity extends AppCompatActivity recyclerView.setAdapter(adapter); setTitle(getString(R.string.status_wallet_loading)); + startWalletService(); //Log.d(TAG, "onCreate() done."); } @@ -136,33 +145,34 @@ public class WalletActivity extends AppCompatActivity private boolean synced = false; private void updateStatus(Wallet wallet) { + Log.d(TAG, "updateStatus()"); setActivityTitle(wallet); final TextView balanceView = (TextView) findViewById(R.id.tvBalance); final TextView unlockedView = (TextView) findViewById(R.id.tvUnlockedBalance); final TextView syncProgressView = (TextView) findViewById(R.id.tvBlockHeightProgress); final TextView connectionStatusView = (TextView) findViewById(R.id.tvConnectionStatus); - - //Wallet wallet = getWallet(); balanceView.setText(Wallet.getDisplayAmount(wallet.getBalance())); unlockedView.setText(Wallet.getDisplayAmount(wallet.getUnlockedBalance())); String sync = ""; - // TODO: getConnectionStatus() blocks as it tries to connect - this is bad in the UI thread! - if (wallet.getConnectionStatus() == Wallet.ConnectionStatus.ConnectionStatus_Connected) { + if (mBoundService == null) throw new IllegalStateException("WalletService not bound."); + Wallet.ConnectionStatus daemonConnected = mBoundService.getConnectionStatus(); + if (daemonConnected == Wallet.ConnectionStatus.ConnectionStatus_Connected) { + long daemonHeight = mBoundService.getDaemonHeight(); if (!wallet.isSynchronized()) { - long n = wallet.getDaemonBlockChainHeight() - wallet.getBlockChainHeight(); + long n = daemonHeight - wallet.getBlockChainHeight(); sync = n + " " + getString(R.string.status_remaining); if (firstBlock == 0) { firstBlock = wallet.getBlockChainHeight(); } - int x = 100 - Math.round(100f * n / (1f * wallet.getDaemonBlockChainHeight() - firstBlock)); - //Log.d(TAG, n + "/" + (wallet.getDaemonBlockChainHeight() - firstBlock)); + int x = 100 - Math.round(100f * n / (1f * daemonHeight - firstBlock)); onProgress(getString(R.string.status_syncing) + " " + sync); + if (x == 0) x = -1; onProgress(x); } else { sync = getString(R.string.status_synced) + ": " + wallet.getBlockChainHeight(); if (!synced) { hideProgress(); - saveWallet(); // save ONLY on first sync + saveWallet(); // save on first sync // the usual use case is: // open the wallet, wait for sync, check balance, close app // even if we wait for new transactions, they will be synced and saved next time @@ -174,7 +184,7 @@ public class WalletActivity extends AppCompatActivity } String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet)); syncProgressView.setText(sync); - connectionStatusView.setText(net + " " + wallet.getConnectionStatus().toString().substring(17)); + connectionStatusView.setText(net + " " + daemonConnected.toString().substring(17)); } @Override @@ -364,7 +374,12 @@ public class WalletActivity extends AppCompatActivity runOnUiThread(new Runnable() { public void run() { ProgressBar progress = (ProgressBar) findViewById(R.id.pbProgress); - progress.setProgress(n); + if (n >= 0) { + progress.setIndeterminate(false); + progress.setProgress(n); + } else { + progress.setIndeterminate(true); + } } }); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java new file mode 100644 index 0000000..a214ded --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * Copyright (c) 2017 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.service; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Process; + + +/** + * Handy class for starting a new thread that has a looper. The looper can then be + * used to create handler classes. Note that start() must still be called. + * The started Thread has a stck size of STACK_SIZE (=3MB) + */ +public class MoneroHandlerThread extends Thread { + // from src/cryptonote_config.h + static public final long THREAD_STACK_SIZE = 5 * 1024 * 1024; + int mPriority; + int mTid = -1; + Looper mLooper; + + public MoneroHandlerThread(String name) { + super(null, null, name, THREAD_STACK_SIZE); + mPriority = Process.THREAD_PRIORITY_DEFAULT; + } + + /** + * Constructs a MoneroHandlerThread. + * + * @param name + * @param priority The priority to run the thread at. The value supplied must be from + * {@link android.os.Process} and not from java.lang.Thread. + */ + public MoneroHandlerThread(String name, int priority) { + super(null, null, name, THREAD_STACK_SIZE); + mPriority = priority; + } + + /** + * Call back method that can be explicitly overridden if needed to execute some + * setup before Looper loops. + */ + + protected void onLooperPrepared() { + } + + @Override + public void run() { + mTid = Process.myTid(); + Looper.prepare(); + synchronized (this) { + mLooper = Looper.myLooper(); + notifyAll(); + } + Process.setThreadPriority(mPriority); + onLooperPrepared(); + Looper.loop(); + mTid = -1; + } + + /** + * This method returns the Looper associated with this thread. If this thread not been started + * or for any reason is isAlive() returns false, this method will return null. If this thread + * has been started, this method will block until the looper has been initialized. + * + * @return The looper. + */ + public Looper getLooper() { + if (!isAlive()) { + return null; + } + + // If the thread has been started, wait until the looper has been created. + synchronized (this) { + while (isAlive() && mLooper == null) { + try { + wait(); + } catch (InterruptedException e) { + } + } + } + return mLooper; + } + + /** + * Quits the handler thread's looper. + *

+ * Causes the handler thread's looper to terminate without processing any + * more messages in the message queue. + *

+ * Any attempt to post messages to the queue after the looper is asked to quit will fail. + * For example, the {@link Handler#sendMessage(Message)} method will return false. + *

+ * Using this method may be unsafe because some messages may not be delivered + * before the looper terminates. Consider using {@link #quitSafely} instead to ensure + * that all pending work is completed in an orderly manner. + *

+ * + * @return True if the looper looper has been asked to quit or false if the + * thread had not yet started running. + * @see #quitSafely + */ + public boolean quit() { + Looper looper = getLooper(); + if (looper != null) { + looper.quit(); + return true; + } + return false; + } + + /** + * Quits the handler thread's looper safely. + *

+ * Causes the handler thread's looper to terminate as soon as all remaining messages + * in the message queue that are already due to be delivered have been handled. + * Pending delayed messages with due times in the future will not be delivered. + *

+ * Any attempt to post messages to the queue after the looper is asked to quit will fail. + * For example, the {@link Handler#sendMessage(Message)} method will return false. + *

+ * If the thread has not been started or has finished (that is if + * {@link #getLooper} returns null), then false is returned. + * Otherwise the looper is asked to quit and true is returned. + *

+ * + * @return True if the looper looper has been asked to quit or false if the + * thread had not yet started running. + */ + public boolean quitSafely() { + Looper looper = getLooper(); + if (looper != null) { + looper.quitSafely(); + return true; + } + return false; + } + + /** + * Returns the identifier of this thread. See Process.myTid(). + */ + public int getThreadId() { + return mTid; + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java index 4cdc4f0..7fd17d2 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java @@ -21,7 +21,6 @@ import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -29,6 +28,7 @@ import android.os.Process; import android.util.Log; import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.model.TransactionHistory; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletListener; import com.m2049r.xmrwallet.model.WalletManager; @@ -97,15 +97,27 @@ public class WalletService extends Service { } long lastBlockTime = 0; + int lastTxCount = 0; public void newBlock(long height) { if (wallet == null) throw new IllegalStateException("No wallet!"); // don't flood with an update for every block ... if (lastBlockTime < System.currentTimeMillis() - 2000) { - Log.d(TAG, "newBlock() @" + height + "with observer " + observer); + Log.d(TAG, "newBlock() @" + height + " with observer " + observer); lastBlockTime = System.currentTimeMillis(); if (observer != null) { - observer.onRefreshed(wallet, false); + boolean fullRefresh = false; + updateDaemonState(wallet, wallet.isSynchronized() ? height : 0); + if (!wallet.isSynchronized()) { + // we want to see our transactions as they come in + wallet.getHistory().refresh(); + int txCount = wallet.getHistory().getCount(); + if (txCount > lastTxCount) { + lastTxCount = txCount; + fullRefresh = true; + } + } + observer.onRefreshed(wallet, fullRefresh); } } } @@ -118,17 +130,61 @@ public class WalletService extends Service { public void refreshed() { if (wallet == null) throw new IllegalStateException("No wallet!"); - Log.d(TAG, "refreshed() " + wallet.getBalance() + " sync=" + wallet.isSynchronized() + "with observer " + observer); + Log.d(TAG, "refreshed() " + wallet.getName() + " " + wallet.getBalance() + " sync=" + wallet.isSynchronized() + " with observer " + observer); if (updated) { if (observer != null) { - wallet.getHistory().refresh(); + Log.d(TAG, "refreshed() A"); + updateDaemonState(wallet, 0); + Log.d(TAG, "refreshed() B"); + TransactionHistory history = wallet.getHistory(); + Log.d(TAG, "refreshed() C " + history.getCount()); + history.refresh(); + Log.d(TAG, "refreshed() D " + history.getCount()); + Log.d(TAG, "refreshed() E"); observer.onRefreshed(wallet, true); + Log.d(TAG, "refreshed() D"); updated = false; } } } } + private long lastDaemonStatusUpdate = 0; + private long daemonHeight = 0; + private Wallet.ConnectionStatus connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Disconnected; + private static final long STATUS_UPDATE_INTERVAL = 120000; // 120s (blocktime) + + private void updateDaemonState(Wallet wallet, long height) { + long t = System.currentTimeMillis(); + if (height > 0) { // if we get a height, we are connected + daemonHeight = height; + connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Connected; + lastDaemonStatusUpdate = t; + } else { + if (t - lastDaemonStatusUpdate > STATUS_UPDATE_INTERVAL) { + lastDaemonStatusUpdate = t; + // these calls really connect to the daemon - wasting time + daemonHeight = wallet.getDaemonBlockChainHeight(); + if (daemonHeight > 0) { + // if we get a valid height, the obviously we are connected + connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Connected; + } else { + // TODO: or connectionStatus = wallet.getConnectionStatus(); ? + connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Disconnected; + } + } + } + //Log.d(TAG, "updated daemon status: " + daemonHeight + "/" + connectionStatus.toString()); + } + + public long getDaemonHeight() { + return this.daemonHeight; + } + + public Wallet.ConnectionStatus getConnectionStatus() { + return this.connectionStatus; + } + ///////////////////////////////////////////// // communication back to client (activity) // ///////////////////////////////////////////// @@ -217,7 +273,7 @@ public class WalletService extends Service { // We are using a HandlerThread and a Looper to avoid loading and closing // concurrency - HandlerThread thread = new HandlerThread("WalletService", + MoneroHandlerThread thread = new MoneroHandlerThread("WalletService", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); @@ -294,14 +350,13 @@ public class WalletService extends Service { Wallet aWallet = loadWallet(walletName, walletPassword); listener = new MyWalletListener(aWallet); listener.start(); - showProgress(95); + showProgress(100); } + showProgress(getString(R.string.status_wallet_connecting)); + showProgress(-1); + // if we try to refresh the history here we get occasional segfaults! + // doesnt matter since we update as soon as we get a new block anyway Log.d(TAG, "start() done"); - if (observer != null) { - Wallet myWallet = getWallet(); - myWallet.getHistory().refresh(); - observer.onRefreshed(myWallet, true); - } } public void stop() { @@ -309,8 +364,13 @@ public class WalletService extends Service { setObserver(null); // in case it was not reset already if (listener != null) { listener.stop(); + Wallet myWallet = getWallet(); +// if (!myWallet.isSynchronized()) { // save only if NOT synced (to continue later) +// Log.d(TAG, "stop() saving"); +// myWallet.store(); +// } Log.d(TAG, "stop() closing"); - listener.getWallet().close(); + myWallet.close(); Log.d(TAG, "stop() closed"); listener = null; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ad67e4..e616d69 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,11 +11,12 @@ Loading Wallet List Loading Wallet … Saving Wallet + Connecting … Password for Bad password! Daemon address must be set! Daemon type does not fit to wallet! - Warning: cannot reach daemon! + Cannot connect to daemon! Something\'s wrong! Amount diff --git a/external-libs/monero/lib/armeabi-v7a/libblockchain_db.a b/external-libs/monero/lib/armeabi-v7a/libblockchain_db.a index 875ed7d..a3c17e0 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libblockchain_db.a and b/external-libs/monero/lib/armeabi-v7a/libblockchain_db.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libblocks.a b/external-libs/monero/lib/armeabi-v7a/libblocks.a index 62a86e4..4f2accf 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libblocks.a and b/external-libs/monero/lib/armeabi-v7a/libblocks.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libcncrypto.a b/external-libs/monero/lib/armeabi-v7a/libcncrypto.a index bf59079..3b9769b 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libcncrypto.a and b/external-libs/monero/lib/armeabi-v7a/libcncrypto.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libcommon.a b/external-libs/monero/lib/armeabi-v7a/libcommon.a index 009d6d3..33720a0 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libcommon.a and b/external-libs/monero/lib/armeabi-v7a/libcommon.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libcryptonote_basic.a b/external-libs/monero/lib/armeabi-v7a/libcryptonote_basic.a index d578cc9..8d0e4fe 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libcryptonote_basic.a and b/external-libs/monero/lib/armeabi-v7a/libcryptonote_basic.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libcryptonote_core.a b/external-libs/monero/lib/armeabi-v7a/libcryptonote_core.a index e462875..92208c7 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libcryptonote_core.a and b/external-libs/monero/lib/armeabi-v7a/libcryptonote_core.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libcryptonote_protocol.a b/external-libs/monero/lib/armeabi-v7a/libcryptonote_protocol.a index 496c699..5d31a89 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libcryptonote_protocol.a and b/external-libs/monero/lib/armeabi-v7a/libcryptonote_protocol.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libdaemonizer.a b/external-libs/monero/lib/armeabi-v7a/libdaemonizer.a index 06db80a..568d67b 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libdaemonizer.a and b/external-libs/monero/lib/armeabi-v7a/libdaemonizer.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libeasylogging.a b/external-libs/monero/lib/armeabi-v7a/libeasylogging.a deleted file mode 100644 index 028a12c..0000000 Binary files a/external-libs/monero/lib/armeabi-v7a/libeasylogging.a and /dev/null differ diff --git a/external-libs/monero/lib/armeabi-v7a/libepee.a b/external-libs/monero/lib/armeabi-v7a/libepee.a index c24969f..40dd849 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libepee.a and b/external-libs/monero/lib/armeabi-v7a/libepee.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/liblmdb.a b/external-libs/monero/lib/armeabi-v7a/liblmdb.a index 18b69c3..dd38c1e 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/liblmdb.a and b/external-libs/monero/lib/armeabi-v7a/liblmdb.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libminiupnpc.a b/external-libs/monero/lib/armeabi-v7a/libminiupnpc.a index 341c329..dba8084 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libminiupnpc.a and b/external-libs/monero/lib/armeabi-v7a/libminiupnpc.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libmnemonics.a b/external-libs/monero/lib/armeabi-v7a/libmnemonics.a index 4589a99..6df688b 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libmnemonics.a and b/external-libs/monero/lib/armeabi-v7a/libmnemonics.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libp2p.a b/external-libs/monero/lib/armeabi-v7a/libp2p.a index e1ea1c4..6076496 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libp2p.a and b/external-libs/monero/lib/armeabi-v7a/libp2p.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libringct.a b/external-libs/monero/lib/armeabi-v7a/libringct.a index 6bc6602..21d235b 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libringct.a and b/external-libs/monero/lib/armeabi-v7a/libringct.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/librpc.a b/external-libs/monero/lib/armeabi-v7a/librpc.a index 71025e4..8136509 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/librpc.a and b/external-libs/monero/lib/armeabi-v7a/librpc.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libunbound.a b/external-libs/monero/lib/armeabi-v7a/libunbound.a index 0a1f8c9..a2ff8df 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libunbound.a and b/external-libs/monero/lib/armeabi-v7a/libunbound.a differ diff --git a/external-libs/monero/lib/armeabi-v7a/libwallet.a b/external-libs/monero/lib/armeabi-v7a/libwallet.a index 715b96e..2959da8 100644 Binary files a/external-libs/monero/lib/armeabi-v7a/libwallet.a and b/external-libs/monero/lib/armeabi-v7a/libwallet.a differ