diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 570b08de783..686ce6e0f73 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 ()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 (Lcom/facebook/react/bridge/ReactApplicationContext;)V public fun getAndroidID ()Ljava/lang/String; - public fun getTypedExportedConstants ()Ljava/util/Map; public fun invalidate ()V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java index 49501712545..5bbf5bbd2c9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java @@ -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, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java deleted file mode 100644 index 68225d58a97..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java +++ /dev/null @@ -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 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.of( - "appDisplayName", - appDisplayName, - "appIdentifier", - appIdentifier, - "platform", - "android", - "deviceName", - Build.MODEL, - "reactNativeVersion", - getReactNativeVersionString()); - } - - private static String getReactNativeVersionString() { - Map 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; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.kt new file mode 100644 index 00000000000..b85015aa724 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.kt @@ -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 { + 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 ?: "" + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java deleted file mode 100644 index 3c283271487..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.java +++ /dev/null @@ -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 getTypedExportedConstants() { - HashMap 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; - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.kt new file mode 100644 index 00000000000..dcccd9a8252 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoModule.kt @@ -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 { + val constants = mutableMapOf() + 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 + } + } +}