Migrate to the new graphenej version.

- Migrated the whole app to the new graphenej version, which removes the usage of the Android Service, enabling to aim for a better and simpler architecture.
- Did a good number of modifications to keep the same or better functionality in all parts of the app that use the NetworkService.
This commit is contained in:
Severiano Jaramillo 2019-12-19 16:37:45 -06:00
parent b5411fa7fc
commit 766d42386a
9 changed files with 148 additions and 167 deletions

View file

@ -1,14 +1,9 @@
package cy.agorise.bitsybitshareswallet.activities
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.os.AsyncTask
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.preference.PreferenceManager
import android.util.Log
import android.widget.Toast
@ -56,7 +51,7 @@ import kotlin.concurrent.thread
* This class manages everything related to keeping the information in the database updated using graphenej's
* NetworkService, leaving to MainActivity only the Navigation work and some other UI features.
*/
abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
abstract class ConnectedActivity : AppCompatActivity() {
companion object {
private const val TAG = "ConnectedActivity"
@ -96,7 +91,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
private var missingAssets = ArrayList<Asset>()
/* Network service connection */
protected var mNetworkService: NetworkService? = null
protected var mNetworkService: NetworkService? = NetworkService.getInstance()
// Map used to keep track of request and response id pairs
private val responseMap = HashMap<Long, Int>()
@ -110,11 +105,6 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
// to resolve an equivalent BTS value
var transfer: Transfer? = null
/**
* Flag used to keep track of the NetworkService binding state
*/
private var mShouldUnbindNetwork: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -227,8 +217,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
if (message.error == null) {
if (responseMap.containsKey(message.id)) {
val responseType = responseMap[message.id]
when (responseType) {
when (responseMap[message.id]) {
RESPONSE_GET_FULL_ACCOUNTS ->
handleAccountDetails((message.result as List<*>)[0] as FullAccountDetails)
@ -522,23 +511,9 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
}
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
val binder = service as NetworkService.LocalBinder
mNetworkService = binder.service
}
override fun onServiceDisconnected(name: ComponentName?) { }
override fun onResume() {
super.onResume()
val intent = Intent(this, NetworkService::class.java)
if (bindService(intent, this, Context.BIND_AUTO_CREATE)) {
mShouldUnbindNetwork = true
} else {
Log.e(TAG, "Binding to the network service failed.")
}
mHandler.postDelayed(mCheckMissingPaymentsTask, Constants.MISSING_PAYMENT_CHECK_PERIOD)
mHandler.postDelayed(verifyConnectionToSuitableNodeTask, NODE_CHECK_DELAY)
}
@ -549,12 +524,6 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
mConnectedActivityViewModel.updateNodeLatencies(nodes as List<FullNode>)
}
// Unbinding from network service
if (mShouldUnbindNetwork) {
unbindService(this)
mShouldUnbindNetwork = false
mNetworkService = null
}
mHandler.removeCallbacks(mCheckMissingPaymentsTask)
mHandler.removeCallbacks(mRequestMissingUserAccountsTask)
mHandler.removeCallbacks(mRequestMissingAssetsTask)

View file

@ -1,11 +1,6 @@
package cy.agorise.bitsybitshareswallet.fragments
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.View
import androidx.core.os.ConfigurationCompat
@ -23,17 +18,14 @@ import io.reactivex.disposables.CompositeDisposable
* Base fragment that defines the methods and variables commonly used in all fragments that directly connect and
* talk to the BitShares nodes through graphenej's NetworkService
*/
abstract class ConnectedFragment : Fragment(), ServiceConnection {
abstract class ConnectedFragment : Fragment() {
companion object {
private const val TAG = "ConnectedFragment"
}
/** Network service connection */
protected var mNetworkService: NetworkService? = null
/** Flag used to keep track of the NetworkService binding state */
private var mShouldUnbindNetwork: Boolean = false
protected var mNetworkService: NetworkService? = NetworkService.getInstance()
/** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */
protected var mDisposables = CompositeDisposable()
@ -66,41 +58,12 @@ abstract class ConnectedFragment : Fragment(), ServiceConnection {
}
}
override fun onResume() {
super.onResume()
val intent = Intent(context, NetworkService::class.java)
if (context?.bindService(intent, this, Context.BIND_AUTO_CREATE) == true) {
mShouldUnbindNetwork = true
} else {
Log.e(TAG, "Binding to the network service failed.")
}
}
override fun onPause() {
super.onPause()
// Unbinding from network service
if (mShouldUnbindNetwork) {
context?.unbindService(this)
mShouldUnbindNetwork = false
}
}
override fun onDestroy() {
super.onDestroy()
if (!mDisposables.isDisposed) mDisposables.dispose()
}
override fun onServiceDisconnected(name: ComponentName?) { }
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
val binder = service as NetworkService.LocalBinder
mNetworkService = binder.service
}
/**
* Method to be implemented by all subclasses in order to be notified of JSON-RPC responses.
* @param response

View file

@ -1,9 +1,7 @@
package cy.agorise.bitsybitshareswallet.fragments
import android.content.ComponentName
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.util.Log
import android.view.LayoutInflater
import android.view.View
@ -35,7 +33,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.main.fragment_import_brainkey.*
import org.bitcoinj.core.ECKey
import java.text.NumberFormat
import java.util.ArrayList
import java.util.*
import java.util.concurrent.TimeUnit
class ImportBrainkeyFragment : BaseAccountFragment() {
@ -249,10 +247,10 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
if (Character.isUpperCase(brainKey.toCharArray()[brainKey.length - 1])) {
// If the last character is an uppercase, we assume the whole brainkey
// was given in capital letters and turn it to lowercase
getAccountFromBrainkey(brainKey.toLowerCase())
getAccountFromBrainkey(brainKey.toLowerCase(Locale.ROOT))
} else {
// Otherwise we turn the whole brainkey to capital letters
getAccountFromBrainkey(brainKey.toUpperCase())
getAccountFromBrainkey(brainKey.toUpperCase(Locale.ROOT))
}
} else {
// If no case switching should take place, we perform the network call with
@ -274,6 +272,15 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
keyReferencesRequestId = mNetworkService?.sendMessage(GetKeyReferences(address), GetKeyReferences.REQUIRED_API)
}
override fun onStart() {
super.onStart()
if (mNetworkService?.isConnected == true)
showConnectedState()
else
showDisconnectedState()
}
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
if (response.id == keyReferencesRequestId) {
handleBrainKeyAccountReferences(response.result)
@ -289,7 +296,24 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
}
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {
Log.d(TAG, "handleConnectionStatusUpdate. code: " + connectionStatusUpdate.updateCode)
when (connectionStatusUpdate.updateCode) {
ConnectionStatusUpdate.CONNECTED -> {
showConnectedState()
}
ConnectionStatusUpdate.DISCONNECTED -> {
showDisconnectedState()
}
}
}
private fun showConnectedState() {
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
resources.getDrawable(R.drawable.ic_connected, null), null)
}
private fun showDisconnectedState() {
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
resources.getDrawable(R.drawable.ic_disconnected, null), null)
}
/**
@ -385,18 +409,4 @@ class ImportBrainkeyFragment : BaseAccountFragment() {
mHandler.postDelayed(this, Constants.BLOCK_PERIOD)
}
}
override fun onServiceDisconnected(name: ComponentName?) {
super.onServiceDisconnected(name)
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
resources.getDrawable(R.drawable.ic_disconnected, null), null)
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
super.onServiceConnected(name, service)
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
resources.getDrawable(R.drawable.ic_connected, null), null)
}
}

View file

@ -3,7 +3,6 @@ package cy.agorise.bitsybitshareswallet.fragments
import android.content.*
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.preference.PreferenceManager
import android.util.Log
import android.view.LayoutInflater
@ -190,6 +189,15 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
}
}
override fun onStart() {
super.onStart()
if (mNetworkService?.isConnected == true)
showConnectedState()
else
showDisconnectedState()
}
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
if (responseMap.containsKey(response.id)) {
when (responseMap[response.id]) {
@ -201,7 +209,26 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
}
}
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) { }
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {
when (connectionStatusUpdate.updateCode) {
ConnectionStatusUpdate.CONNECTED -> {
showConnectedState()
}
ConnectionStatusUpdate.DISCONNECTED -> {
showDisconnectedState()
}
}
}
private fun showConnectedState() {
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
resources.getDrawable(R.drawable.ic_connected, null), null)
}
private fun showDisconnectedState() {
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
resources.getDrawable(R.drawable.ic_disconnected, null), null)
}
/** Handles the result of the [GetDynamicGlobalProperties] api call to obtain the current block number and update
* it in the Nodes Dialog */
@ -531,19 +558,5 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
activity?.finish()
activity?.startActivity(intent)
}
override fun onServiceDisconnected(name: ComponentName?) {
super.onServiceDisconnected(name)
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
resources.getDrawable(R.drawable.ic_disconnected, null), null)
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
super.onServiceConnected(name, service)
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
resources.getDrawable(R.drawable.ic_connected, null), null)
}
}

View file

@ -0,0 +1,72 @@
package cy.agorise.bitsybitshareswallet.network
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
import android.os.Handler
import cy.agorise.graphenej.api.android.NetworkService
import cy.agorise.graphenej.stats.ExponentialMovingAverage
/**
* Class used to manage the connection status of the NetworkService.
*
*
* The basic idea here is to keep track of the sequence of activity life cycle callbacks so that we
* can infer when the user has left the app and the node connection can be salfely shut down.
*/
class NetworkServiceManager(nodes: List<String>) :
ActivityLifecycleCallbacks {
/**
* Handler instance used to schedule tasks back to the main thread
*/
private val mHandler = Handler()
private var mNetworkService: NetworkService? = null
private val mNodeUrls: Array<String> = nodes.toTypedArray()
/**
* Runnable used to schedule a service disconnection once the app is not visible to the user for
* more than DISCONNECT_DELAY milliseconds.
*/
private val mDisconnectRunnable = Runnable {
if (mNetworkService != null) {
mNetworkService?.stop()
mNetworkService = null
}
}
override fun onActivityCreated(activity: Activity, bundle: Bundle) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity?) {
mHandler.removeCallbacks(mDisconnectRunnable)
if (mNetworkService == null) {
mNetworkService = NetworkService.getInstance()
mNetworkService?.start(mNodeUrls, ExponentialMovingAverage.DEFAULT_ALPHA)
}
}
override fun onActivityPaused(activity: Activity) {
mHandler.postDelayed(
mDisconnectRunnable,
DISCONNECT_DELAY.toLong()
)
}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(
activity: Activity,
bundle: Bundle
) {
}
override fun onActivityDestroyed(activity: Activity) {}
companion object {
/**
* Constant used to specify how long will the app wait for another activity to go through its starting life
* cycle events before running the teardownConnectionTask task.
*
* This is used as a means to detect whether or not the user has left the app.
*/
private const val DISCONNECT_DELAY = 1500
}
}

View file

@ -1,10 +1,6 @@
package cy.agorise.bitsybitshareswallet.processors
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.preference.PreferenceManager
import android.util.Log
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
@ -29,6 +25,7 @@ import org.bitcoinj.core.DumpedPrivateKey
import org.bitcoinj.core.ECKey
import java.util.*
import javax.crypto.AEADBadTagException
import kotlin.math.floor
/**
* This class is responsible for loading the local database with all past transfer operations of the
@ -66,33 +63,14 @@ class TransfersLoader(private var mContext: Context?) {
private var authorityRepository: AuthorityRepository? = null
/* Network service connection */
private var mNetworkService: NetworkService? = null
private var mNetworkService: NetworkService? = NetworkService.getInstance()
/* Counter used to keep track of the transfer history batch count */
private var historicalTransferCount = 0
/** Flag used to keep track of the NetworkService binding state */
private var mBound: Boolean = false
// Map used to keep track of request and response id pairs
private val responseMap = HashMap<Long, Int>()
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
val binder = service as NetworkService.LocalBinder
mNetworkService = binder.service
mBound = true
// Start the transfers update
startTransfersUpdateProcedure()
}
override fun onServiceDisconnected(name: ComponentName?) {
mBound = false
}
}
init {
transferRepository = TransferRepository(mContext!!)
authorityRepository = AuthorityRepository(mContext!!)
@ -138,14 +116,8 @@ class TransfersLoader(private var mContext: Context?) {
}
)
onStart()
}
}
private fun onStart() {
// Bind to LocalService
Intent(mContext, NetworkService::class.java).also { intent ->
mContext?.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
// Start the transfers update
startTransfersUpdateProcedure()
}
}
@ -165,7 +137,7 @@ class TransfersLoader(private var mContext: Context?) {
if (transferCount > 0) {
// If we already have some transfers in the database, we might want to skip the request
// straight to the last batch
historicalTransferCount = Math.floor((transferCount /
historicalTransferCount = floor((transferCount /
HISTORICAL_TRANSFER_BATCH_SIZE).toDouble()).toInt()
}
// Retrieving account transactions
@ -257,10 +229,10 @@ class TransfersLoader(private var mContext: Context?) {
val memo = op.memo
if (memo.byteMessage != null) {
try {
if (memo.destination.equals(myAddress)) {
if (memo.destination == myAddress) {
val decryptedMessage = Memo.decryptMessage(memoKey, memo.source, memo.nonce, memo.byteMessage)
memo.plaintextMessage = decryptedMessage
}else if(memo.source.equals(myAddress)){
}else if(memo.source == myAddress){
val decryptedMessage = Memo.decryptMessage(memoKey, memo.destination, memo.nonce, memo.byteMessage)
memo.plaintextMessage = decryptedMessage
}
@ -271,7 +243,7 @@ class TransfersLoader(private var mContext: Context?) {
Log.e(TAG, "NullPointerException. Msg: " + e.message)
} catch (e: Exception) {
Log.e(TAG, "Exception while decoding memo. Msg: " + e.message)
Log.e(TAG,"Exception type: " + e)
Log.e(TAG, "Exception type: $e")
for(element in e.stackTrace){
Log.e(TAG, String.format("%s#%s:%d", element.className, element.methodName, element.lineNumber))
}
@ -300,13 +272,6 @@ class TransfersLoader(private var mContext: Context?) {
Log.d(TAG, "Destroying TransfersLoader")
if (!mDisposables.isDisposed) mDisposables.dispose()
try {
if (mBound && mNetworkService != null)
mContext?.unbindService(mConnection)
} catch (e: IllegalArgumentException) {
Log.d(TAG, "Avoid crash related to Service not registered: ${e.message}")
}
mBound = false
mContext = null
mNetworkService = null
}

View file

@ -50,7 +50,6 @@ class NodeRepository(private val nodeDao: NodeDao) {
suspend fun getFormattedNodes(): Pair<String, Boolean> {
val nodes = nodeDao.getSortedNodes()
// TODO verify if this is the best way to fire and forget launch a coroutine inside another coroutine
// Launches a job to refresh the list of nodes into the database, without blocking the
// execution of this function, so that the formatted nodes can be returned immediately
// without waiting until the nodes have been updated in the database.

View file

@ -3,9 +3,8 @@ package cy.agorise.bitsybitshareswallet.utils
import android.app.Application
import com.crashlytics.android.Crashlytics
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
import cy.agorise.bitsybitshareswallet.network.NetworkServiceManager
import cy.agorise.bitsybitshareswallet.repositories.NodeRepository
import cy.agorise.graphenej.api.ApiAccess
import cy.agorise.graphenej.api.android.NetworkServiceManager
import io.reactivex.plugins.RxJavaPlugins
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -46,18 +45,9 @@ class BitsyApplication : Application() {
}
private suspend fun startNetworkServiceConnection() {
// Specifying some important information regarding the connection, such as the
// credentials and the requested API accesses
val requestedApis = ApiAccess.API_DATABASE or ApiAccess.API_HISTORY or ApiAccess.API_NETWORK_BROADCAST
val (nodes, autoConnect) = mNodeRepository.getFormattedNodes()
val networkManager = NetworkServiceManager.Builder()
.setUserName("")
.setPassword("")
.setRequestedApis(requestedApis)
.setCustomNodeUrls(nodes)
.setAutoConnect(autoConnect)
.setNodeLatencyVerification(true)
.build(this)
val networkManager = NetworkServiceManager(nodes.split(","))
/*
* Registering this class as a listener to all activity's callback cycle events, in order to
* better estimate when the user has left the app and it is safe to disconnect the websocket connection

@ -1 +1 @@
Subproject commit 1129a92aa3e4d1ceeac6826fcfe154bf909930fe
Subproject commit 3d5a57f8961c76fc2cadb327c86374d30c6692b7