kotlinify parts of status module, package & Utils (#19408)

parent issue: https://github.com/status-im/status-mobile/issues/18310

This commit converts the following `Java` files to `Kotlin` :

- modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java
- modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusPackage.java
- modules/react-native-status/android/src/main/java/im/status/ethereum/module/Utils.java

This commit converts Java code to `Kotlin` which involves a helper function that is used to execute `statusgo` methods.
It may impact everything or nothing.

- Android
This commit is contained in:
Siddarth Kumar 2024-04-08 22:46:01 +05:30 committed by GitHub
parent 8e31dae33b
commit 55a182f430
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 304 additions and 365 deletions

View File

@ -210,18 +210,14 @@ public class AccountManager extends ReactContextBaseJavaModule {
@ReactMethod
public void saveAccountAndLoginWithKeycard(final String multiaccountData, final String password, final String settings, final String config, final String accountsData, final String chatKey) {
try {
Log.d(TAG, "saveAccountAndLoginWithKeycard");
String finalConfig = prepareDirAndUpdateConfig(config, this.utils.getKeyUID(multiaccountData));
String result = Statusgo.saveAccountAndLoginWithKeycard(multiaccountData, password, settings, finalConfig, accountsData, chatKey);
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "saveAccountAndLoginWithKeycard result: " + result);
Log.d(TAG, "Geth node started");
} else {
Log.e(TAG, "saveAccountAndLoginWithKeycard failed: " + result);
}
} catch (JSONException e) {
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
Log.d(TAG, "saveAccountAndLoginWithKeycard");
String finalConfig = prepareDirAndUpdateConfig(config, this.utils.getKeyUID(multiaccountData));
String result = Statusgo.saveAccountAndLoginWithKeycard(multiaccountData, password, settings, finalConfig, accountsData, chatKey);
if (result.startsWith("{\"error\":\"\"")) {
Log.d(TAG, "saveAccountAndLoginWithKeycard result: " + result);
Log.d(TAG, "Geth node started");
} else {
Log.e(TAG, "saveAccountAndLoginWithKeycard failed: " + result);
}
}

View File

@ -1,147 +0,0 @@
package im.status.ethereum.module;
import android.app.Activity;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
import android.os.Build;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import statusgo.SignalHandler;
import statusgo.Statusgo;
import org.json.JSONException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventListener, SignalHandler {
private static final String TAG = "StatusModule";
private static StatusModule module;
private ReactApplicationContext reactContext;
private boolean rootedDevice;
private boolean background;
private Utils utils;
StatusModule(ReactApplicationContext reactContext, boolean rootedDevice) {
super(reactContext);
this.reactContext = reactContext;
this.rootedDevice = rootedDevice;
this.utils = new Utils(reactContext);
reactContext.addLifecycleEventListener(this);
}
@Override
public String getName() {
return "Status";
}
@Override
public void onHostResume() {
module = this;
this.background = false;
Statusgo.setMobileSignalHandler(this);
}
@Override
public void onHostPause() {
this.background = true;
}
@Override
public void onHostDestroy() {
Log.d(TAG, "******************* ON HOST DESTROY *************************");
}
public void handleSignal(final String jsonEventString) {
Log.d(TAG, "Signal event");
WritableMap params = Arguments.createMap();
params.putString("jsonEvent", jsonEventString);
this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params);
}
@ReactMethod
public void closeApplication() {
System.exit(0);
}
@ReactMethod
public void connectionChange(final String type, final boolean isExpensive) {
Log.d(TAG, "ConnectionChange: " + type + ", is expensive " + isExpensive);
Statusgo.connectionChange(type, isExpensive ? 1 : 0);
}
@ReactMethod
public void appStateChange(final String type) {
Log.d(TAG, "AppStateChange: " + type);
Statusgo.appStateChange(type);
}
@ReactMethod
public void startLocalNotifications() {
Log.d(TAG, "startLocalNotifications");
Statusgo.startLocalNotifications();
}
@ReactMethod
public void getNodeConfig(final Callback callback) throws JSONException {
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.getNodeConfig(), callback);
}
@ReactMethod
public void deleteImportedKey(final String keyUID, final String address, final String password, final Callback callback) throws JSONException {
final String keyStoreDir = this.utils.getKeyStorePath(keyUID);
this.utils.executeRunnableStatusGoMethod(() -> Statusgo.deleteImportedKey(address, password, keyStoreDir), callback);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String fleets() {
return Statusgo.fleets();
}
@Override
public @Nullable
Map<String, Object> getConstants() {
HashMap<String, Object> constants = new HashMap<String, Object>();
constants.put("is24Hour", this.utils.is24Hour());
constants.put("model", Build.MODEL);
constants.put("brand", Build.BRAND);
constants.put("buildId", Build.ID);
constants.put("deviceId", Build.BOARD);
return constants;
}
@ReactMethod
public void isDeviceRooted(final Callback callback) {
callback.invoke(rootedDevice);
}
@ReactMethod
public void deactivateKeepAwake() {
final Activity activity = getCurrentActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.getWindow().clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
});
}
}
}

View File

@ -0,0 +1,114 @@
package im.status.ethereum.module
import android.app.Activity
import android.os.Build
import android.util.Log
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
import statusgo.SignalHandler
import statusgo.Statusgo
import org.json.JSONException
import android.view.WindowManager
class StatusModule(private val reactContext: ReactApplicationContext, private val rootedDevice: Boolean) : ReactContextBaseJavaModule(reactContext), LifecycleEventListener, SignalHandler {
companion object {
private const val TAG = "StatusModule"
private var module: StatusModule? = null
}
private val utils: Utils = Utils(reactContext)
private var background: Boolean = false
init {
reactContext.addLifecycleEventListener(this)
}
override fun getName(): String {
return "Status"
}
override fun onHostResume() {
module = this
background = false
Statusgo.setMobileSignalHandler(this)
}
override fun onHostPause() {
background = true
}
override fun onHostDestroy() {
Log.d(TAG, "******************* ON HOST DESTROY *************************")
}
override fun handleSignal(jsonEventString: String) {
Log.d(TAG, "Signal event")
val params = Arguments.createMap()
params.putString("jsonEvent", jsonEventString)
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit("gethEvent", params)
}
@ReactMethod
fun closeApplication() {
System.exit(0)
}
@ReactMethod
fun connectionChange(type: String, isExpensive: Boolean) {
Log.d(TAG, "ConnectionChange: $type, is expensive $isExpensive")
Statusgo.connectionChange(type, if (isExpensive) 1 else 0)
}
@ReactMethod
fun appStateChange(type: String) {
Log.d(TAG, "AppStateChange: $type")
Statusgo.appStateChange(type)
}
@ReactMethod
fun startLocalNotifications() {
Log.d(TAG, "startLocalNotifications")
Statusgo.startLocalNotifications()
}
@ReactMethod
fun getNodeConfig(callback: Callback) {
utils.executeRunnableStatusGoMethod({ Statusgo.getNodeConfig() }, callback)
}
@ReactMethod
fun deleteImportedKey(keyUID: String, address: String, password: String, callback: Callback) {
val keyStoreDir = utils.getKeyStorePath(keyUID)
utils.executeRunnableStatusGoMethod({ Statusgo.deleteImportedKey(address, password, keyStoreDir) }, callback)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun fleets(): String {
return Statusgo.fleets()
}
override fun getConstants(): Map<String, Any>? {
return hashMapOf(
"is24Hour" to utils.is24Hour(),
"model" to Build.MODEL,
"brand" to Build.BRAND,
"buildId" to Build.ID,
"deviceId" to Build.BOARD
)
}
@ReactMethod
fun isDeviceRooted(callback: Callback) {
callback.invoke(rootedDevice)
}
@ReactMethod
fun deactivateKeepAwake() {
val activity = currentActivity
activity?.runOnUiThread {
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}

View File

@ -1,51 +0,0 @@
package im.status.ethereum.module;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import statusgo.Statusgo;
public class StatusPackage implements ReactPackage {
private boolean rootedDevice;
public static String getImageTLSCert() {
return Statusgo.imageServerTLSCert();
}
public StatusPackage(boolean rootedDevice) {
this.rootedDevice = rootedDevice;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new StatusModule(reactContext, this.rootedDevice));
modules.add(new AccountManager(reactContext));
modules.add(new EncryptionUtils(reactContext));
modules.add(new DatabaseManager(reactContext));
modules.add(new UIHelper(reactContext));
modules.add(new LogManager(reactContext));
modules.add(new Utils(reactContext));
modules.add(new NetworkManager(reactContext));
modules.add(new RNSelectableTextInputModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new RNSelectableTextInputViewManager()
);
}
}

View File

@ -0,0 +1,38 @@
package im.status.ethereum.module
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import statusgo.Statusgo
class StatusPackage(private val rootedDevice: Boolean) : ReactPackage {
companion object {
fun getImageTLSCert(): String = Statusgo.imageServerTLSCert()
}
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
val modules = mutableListOf<NativeModule>()
modules.apply {
add(StatusModule(reactContext, rootedDevice))
add(AccountManager(reactContext))
add(EncryptionUtils(reactContext))
add(DatabaseManager(reactContext))
add(UIHelper(reactContext))
add(LogManager(reactContext))
add(Utils(reactContext))
add(NetworkManager(reactContext))
add(RNSelectableTextInputModule(reactContext))
}
return modules
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(
RNSelectableTextInputViewManager()
)
}
}

View File

@ -1,155 +0,0 @@
package im.status.ethereum.module;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import android.util.Log;
import java.util.function.Supplier;
import java.io.File;
import android.content.Context;
import android.os.Environment;
import org.json.JSONObject;
import org.json.JSONException;
import statusgo.Statusgo;
public class Utils extends ReactContextBaseJavaModule {
private static final String gethLogFileName = "geth.log";
private static final String TAG = "Utils";
private ReactApplicationContext reactContext;
public Utils(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return "Utils";
}
public String getNoBackupDirectory() {
return this.getReactApplicationContext().getNoBackupFilesDir().getAbsolutePath();
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String backupDisabledDataDir() {
return getNoBackupDirectory();
}
public File getPublicStorageDirectory() {
final Context context = this.getReactApplicationContext();
// Environment.getExternalStoragePublicDirectory doesn't work as expected on Android Q
// https://developer.android.com/reference/android/os/Environment#getExternalStoragePublicDirectory(java.lang.String)
return context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
}
public File getLogsFile() {
final File pubDirectory = getPublicStorageDirectory();
final File logFile = new File(pubDirectory, gethLogFileName);
return logFile;
}
public String getKeyUID(final String json) throws JSONException {
final JSONObject jsonObj = new JSONObject(json);
return jsonObj.getString("key-uid");
}
public String pathCombine(final String path1, final String path2) {
// Replace this logic with Paths.get(path1, path2) once API level 26+ becomes the minimum supported API level
final File file = new File(path1, path2);
return file.getAbsolutePath();
}
public String getKeyStorePath(String keyUID) {
final String commonKeydir = pathCombine(getNoBackupDirectory(), "/keystore");
final String keydir = pathCombine(commonKeydir, keyUID);
return keydir;
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String keystoreDir() {
final String absRootDirPath = getNoBackupDirectory();
return pathCombine(absRootDirPath, "keystore");
}
public void migrateKeyStoreDir(final String accountData, final String password) {
try {
final String commonKeydir = pathCombine(getNoBackupDirectory(), "/keystore");
final String keydir = getKeyStorePath(getKeyUID(accountData));
Log.d(TAG, "before migrateKeyStoreDir " + keydir);
File keydirFile = new File(keydir);
if(!keydirFile.exists() || keydirFile.list().length == 0) {
Log.d(TAG, "migrateKeyStoreDir");
Statusgo.migrateKeyStoreDir(accountData, password, commonKeydir, keydir);
Statusgo.initKeystore(keydir);
}
} catch (JSONException e) {
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
}
}
public boolean checkAvailability() {
// We wait at least 10s for getCurrentActivity to return a value,
// otherwise we give up
for (int attempts = 0; attempts < 100; attempts++) {
if (getCurrentActivity() != null) {
return true;
}
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
if (getCurrentActivity() != null) {
return true;
}
Log.d(TAG, "Activity doesn't exist");
return false;
}
}
Log.d(TAG, "Activity doesn't exist");
return false;
}
public void executeRunnableStatusGoMethod(Supplier<String> method, Callback callback) throws JSONException {
if (!checkAvailability()) {
callback.invoke(false);
return;
}
Runnable runnableTask = () -> {
String res = method.get();
callback.invoke(res);
};
StatusThreadPoolExecutor.getInstance().execute(runnableTask);
}
@ReactMethod
public void validateMnemonic(final String seed, final Callback callback) throws JSONException {
executeRunnableStatusGoMethod(() -> Statusgo.validateMnemonic(seed), callback);
}
public Boolean is24Hour() {
return android.text.format.DateFormat.is24HourFormat(this.reactContext.getApplicationContext());
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String checkAddressChecksum(final String address) {
return Statusgo.checkAddressChecksum(address);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String isAddress(final String address) {
return Statusgo.isAddress(address);
}
@ReactMethod(isBlockingSynchronousMethod = true)
public String toChecksumAddress(final String address) {
return Statusgo.toChecksumAddress(address);
}
}

View File

@ -0,0 +1,144 @@
package im.status.ethereum.module
import android.content.Context
import android.os.Environment
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import android.util.Log
import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.util.function.Supplier
import statusgo.Statusgo
class Utils(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
companion object {
private const val gethLogFileName = "geth.log"
private const val TAG = "Utils"
}
override fun getName(): String {
return "Utils"
}
fun getNoBackupDirectory(): String {
return reactContext.noBackupFilesDir.absolutePath
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun backupDisabledDataDir(): String {
return getNoBackupDirectory()
}
fun getPublicStorageDirectory(): File? {
// Environment.getExternalStoragePublicDirectory doesn't work as expected on Android Q
// https://developer.android.com/reference/android/os/Environment#getExternalStoragePublicDirectory(java.lang.String)
return reactContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
}
fun getLogsFile(): File {
val pubDirectory = getPublicStorageDirectory()
return File(pubDirectory, gethLogFileName)
}
fun getKeyUID(json: String): String {
val jsonObj = JSONObject(json)
return jsonObj.getString("key-uid")
}
fun pathCombine(path1: String, path2: String): String {
val file = File(path1, path2)
return file.absolutePath
}
fun getKeyStorePath(keyUID: String): String {
val commonKeydir = pathCombine(getNoBackupDirectory(), "/keystore")
return pathCombine(commonKeydir, keyUID)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun keystoreDir(): String {
val absRootDirPath = getNoBackupDirectory()
return pathCombine(absRootDirPath, "keystore")
}
fun migrateKeyStoreDir(accountData: String, password: String) {
try {
val commonKeydir = pathCombine(getNoBackupDirectory(), "/keystore")
val keydir = getKeyStorePath(getKeyUID(accountData))
Log.d(TAG, "before migrateKeyStoreDir $keydir")
val keydirFile = File(keydir)
if (!keydirFile.exists() || keydirFile.list().isEmpty()) {
Log.d(TAG, "migrateKeyStoreDir")
Statusgo.migrateKeyStoreDir(accountData, password, commonKeydir, keydir)
Statusgo.initKeystore(keydir)
}
} catch (e: JSONException) {
Log.e(TAG, "JSON conversion failed: ${e.message}")
}
}
fun checkAvailability(): Boolean {
// We wait at least 10s for getCurrentActivity to return a value,
// otherwise we give up
for (attempts in 0 until 100) {
if (currentActivity != null) {
return true
}
try {
Thread.sleep(100)
} catch (ex: InterruptedException) {
if (currentActivity != null) {
return true
}
Log.d(TAG, "Activity doesn't exist")
return false
}
}
Log.d(TAG, "Activity doesn't exist")
return false
}
fun executeRunnableStatusGoMethod(method: Supplier<String>, callback: Callback) {
if (!checkAvailability()) {
callback.invoke(false)
return
}
val runnableTask = Runnable {
val res = method.get()
callback.invoke(res)
}
StatusThreadPoolExecutor.getInstance().execute(runnableTask)
}
@ReactMethod
fun validateMnemonic(seed: String, callback: Callback) {
executeRunnableStatusGoMethod({ Statusgo.validateMnemonic(seed) }, callback)
}
fun is24Hour(): Boolean {
return android.text.format.DateFormat.is24HourFormat(reactContext.applicationContext)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun checkAddressChecksum(address: String): String {
return Statusgo.checkAddressChecksum(address)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun isAddress(address: String): String {
return Statusgo.isAddress(address)
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun toChecksumAddress(address: String): String {
return Statusgo.toChecksumAddress(address)
}
}