chore(Android): convert systeminfo module classes to kotlin (#47616)

Summary:
Saw a lot of PRs and push to convert existing Java code to Kotlin, and wanted to contribute to the cause. This is a starting point for me so I can understand more about the Java to Kotlin conversion process for ReactAndroid package.

## Changelog:
- Converted AndroidInfoHelpers to Kotlin class
- Converted AndroidInfoModule to Kotlin object and updated usage of it across the package

Pick one each for the category and type tags:

[ANDROID] [CHANGED] - Migrated systeminfo module code from Java to Kotlin

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests

Pull Request resolved: https://github.com/facebook/react-native/pull/47616

Test Plan: Verified build on local dev environment.

Reviewed By: NickGerleman

Differential Revision: D66258996

Pulled By: javache

fbshipit-source-id: e0958c0be735f54cb20c7daeee1526059edaabcc
This commit is contained in:
oddlyspaced
2024-11-22 03:35:36 -08:00
committed by Facebook GitHub Bot
parent d424bb9d7c
commit 8dc2c90ce5
6 changed files with 224 additions and 291 deletions
@@ -3595,24 +3595,23 @@ public final class com/facebook/react/modules/statusbar/StatusBarModule : com/fa
public final class com/facebook/react/modules/statusbar/StatusBarModule$Companion {
}
public class com/facebook/react/modules/systeminfo/AndroidInfoHelpers {
public final class com/facebook/react/modules/systeminfo/AndroidInfoHelpers {
public static final field DEVICE_LOCALHOST Ljava/lang/String;
public static final field EMULATOR_LOCALHOST Ljava/lang/String;
public static final field GENYMOTION_LOCALHOST Ljava/lang/String;
public static final field INSTANCE Lcom/facebook/react/modules/systeminfo/AndroidInfoHelpers;
public static final field METRO_HOST_PROP_NAME Ljava/lang/String;
public fun <init> ()V
public static fun getAdbReverseTcpCommand (Landroid/content/Context;)Ljava/lang/String;
public static fun getAdbReverseTcpCommand (Ljava/lang/Integer;)Ljava/lang/String;
public static fun getFriendlyDeviceName ()Ljava/lang/String;
public static fun getInspectorHostMetadata (Landroid/content/Context;)Ljava/util/Map;
public static fun getServerHost (Landroid/content/Context;)Ljava/lang/String;
public static fun getServerHost (Ljava/lang/Integer;)Ljava/lang/String;
public static final fun getAdbReverseTcpCommand (I)Ljava/lang/String;
public static final fun getAdbReverseTcpCommand (Landroid/content/Context;)Ljava/lang/String;
public static final fun getFriendlyDeviceName ()Ljava/lang/String;
public static final fun getInspectorHostMetadata (Landroid/content/Context;)Ljava/util/Map;
public static final fun getServerHost (I)Ljava/lang/String;
public static final fun getServerHost (Landroid/content/Context;)Ljava/lang/String;
}
public class com/facebook/react/modules/systeminfo/AndroidInfoModule : com/facebook/fbreact/specs/NativePlatformConstantsAndroidSpec, com/facebook/react/turbomodule/core/interfaces/TurboModule {
public final class com/facebook/react/modules/systeminfo/AndroidInfoModule : com/facebook/fbreact/specs/NativePlatformConstantsAndroidSpec {
public fun <init> (Lcom/facebook/react/bridge/ReactApplicationContext;)V
public fun getAndroidID ()Ljava/lang/String;
public fun getTypedExportedConstants ()Ljava/util/Map;
public fun invalidate ()V
}
@@ -8,7 +8,6 @@
package com.facebook.react;
import static com.facebook.react.ReactInstanceManager.initializeSoLoaderIfNecessary;
import static com.facebook.react.modules.systeminfo.AndroidInfoHelpers.getFriendlyDeviceName;
import android.app.Activity;
import android.app.Application;
@@ -37,6 +36,7 @@ import com.facebook.react.internal.ChoreographerProvider;
import com.facebook.react.jscexecutor.JSCExecutor;
import com.facebook.react.jscexecutor.JSCExecutorFactory;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.systeminfo.AndroidInfoHelpers;
import com.facebook.react.packagerconnection.RequestHandler;
import java.util.ArrayList;
import java.util.List;
@@ -339,7 +339,7 @@ public class ReactInstanceManagerBuilder {
// We use the name of the device and the app for debugging & metrics
//noinspection ConstantConditions
String appName = mApplication.getPackageName();
String deviceName = getFriendlyDeviceName();
String deviceName = AndroidInfoHelpers.getFriendlyDeviceName();
return new ReactInstanceManager(
mApplication,
@@ -1,175 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.systeminfo;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.os.Build;
import com.facebook.common.logging.FLog;
import com.facebook.react.R;
import com.facebook.react.common.MapBuilder;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nullable;
public class AndroidInfoHelpers {
public static final String EMULATOR_LOCALHOST = "10.0.2.2";
public static final String GENYMOTION_LOCALHOST = "10.0.3.2";
public static final String DEVICE_LOCALHOST = "localhost";
public static final String METRO_HOST_PROP_NAME = "metro.host";
private static final String TAG = AndroidInfoHelpers.class.getSimpleName();
private static boolean isRunningOnGenymotion() {
return Build.FINGERPRINT.contains("vbox");
}
private static boolean isRunningOnStockEmulator() {
return Build.FINGERPRINT.contains("generic")
|| Build.FINGERPRINT.startsWith("google/sdk_gphone");
}
public static String getServerHost(Integer port) {
return getServerIpAddress(port);
}
public static String getServerHost(Context context) {
return getServerIpAddress(getDevServerPort(context));
}
public static String getAdbReverseTcpCommand(Integer port) {
return "adb reverse tcp:" + port + " tcp:" + port;
}
public static String getAdbReverseTcpCommand(Context context) {
return getAdbReverseTcpCommand(getDevServerPort(context));
}
// WARNING(festevezga): This RN helper method has been copied to another FB-only target. Any
// changes should be applied to both.
public static String getFriendlyDeviceName() {
if (isRunningOnGenymotion()) {
// Genymotion already has a friendly name by default
return Build.MODEL;
} else {
return Build.MODEL + " - " + Build.VERSION.RELEASE + " - API " + Build.VERSION.SDK_INT;
}
}
/**
* Helper returning common metadata describing the React Native host, used to identify an
* inspector {@code HostTarget}. The returned mapping is a subset of {@code
* jsinspector_modern::HostTargetMetadata}.
*/
public static Map<String, String> getInspectorHostMetadata(@Nullable Context applicationContext) {
String appIdentifier = null;
String appDisplayName = null;
if (applicationContext != null) {
ApplicationInfo applicationInfo = applicationContext.getApplicationInfo();
int labelResourceId = applicationInfo.labelRes;
appIdentifier = applicationContext.getPackageName();
appDisplayName =
labelResourceId == 0
? applicationInfo.nonLocalizedLabel.toString()
: applicationContext.getString(labelResourceId);
}
return MapBuilder.<String, String>of(
"appDisplayName",
appDisplayName,
"appIdentifier",
appIdentifier,
"platform",
"android",
"deviceName",
Build.MODEL,
"reactNativeVersion",
getReactNativeVersionString());
}
private static String getReactNativeVersionString() {
Map<String, Object> version = ReactNativeVersion.VERSION;
return version.get("major")
+ "."
+ version.get("minor")
+ "."
+ version.get("patch")
+ (version.get("prerelease") != null ? "-" + version.get("prerelease") : "");
}
private static Integer getDevServerPort(Context context) {
Resources resources = context.getResources();
return resources.getInteger(R.integer.react_native_dev_server_port);
}
private static String getServerIpAddress(int port) {
// Since genymotion runs in vbox it use different hostname to refer to adb host.
// We detect whether app runs on genymotion and replace js bundle server hostname accordingly
String ipAddress;
String metroHostProp = getMetroHostPropValue();
if (!metroHostProp.equals("")) {
ipAddress = metroHostProp;
} else if (isRunningOnGenymotion()) {
ipAddress = GENYMOTION_LOCALHOST;
} else if (isRunningOnStockEmulator()) {
ipAddress = EMULATOR_LOCALHOST;
} else {
ipAddress = DEVICE_LOCALHOST;
}
return String.format(Locale.US, "%s:%d", ipAddress, port);
}
private static String metroHostPropValue = null;
private static synchronized String getMetroHostPropValue() {
if (metroHostPropValue != null) {
return metroHostPropValue;
}
Process process = null;
BufferedReader reader = null;
try {
process =
Runtime.getRuntime().exec(new String[] {"/system/bin/getprop", METRO_HOST_PROP_NAME});
reader =
new BufferedReader(
new InputStreamReader(process.getInputStream(), Charset.forName("UTF-8")));
String lastLine = "";
String line;
while ((line = reader.readLine()) != null) {
lastLine = line;
}
metroHostPropValue = lastLine;
} catch (Exception e) {
FLog.w(TAG, "Failed to query for metro.host prop:", e);
metroHostPropValue = "";
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (Exception exc) {
}
if (process != null) {
process.destroy();
}
}
return metroHostPropValue;
}
}
@@ -0,0 +1,126 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.systeminfo
import android.content.Context
import android.os.Build
import com.facebook.common.logging.FLog
import com.facebook.react.R
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.charset.Charset
import java.util.Locale
public object AndroidInfoHelpers {
public const val EMULATOR_LOCALHOST: String = "10.0.2.2"
public const val GENYMOTION_LOCALHOST: String = "10.0.3.2"
public const val DEVICE_LOCALHOST: String = "localhost"
public const val METRO_HOST_PROP_NAME: String = "metro.host"
private val TAG = AndroidInfoHelpers::class.java.simpleName
private var metroHostPropValue: String? = null
private fun isRunningOnGenymotion(): Boolean = Build.FINGERPRINT.contains("vbox")
private fun isRunningOnStockEmulator(): Boolean =
Build.FINGERPRINT.contains("generic") || Build.FINGERPRINT.startsWith("google/sdk_gphone")
@JvmStatic public fun getServerHost(port: Int): String = getServerIpAddress(port)
@JvmStatic
public fun getServerHost(context: Context): String = getServerIpAddress(getDevServerPort(context))
@JvmStatic
public fun getAdbReverseTcpCommand(port: Int): String = "adb reverse tcp:$port tcp:$port"
@JvmStatic
public fun getAdbReverseTcpCommand(context: Context): String =
getAdbReverseTcpCommand(getDevServerPort(context))
@JvmStatic
public fun getFriendlyDeviceName(): String {
return if (isRunningOnGenymotion()) {
Build.MODEL
} else {
"${Build.MODEL} - ${Build.VERSION.RELEASE} - API ${Build.VERSION.SDK_INT}"
}
}
@JvmStatic
public fun getInspectorHostMetadata(applicationContext: Context?): Map<String, String?> {
var appIdentifier: String? = null
var appDisplayName: String? = null
if (applicationContext != null) {
val applicationInfo = applicationContext.applicationInfo
val labelResourceId = applicationInfo.labelRes
appIdentifier = applicationContext.packageName
appDisplayName =
if (labelResourceId == 0) {
applicationInfo.nonLocalizedLabel.toString()
} else {
applicationContext.getString(labelResourceId)
}
}
return mapOf(
"appDisplayName" to appDisplayName,
"appIdentifier" to appIdentifier,
"platform" to "android",
"deviceName" to Build.MODEL,
"reactNativeVersion" to getReactNativeVersionString())
}
private fun getReactNativeVersionString(): String {
val version = ReactNativeVersion.VERSION
return "${version["major"]}.${version["minor"]}.${version["patch"]}" +
(version["prerelease"]?.let { "-$it" } ?: "")
}
private fun getDevServerPort(context: Context): Int =
context.resources.getInteger(R.integer.react_native_dev_server_port)
private fun getServerIpAddress(port: Int): String {
val ipAddress: String =
when {
getMetroHostPropValue().isNotEmpty() -> getMetroHostPropValue()
isRunningOnGenymotion() -> GENYMOTION_LOCALHOST
isRunningOnStockEmulator() -> EMULATOR_LOCALHOST
else -> DEVICE_LOCALHOST
}
return String.format(Locale.US, "%s:%d", ipAddress, port)
}
@Synchronized
private fun getMetroHostPropValue(): String {
if (metroHostPropValue != null) {
return metroHostPropValue!!
}
var process: Process? = null
var reader: BufferedReader? = null
try {
process = Runtime.getRuntime().exec(arrayOf("/system/bin/getprop", METRO_HOST_PROP_NAME))
reader = BufferedReader(InputStreamReader(process.inputStream, Charset.forName("UTF-8")))
var lastLine = ""
var line: String?
while (reader.readLine().also { line = it } != null) {
lastLine = line ?: ""
}
metroHostPropValue = lastLine
} catch (e: Exception) {
FLog.w(TAG, "Failed to query for metro.host prop:", e)
metroHostPropValue = ""
} finally {
reader?.close()
process?.destroy()
}
return metroHostPropValue ?: ""
}
}
@@ -1,104 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.systeminfo;
import static android.content.Context.UI_MODE_SERVICE;
import android.annotation.SuppressLint;
import android.app.UiModeManager;
import android.content.res.Configuration;
import android.os.Build;
import android.provider.Settings.Secure;
import androidx.annotation.Nullable;
import com.facebook.fbreact.specs.NativePlatformConstantsAndroidSpec;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import java.util.HashMap;
import java.util.Map;
/** Module that exposes Android Constants to JS. */
@ReactModule(name = NativePlatformConstantsAndroidSpec.NAME)
@SuppressLint("HardwareIds")
public class AndroidInfoModule extends NativePlatformConstantsAndroidSpec implements TurboModule {
private static final String IS_TESTING = "IS_TESTING";
private static final String IS_DISABLE_ANIMATIONS = "IS_DISABLE_ANIMATIONS";
public AndroidInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* See:
* https://developer.android.com/reference/android/app/UiModeManager.html#getCurrentModeType()
*/
private String uiMode() {
UiModeManager uiModeManager =
(UiModeManager) getReactApplicationContext().getSystemService(UI_MODE_SERVICE);
switch (uiModeManager.getCurrentModeType()) {
case Configuration.UI_MODE_TYPE_TELEVISION:
return "tv";
case Configuration.UI_MODE_TYPE_CAR:
return "car";
case Configuration.UI_MODE_TYPE_DESK:
return "desk";
case Configuration.UI_MODE_TYPE_WATCH:
return "watch";
case Configuration.UI_MODE_TYPE_VR_HEADSET:
return "vrheadset";
case Configuration.UI_MODE_TYPE_NORMAL:
return "normal";
default:
return "unknown";
}
}
@Override
public @Nullable Map<String, Object> getTypedExportedConstants() {
HashMap<String, Object> constants = new HashMap<>();
constants.put("Version", Build.VERSION.SDK_INT);
constants.put("Release", Build.VERSION.RELEASE);
constants.put("Serial", Build.SERIAL);
constants.put("Fingerprint", Build.FINGERPRINT);
constants.put("Model", Build.MODEL);
constants.put("Manufacturer", Build.MANUFACTURER);
constants.put("Brand", Build.BRAND);
if (ReactBuildConfig.DEBUG) {
constants.put(
"ServerHost",
AndroidInfoHelpers.getServerHost(getReactApplicationContext().getApplicationContext()));
}
constants.put(
"isTesting", "true".equals(System.getProperty(IS_TESTING)) || isRunningScreenshotTest());
String isDisableAnimations = System.getProperty(IS_DISABLE_ANIMATIONS);
if (isDisableAnimations != null) {
constants.put("isDisableAnimations", "true".equals(isDisableAnimations));
}
constants.put("reactNativeVersion", ReactNativeVersion.VERSION);
constants.put("uiMode", uiMode());
return constants;
}
@Override
public String getAndroidID() {
return Secure.getString(getReactApplicationContext().getContentResolver(), Secure.ANDROID_ID);
}
@Override
public void invalidate() {}
private Boolean isRunningScreenshotTest() {
try {
Class.forName("com.facebook.testing.react.screenshots.ReactAppScreenshotTestActivity");
return true;
} catch (ClassNotFoundException ignored) {
return false;
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.systeminfo
import android.annotation.SuppressLint
import android.app.UiModeManager
import android.content.Context.UI_MODE_SERVICE
import android.content.res.Configuration
import android.os.Build
import android.provider.Settings.Secure
import com.facebook.fbreact.specs.NativePlatformConstantsAndroidSpec
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.module.annotations.ReactModule
/** Module that exposes Android Constants to JS. */
@ReactModule(name = NativePlatformConstantsAndroidSpec.NAME)
@SuppressLint("HardwareIds")
@Suppress("DEPRECATION")
public class AndroidInfoModule(reactContext: ReactApplicationContext) :
NativePlatformConstantsAndroidSpec(reactContext) {
private companion object {
private const val IS_TESTING = "IS_TESTING"
private const val IS_DISABLE_ANIMATIONS = "IS_DISABLE_ANIMATIONS"
}
/**
* See:
* https://developer.android.com/reference/android/app/UiModeManager.html#getCurrentModeType()
*/
private fun uiMode(): String {
val uiModeManager = reactApplicationContext.getSystemService(UI_MODE_SERVICE) as UiModeManager
return when (uiModeManager.currentModeType) {
Configuration.UI_MODE_TYPE_TELEVISION -> "tv"
Configuration.UI_MODE_TYPE_CAR -> "car"
Configuration.UI_MODE_TYPE_DESK -> "desk"
Configuration.UI_MODE_TYPE_WATCH -> "watch"
Configuration.UI_MODE_TYPE_VR_HEADSET -> "vrheadset"
Configuration.UI_MODE_TYPE_NORMAL -> "normal"
else -> "unknown"
}
}
override fun getTypedExportedConstants(): Map<String, Any?> {
val constants = mutableMapOf<String, Any?>()
constants["Version"] = Build.VERSION.SDK_INT
constants["Release"] = Build.VERSION.RELEASE
constants["Serial"] = Build.SERIAL
constants["Fingerprint"] = Build.FINGERPRINT
constants["Model"] = Build.MODEL
constants["Manufacturer"] = Build.MANUFACTURER
constants["Brand"] = Build.BRAND
if (ReactBuildConfig.DEBUG) {
constants["ServerHost"] =
AndroidInfoHelpers.getServerHost(reactApplicationContext.applicationContext)
}
constants["isTesting"] = "true" == System.getProperty(IS_TESTING) || isRunningScreenshotTest()
val isDisableAnimations = System.getProperty(IS_DISABLE_ANIMATIONS)
if (isDisableAnimations != null) {
constants["isDisableAnimations"] = "true" == isDisableAnimations
}
constants["reactNativeVersion"] = ReactNativeVersion.VERSION
constants["uiMode"] = uiMode()
return constants
}
override fun getAndroidID(): String {
return Secure.getString(reactApplicationContext.contentResolver, Secure.ANDROID_ID)
}
override fun invalidate() {}
private fun isRunningScreenshotTest(): Boolean {
return try {
Class.forName("com.facebook.testing.react.screenshots.ReactAppScreenshotTestActivity")
true
} catch (ignored: ClassNotFoundException) {
false
}
}
}