From b8fe3180afd20b8a6101232cef1d543dc506b708 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 7 Apr 2025 08:40:47 -0700 Subject: [PATCH 1/7] Migrate to Kotlin - ReactInstanceDevHelper Summary: This diff migrates the following file to Kotlin - ReactInstanceDevHelper as part of our ongoing effort of migrating the codebase to Kotlin Changelog: [Android] [Changed] - Migrate to Kotlin - ReactInstanceDevHelper. Some users implementing this class in Kotlin could have breakages. As this is a devtools/frameworks API we're not marking this as breaking. Differential Revision: D72555226 --- .../facebook/react/ReactInstanceManager.java | 2 +- .../devsupport/DevSupportManagerBase.java | 9 +++- .../devsupport/ReactInstanceDevHelper.java | 47 ------------------- .../devsupport/ReactInstanceDevHelper.kt | 44 +++++++++++++++++ .../react/runtime/ReactHostImplDevHelper.kt | 21 +++++---- 5 files changed, 64 insertions(+), 59 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevHelper.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevHelper.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 5c1d66da87fafe..d9efcc5f144d04 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -374,7 +374,7 @@ public void destroyRootView(View rootView) { } @Override - public void reload(String s) { + public void reload(String reason) { // no-op not implemented for Bridge Mode } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java index 0d68f1a2d255c0..734ad9b1281d83 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java @@ -38,6 +38,7 @@ import com.facebook.react.R; import com.facebook.react.bridge.DefaultJSExceptionHandler; import com.facebook.react.bridge.JSBundleLoader; +import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReactMarkerConstants; @@ -540,7 +541,13 @@ public View getView(int position, @Nullable View convertView, ViewGroup parent) private @Nullable String getJSExecutorDescription() { try { - return getReactInstanceDevHelper().getJavaScriptExecutorFactory().toString(); + JavaScriptExecutorFactory factory = + getReactInstanceDevHelper().getJavaScriptExecutorFactory(); + if (factory != null) { + return factory.toString(); + } else { + return null; + } } catch (IllegalStateException e) { return null; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevHelper.java deleted file mode 100644 index 918f569425ae33..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevHelper.java +++ /dev/null @@ -1,47 +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.devsupport; - -import android.app.Activity; -import android.view.View; -import androidx.annotation.Nullable; -import com.facebook.react.bridge.JSBundleLoader; -import com.facebook.react.bridge.JavaScriptExecutorFactory; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.interfaces.TaskInterface; - -/** - * Interface used by {@link DevSupportManager} for accessing some fields and methods of {@link - * ReactInstanceManager} or {@link ReactHost} for the purpose of displaying and handling developer - * menu options. - */ -public interface ReactInstanceDevHelper { - /** Notify react instance manager about new JS bundle version downloaded from the server. */ - void onJSBundleLoadedFromServer(); - - /** Request to toggle the react element inspector. */ - void toggleElementInspector(); - - /** Get reference to top level #{link Activity} attached to react context */ - @Nullable - Activity getCurrentActivity(); - - JavaScriptExecutorFactory getJavaScriptExecutorFactory(); - - @Nullable - View createRootView(String appKey); - - void destroyRootView(View rootView); - - void reload(String s); - - TaskInterface loadBundle(JSBundleLoader bundleLoader); - - @Nullable - ReactContext getCurrentReactContext(); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevHelper.kt new file mode 100644 index 00000000000000..f9632700e41107 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevHelper.kt @@ -0,0 +1,44 @@ +/* + * 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.devsupport + +import android.app.Activity +import android.view.View +import com.facebook.react.bridge.JSBundleLoader +import com.facebook.react.bridge.JavaScriptExecutorFactory +import com.facebook.react.bridge.ReactContext +import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.interfaces.TaskInterface + +/** + * Interface used by [DevSupportManager] for accessing some fields and methods of [ReactHost] for + * the purpose of displaying and handling developer menu options. + */ +public interface ReactInstanceDevHelper { + + /** Get reference to top level [Activity] attached to react context */ + public val currentActivity: Activity? + + public val javaScriptExecutorFactory: JavaScriptExecutorFactory + + public val currentReactContext: ReactContext? + + /** Notify react instance manager about new JS bundle version downloaded from the server. */ + public fun onJSBundleLoadedFromServer() + + /** Request to toggle the react element inspector. */ + public fun toggleElementInspector() + + public fun createRootView(appKey: String): View? + + public fun destroyRootView(rootView: View) + + public fun reload(reason: String) + + public fun loadBundle(bundleLoader: JSBundleLoader): TaskInterface +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt index 5af9594ee44b9d..91351a09f89719 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt @@ -26,6 +26,15 @@ import com.facebook.react.modules.core.DeviceEventManagerModule internal class ReactHostImplDevHelper(private val delegate: ReactHostImpl) : ReactInstanceDevHelper { + override val currentActivity: Activity? + get() = delegate.lastUsedActivity + + override val javaScriptExecutorFactory: JavaScriptExecutorFactory + get() = throw IllegalStateException("Not implemented for bridgeless mode") + + override val currentReactContext: ReactContext? + get() = delegate.currentReactContext + override fun onJSBundleLoadedFromServer() { // Not implemented, only referenced by BridgeDevSupportManager } @@ -37,12 +46,6 @@ internal class ReactHostImplDevHelper(private val delegate: ReactHostImpl) : ?.emit("toggleElementInspector", null) } - override fun getCurrentActivity(): Activity? = delegate.lastUsedActivity - - override fun getJavaScriptExecutorFactory(): JavaScriptExecutorFactory { - throw IllegalStateException("Not implemented for bridgeless mode") - } - override fun createRootView(appKey: String): View? { val currentActivity = currentActivity if (currentActivity != null && !delegate.isSurfaceWithModuleNameAttached(appKey)) { @@ -59,12 +62,10 @@ internal class ReactHostImplDevHelper(private val delegate: ReactHostImpl) : // Not implemented, only referenced by BridgeDevSupportManager } - override fun reload(s: String) { - delegate.reload(s) + override fun reload(reason: String) { + delegate.reload(reason) } override fun loadBundle(bundleLoader: JSBundleLoader): TaskInterface = delegate.loadBundle(bundleLoader) - - override fun getCurrentReactContext(): ReactContext? = delegate.currentReactContext } From e68a0abfd0e07c6c1e001ea89e23704d1aa50f58 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 7 Apr 2025 08:40:47 -0700 Subject: [PATCH 2/7] Migrate to Kotlin - DevSupportManagerFactory Summary: This diff migrates the following file to Kotlin - DevSupportManagerFactory as part of our ongoing effort of migrating the codebase to Kotlin Changelog: [Android] [Changed] - Migrate to Kotlin - DevSupportManagerFactory - We couldn't find any implementation of this class in OSS. Some Kotlin implementers might have to change the method signatures. However this interface is not supposed to be extended in OSS. Differential Revision: D72556310 --- .../DefaultDevSupportManagerFactory.kt | 2 +- .../devsupport/DevSupportManagerFactory.java | 56 ------------------ .../devsupport/DevSupportManagerFactory.kt | 57 +++++++++++++++++++ 3 files changed, 58 insertions(+), 57 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.kt index df5724d8083e34..bb03454ac27c71 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.kt @@ -95,7 +95,7 @@ internal class DefaultDevSupportManagerFactory : DevSupportManagerFactory { redBoxHandler: RedBoxHandler?, devBundleDownloadListener: DevBundleDownloadListener?, minNumShakes: Int, - customPackagerCommandHandlers: MutableMap?, + customPackagerCommandHandlers: Map?, surfaceDelegateFactory: SurfaceDelegateFactory?, devLoadingViewManager: DevLoadingViewManager?, pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java deleted file mode 100644 index 29d9f7e3053be9..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java +++ /dev/null @@ -1,56 +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.devsupport; - -import android.content.Context; -import androidx.annotation.Nullable; -import com.facebook.react.common.SurfaceDelegateFactory; -import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; -import com.facebook.react.devsupport.interfaces.DevLoadingViewManager; -import com.facebook.react.devsupport.interfaces.DevSupportManager; -import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager; -import com.facebook.react.devsupport.interfaces.RedBoxHandler; -import com.facebook.react.packagerconnection.RequestHandler; -import java.util.Map; - -public interface DevSupportManagerFactory { - /** - * Factory used by the Old Architecture flow to create a {@link DevSupportManager} and a {@link - * com.facebook.react.runtime.BridgeDevSupportManager} - */ - DevSupportManager create( - Context applicationContext, - ReactInstanceDevHelper reactInstanceManagerHelper, - @Nullable String packagerPathForJSBundleName, - boolean enableOnCreate, - @Nullable RedBoxHandler redBoxHandler, - @Nullable DevBundleDownloadListener devBundleDownloadListener, - int minNumShakes, - @Nullable Map customPackagerCommandHandlers, - @Nullable SurfaceDelegateFactory surfaceDelegateFactory, - @Nullable DevLoadingViewManager devLoadingViewManager, - @Nullable PausedInDebuggerOverlayManager pausedInDebuggerOverlayManager); - - /** - * Factory used by the New Architecture/Bridgeless flow to create a {@link DevSupportManager} and - * a {@link BridgelessDevSupportManager} - */ - DevSupportManager create( - Context applicationContext, - ReactInstanceDevHelper reactInstanceManagerHelper, - @Nullable String packagerPathForJSBundleName, - boolean enableOnCreate, - @Nullable RedBoxHandler redBoxHandler, - @Nullable DevBundleDownloadListener devBundleDownloadListener, - int minNumShakes, - @Nullable Map customPackagerCommandHandlers, - @Nullable SurfaceDelegateFactory surfaceDelegateFactory, - @Nullable DevLoadingViewManager devLoadingViewManager, - @Nullable PausedInDebuggerOverlayManager pausedInDebuggerOverlayManager, - boolean useDevSupport); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.kt new file mode 100644 index 00000000000000..c006de2d7ba865 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.kt @@ -0,0 +1,57 @@ +/* + * 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.devsupport + +import android.content.Context +import com.facebook.react.common.SurfaceDelegateFactory +import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener +import com.facebook.react.devsupport.interfaces.DevLoadingViewManager +import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager +import com.facebook.react.devsupport.interfaces.RedBoxHandler +import com.facebook.react.packagerconnection.RequestHandler + +public interface DevSupportManagerFactory { + + /** + * Factory used by the Old Architecture flow to create a [DevSupportManager] and a + * [BridgeDevSupportManager] + */ + public fun create( + applicationContext: Context, + reactInstanceManagerHelper: ReactInstanceDevHelper, + packagerPathForJSBundleName: String?, + enableOnCreate: Boolean, + redBoxHandler: RedBoxHandler?, + devBundleDownloadListener: DevBundleDownloadListener?, + minNumShakes: Int, + customPackagerCommandHandlers: Map?, + surfaceDelegateFactory: SurfaceDelegateFactory?, + devLoadingViewManager: DevLoadingViewManager?, + pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager? + ): DevSupportManager + + /** + * Factory used by the New Architecture/Bridgeless flow to create a [DevSupportManager] and a + * [BridgelessDevSupportManager] + */ + public fun create( + applicationContext: Context, + reactInstanceManagerHelper: ReactInstanceDevHelper, + packagerPathForJSBundleName: String?, + enableOnCreate: Boolean, + redBoxHandler: RedBoxHandler?, + devBundleDownloadListener: DevBundleDownloadListener?, + minNumShakes: Int, + customPackagerCommandHandlers: Map?, + surfaceDelegateFactory: SurfaceDelegateFactory?, + devLoadingViewManager: DevLoadingViewManager?, + pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?, + useDevSupport: Boolean + ): DevSupportManager +} From 0ccae8772610a618a19e6b64312749d66d42bb4c Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 7 Apr 2025 08:42:20 -0700 Subject: [PATCH 3/7] Migrate to Kotlin - DebugCorePackage Summary: This diff migrates the following file to Kotlin - DebugCorePackage as part of our ongoing effort of migrating the codebase to Kotlin Changelog: [Internal] [Changed] - Migrate to Kotlin - DebugCorePackage Differential Revision: D72556867 --- .../ReactAndroid/api/ReactAndroid.api | 2 +- .../com/facebook/react/DebugCorePackage.java | 86 ------------------- .../com/facebook/react/DebugCorePackage.kt | 56 ++++++++++++ 3 files changed, 57 insertions(+), 87 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 94f52d109ec4a7..b88f3cf7b401b0 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -12,7 +12,7 @@ public class com/facebook/react/CoreModulesPackage$$ReactModuleInfoProvider : co public fun getReactModuleInfos ()Ljava/util/Map; } -public class com/facebook/react/DebugCorePackage : com/facebook/react/BaseReactPackage, com/facebook/react/ViewManagerOnDemandReactPackage { +public final class com/facebook/react/DebugCorePackage : com/facebook/react/BaseReactPackage, com/facebook/react/ViewManagerOnDemandReactPackage { public fun ()V public fun createViewManager (Lcom/facebook/react/bridge/ReactApplicationContext;Ljava/lang/String;)Lcom/facebook/react/uimanager/ViewManager; public fun getModule (Ljava/lang/String;Lcom/facebook/react/bridge/ReactApplicationContext;)Lcom/facebook/react/bridge/NativeModule; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java deleted file mode 100644 index 41d9267190834e..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.java +++ /dev/null @@ -1,86 +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; - -import androidx.annotation.Nullable; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.bridge.ModuleSpec; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.UIManager; -import com.facebook.react.module.annotations.ReactModuleList; -import com.facebook.react.module.model.ReactModuleInfo; -import com.facebook.react.module.model.ReactModuleInfoProvider; -import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.views.debuggingoverlay.DebuggingOverlayManager; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Package defining core framework modules (e.g. UIManager). It should be used for modules that - * require special integration with other framework parts (e.g. with the list of packages to load - * view managers from). - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -@ReactModuleList(nativeModules = {}) -/* package */ -public class DebugCorePackage extends BaseReactPackage implements ViewManagerOnDemandReactPackage { - private @Nullable Map mViewManagers; - - public DebugCorePackage() {} - - @Override - public @Nullable NativeModule getModule(String name, ReactApplicationContext reactContext) { - return null; - } - - @Override - public ReactModuleInfoProvider getReactModuleInfoProvider() { - return new ReactModuleInfoProvider() { - @Override - public Map getReactModuleInfos() { - return Collections.emptyMap(); - } - }; - } - - /** - * @return a map of view managers that should be registered with {@link UIManager} - */ - private Map getViewManagersMap() { - if (mViewManagers == null) { - Map viewManagers = new HashMap<>(); - viewManagers.put( - DebuggingOverlayManager.REACT_CLASS, - ModuleSpec.viewManagerSpec(DebuggingOverlayManager::new)); - mViewManagers = viewManagers; - } - return mViewManagers; - } - - @Override - public List getViewManagers(ReactApplicationContext reactContext) { - return new ArrayList<>(getViewManagersMap().values()); - } - - @Override - public Collection getViewManagerNames(ReactApplicationContext reactContext) { - return getViewManagersMap().keySet(); - } - - @Override - public @Nullable ViewManager createViewManager( - ReactApplicationContext reactContext, String viewManagerName) { - ModuleSpec spec = getViewManagersMap().get(viewManagerName); - return spec != null ? (ViewManager) spec.getProvider().get() : null; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.kt new file mode 100644 index 00000000000000..9e1cd7bc1074bb --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/DebugCorePackage.kt @@ -0,0 +1,56 @@ +/* + * 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 + +import com.facebook.react.bridge.ModuleSpec +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.UIManager +import com.facebook.react.module.annotations.ReactModuleList +import com.facebook.react.module.model.ReactModuleInfoProvider +import com.facebook.react.uimanager.ViewManager +import com.facebook.react.views.debuggingoverlay.DebuggingOverlayManager + +/** + * Package defining core framework modules (e.g. [UIManager]). It should be used for modules that + * require special integration with other framework parts (e.g. with the list of packages to load + * view managers from). + */ +@ReactModuleList(nativeModules = []) +public class DebugCorePackage public constructor() : + BaseReactPackage(), ViewManagerOnDemandReactPackage { + + /** A map of view managers that should be registered with [UIManager] */ + private val viewManagersMap: Map by + lazy(LazyThreadSafetyMode.NONE) { + mapOf( + DebuggingOverlayManager.REACT_CLASS to + ModuleSpec.viewManagerSpec { DebuggingOverlayManager() }) + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { + emptyMap() + } + + public override fun getModule( + name: String, + reactContext: ReactApplicationContext + ): NativeModule? = null + + public override fun getViewManagers(reactContext: ReactApplicationContext): List = + viewManagersMap.values.toList() + + override fun getViewManagerNames(reactContext: ReactApplicationContext): Collection = + viewManagersMap.keys + + override fun createViewManager( + reactContext: ReactApplicationContext, + viewManagerName: String + ): ViewManager<*, *>? = + viewManagersMap.getOrDefault(viewManagerName, null)?.provider?.get() as? ViewManager<*, *> +} From 58bb35a400e9ed3d9c36fa6d529415c4bd7a7721 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 7 Apr 2025 08:42:20 -0700 Subject: [PATCH 4/7] Migrate to Kotlin - DebugOverlayController Summary: This diff migrates the following file to Kotlin - DebugOverlayController as part of our ongoing effort of migrating the codebase to Kotlin Changelog: [Internal] [Changed] - DebugOverlayController.java to Kotlin Differential Revision: D72557349 --- .../devsupport/DebugOverlayController.java | 120 ------------------ .../devsupport/DebugOverlayController.kt | 87 +++++++++++++ 2 files changed, 87 insertions(+), 120 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java deleted file mode 100644 index 9db6ef475fb916..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java +++ /dev/null @@ -1,120 +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.devsupport; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.graphics.PixelFormat; -import android.net.Uri; -import android.provider.Settings; -import android.view.WindowManager; -import android.widget.FrameLayout; -import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.common.ReactConstants; - -/** - * Helper class for controlling overlay view with FPS and JS FPS info that gets added directly - * to @{link WindowManager} instance. - */ -/* package */ @Nullsafe(Nullsafe.Mode.LOCAL) -class DebugOverlayController { - - public static void requestPermission(Context context) { - // Get permission to show debug overlay in dev builds. - if (!Settings.canDrawOverlays(context)) { - Intent intent = - new Intent( - Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + context.getPackageName())); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - FLog.w( - ReactConstants.TAG, - "Overlay permissions needs to be granted in order for react native apps to run in dev" - + " mode"); - if (canHandleIntent(context, intent)) { - context.startActivity(intent); - } - } - } - - private static boolean permissionCheck(Context context) { - // Get permission to show debug overlay in dev builds. - // overlay permission not yet granted - return Settings.canDrawOverlays(context); - } - - private static boolean hasPermission(Context context, String permission) { - try { - PackageInfo info = - context - .getPackageManager() - // NULLSAFE_FIXME[Nullable Dereference] - .getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); - if (info.requestedPermissions != null) { - for (String p : info.requestedPermissions) { - if (p.equals(permission)) { - return true; - } - } - } - } catch (PackageManager.NameNotFoundException e) { - FLog.e(ReactConstants.TAG, "Error while retrieving package info", e); - } - return false; - } - - private static boolean canHandleIntent(Context context, Intent intent) { - PackageManager packageManager = context.getPackageManager(); - return packageManager != null && intent.resolveActivity(packageManager) != null; - } - - private final WindowManager mWindowManager; - private final ReactContext mReactContext; - - private @Nullable FrameLayout mFPSDebugViewContainer; - - public DebugOverlayController(ReactContext reactContext) { - mReactContext = reactContext; - mWindowManager = (WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE); - } - - public void setFpsDebugViewVisible(final boolean fpsDebugViewVisible) { - UiThreadUtil.runOnUiThread( - new Runnable() { - @Override - public void run() { - if (fpsDebugViewVisible && mFPSDebugViewContainer == null) { - if (!permissionCheck(mReactContext)) { - FLog.d(ReactConstants.TAG, "Wait for overlay permission to be set"); - return; - } - mFPSDebugViewContainer = new FpsView(mReactContext); - WindowManager.LayoutParams params = - new WindowManager.LayoutParams( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.MATCH_PARENT, - WindowOverlayCompat.TYPE_SYSTEM_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, - PixelFormat.TRANSLUCENT); - mWindowManager.addView(mFPSDebugViewContainer, params); - } else if (!fpsDebugViewVisible && mFPSDebugViewContainer != null) { - mFPSDebugViewContainer.removeAllViews(); - mWindowManager.removeView(mFPSDebugViewContainer); - mFPSDebugViewContainer = null; - } - } - }); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.kt new file mode 100644 index 00000000000000..3467d9af749473 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.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.devsupport + +import android.content.Context +import android.content.Intent +import android.graphics.PixelFormat +import android.net.Uri +import android.provider.Settings +import android.view.WindowManager +import android.widget.FrameLayout +import com.facebook.common.logging.FLog +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.common.ReactConstants + +/** + * Helper class for controlling overlay view with FPS and JS FPS info that gets added directly to + * [WindowManager] instance. + */ +internal class DebugOverlayController(private val reactContext: ReactContext) { + private val windowManager = reactContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager + + private var fpsDebugViewContainer: FrameLayout? = null + + fun setFpsDebugViewVisible(fpsDebugViewVisible: Boolean) { + UiThreadUtil.runOnUiThread( + Runnable { + if (fpsDebugViewVisible && fpsDebugViewContainer == null) { + if (!permissionCheck(reactContext)) { + FLog.d(ReactConstants.TAG, "Wait for overlay permission to be set") + return@Runnable + } + fpsDebugViewContainer = FpsView(reactContext) + val params = + WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + WindowOverlayCompat.TYPE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, + PixelFormat.TRANSLUCENT) + windowManager.addView(fpsDebugViewContainer, params) + } else if (!fpsDebugViewVisible && fpsDebugViewContainer != null) { + fpsDebugViewContainer?.removeAllViews() + windowManager.removeView(fpsDebugViewContainer) + fpsDebugViewContainer = null + } + }) + } + + companion object { + @JvmStatic + fun requestPermission(context: Context) { + // Get permission to show debug overlay in dev builds. + if (!Settings.canDrawOverlays(context)) { + val intent = + Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + context.packageName)) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + FLog.w( + ReactConstants.TAG, + "Overlay permissions needs to be granted in order for react native apps to run in dev mode") + if (canHandleIntent(context, intent)) { + context.startActivity(intent) + } + } + } + + private fun permissionCheck(context: Context): Boolean { + // Get permission to show debug overlay in dev builds. + // overlay permission not yet granted + return Settings.canDrawOverlays(context) + } + + private fun canHandleIntent(context: Context, intent: Intent): Boolean { + val packageManager = context.packageManager + return packageManager != null && intent.resolveActivity(packageManager) != null + } + } +} From e790ad817b8ccf90776724ff97ca9baaaae57e63 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 7 Apr 2025 08:42:20 -0700 Subject: [PATCH 5/7] Migrate to Kotlin - BridgelessDevSupportManager Summary: This diff migrates the following file to Kotlin - BridgelessDevSupportManager as part of our ongoing effort of migrating the codebase to Kotlin Changelog: [Internal] [Changed] - BridgelessDevSupportManager to Kotlin Differential Revision: D72558016 --- .../BridgelessDevSupportManager.java | 124 ------------------ .../devsupport/BridgelessDevSupportManager.kt | 104 +++++++++++++++ 2 files changed, 104 insertions(+), 124 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.java deleted file mode 100644 index 355a88d1a0dda5..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.java +++ /dev/null @@ -1,124 +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.devsupport; - -import android.content.Context; -import androidx.annotation.Nullable; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.react.bridge.JSBundleLoader; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.common.SurfaceDelegateFactory; -import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; -import com.facebook.react.devsupport.interfaces.DevLoadingViewManager; -import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback; -import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager; -import com.facebook.react.devsupport.interfaces.RedBoxHandler; -import com.facebook.react.packagerconnection.RequestHandler; -import java.util.Map; - -/** - * An implementation of {@link com.facebook.react.devsupport.interfaces.DevSupportManager} that - * extends the functionality in {@link DevSupportManagerBase} with some additional, more flexible - * APIs for asynchronously loading the JS bundle. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -class BridgelessDevSupportManager extends DevSupportManagerBase { - - public BridgelessDevSupportManager( - Context context, - ReactInstanceDevHelper reactInstanceManagerHelper, - @Nullable String packagerPathForJSBundleName) { - this( - context.getApplicationContext(), - reactInstanceManagerHelper, - packagerPathForJSBundleName, - true /* enableOnCreate */, - null /* redBoxHandler */, - null /* devBundleDownloadListener */, - 2 /* minNumShakes */, - null /* customPackagerCommandHandlers */, - null /* surfaceDelegateFactory */, - null /* devLoadingViewManager */, - null /* pausedInDebuggerOverlayManager */); - } - - /** - * This constructor mirrors the same constructor we have for {@link BridgeDevSupportManager} and - * is kept for backward compatibility. - */ - public BridgelessDevSupportManager( - Context applicationContext, - ReactInstanceDevHelper reactInstanceManagerHelper, - @Nullable String packagerPathForJSBundleName, - boolean enableOnCreate, - @Nullable RedBoxHandler redBoxHandler, - @Nullable DevBundleDownloadListener devBundleDownloadListener, - int minNumShakes, - @Nullable Map customPackagerCommandHandlers, - @Nullable SurfaceDelegateFactory surfaceDelegateFactory, - @Nullable DevLoadingViewManager devLoadingViewManager, - @Nullable PausedInDebuggerOverlayManager pausedInDebuggerOverlayManager) { - super( - applicationContext, - reactInstanceManagerHelper, - packagerPathForJSBundleName, - enableOnCreate, - redBoxHandler, - devBundleDownloadListener, - minNumShakes, - customPackagerCommandHandlers, - surfaceDelegateFactory, - devLoadingViewManager, - pausedInDebuggerOverlayManager); - } - - @Override - protected String getUniqueTag() { - return "Bridgeless"; - } - - @Override - public void loadSplitBundleFromServer( - final String bundlePath, final DevSplitBundleCallback callback) { - fetchSplitBundleAndCreateBundleLoader( - bundlePath, - new CallbackWithBundleLoader() { - @Override - public void onSuccess(final JSBundleLoader bundleLoader) { - try { - mReactInstanceDevHelper.loadBundle(bundleLoader).waitForCompletion(); - String bundleURL = getDevServerHelper().getDevServerSplitBundleURL(bundlePath); - ReactContext reactContext = mReactInstanceDevHelper.getCurrentReactContext(); - if (reactContext != null) { - reactContext.getJSModule(HMRClient.class).registerBundle(bundleURL); - } - callback.onSuccess(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException( - "[BridgelessDevSupportManager]: Got interrupted while loading bundle", e); - } - } - - @Override - public void onError(String url, Throwable cause) { - callback.onError(url, cause); - } - }); - } - - @Override - public void handleReloadJS() { - UiThreadUtil.assertOnUiThread(); - - // dismiss redbox if exists - hideRedboxDialog(); - mReactInstanceDevHelper.reload("BridgelessDevSupportManager.handleReloadJS()"); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt new file mode 100644 index 00000000000000..fd747969635672 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt @@ -0,0 +1,104 @@ +/* + * 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.devsupport + +import android.content.Context +import com.facebook.react.bridge.JSBundleLoader +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.common.SurfaceDelegateFactory +import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener +import com.facebook.react.devsupport.interfaces.DevLoadingViewManager +import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback +import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager +import com.facebook.react.devsupport.interfaces.RedBoxHandler +import com.facebook.react.packagerconnection.RequestHandler + +/** + * An implementation of [DevSupportManager] that extends the functionality in + * [DevSupportManagerBase] with some additional, more flexible APIs for asynchronously loading the + * JS bundle. + * + * @constructor The primary constructor mirrors the same constructor we have for + * [BridgeDevSupportManager] and + * * is kept for backward compatibility. + */ +internal class BridgelessDevSupportManager( + applicationContext: Context, + reactInstanceManagerHelper: ReactInstanceDevHelper, + packagerPathForJSBundleName: String?, + enableOnCreate: Boolean, + redBoxHandler: RedBoxHandler?, + devBundleDownloadListener: DevBundleDownloadListener?, + minNumShakes: Int, + customPackagerCommandHandlers: Map?, + surfaceDelegateFactory: SurfaceDelegateFactory?, + devLoadingViewManager: DevLoadingViewManager?, + pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager? +) : + DevSupportManagerBase( + applicationContext, + reactInstanceManagerHelper, + packagerPathForJSBundleName, + enableOnCreate, + redBoxHandler, + devBundleDownloadListener, + minNumShakes, + customPackagerCommandHandlers, + surfaceDelegateFactory, + devLoadingViewManager, + pausedInDebuggerOverlayManager) { + + constructor( + context: Context, + reactInstanceManagerHelper: ReactInstanceDevHelper, + packagerPathForJSBundleName: String? + ) : this( + applicationContext = context.applicationContext, + reactInstanceManagerHelper = reactInstanceManagerHelper, + packagerPathForJSBundleName = packagerPathForJSBundleName, + enableOnCreate = true, + redBoxHandler = null, + devBundleDownloadListener = null, + minNumShakes = 2, + customPackagerCommandHandlers = null, + surfaceDelegateFactory = null, + devLoadingViewManager = null, + pausedInDebuggerOverlayManager = null) + + override fun getUniqueTag(): String = "Bridgeless" + + override fun loadSplitBundleFromServer(bundlePath: String, callback: DevSplitBundleCallback) { + fetchSplitBundleAndCreateBundleLoader( + bundlePath, + object : CallbackWithBundleLoader { + override fun onSuccess(bundleLoader: JSBundleLoader) { + try { + mReactInstanceDevHelper.loadBundle(bundleLoader).waitForCompletion() + val bundleURL = devServerHelper.getDevServerSplitBundleURL(bundlePath) + val reactContext = mReactInstanceDevHelper.currentReactContext + reactContext?.getJSModule(HMRClient::class.java)?.registerBundle(bundleURL) + callback.onSuccess() + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + throw RuntimeException( + "[BridgelessDevSupportManager]: Got interrupted while loading bundle", e) + } + } + + override fun onError(url: String, cause: Throwable) = callback.onError(url, cause) + }) + } + + override fun handleReloadJS() { + UiThreadUtil.assertOnUiThread() + // dismiss redbox if exists + hideRedboxDialog() + mReactInstanceDevHelper.reload("BridgelessDevSupportManager.handleReloadJS()") + } +} From 21f071957402f4fa69dcfd8f8953ed17a585784e Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 7 Apr 2025 08:42:20 -0700 Subject: [PATCH 6/7] Migrate to Kotlin - BridgeDevSupportManager Summary: This diff migrates the following file to Kotlin - BridgeDevSupportManager as part of our ongoing effort of migrating the codebase to Kotlin Changelog: [Internal] [Changed] - BridgeDevSupportManager to Kotlin Differential Revision: D72558373 --- .../devsupport/BridgeDevSupportManager.java | 138 ------------------ .../devsupport/BridgeDevSupportManager.kt | 119 +++++++++++++++ 2 files changed, 119 insertions(+), 138 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java deleted file mode 100644 index fadd6a3e4dd6ef..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.java +++ /dev/null @@ -1,138 +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.devsupport; - -import android.content.Context; -import androidx.annotation.Nullable; -import com.facebook.debug.holder.PrinterHolder; -import com.facebook.debug.tags.ReactDebugOverlayTags; -import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.CatalystInstance; -import com.facebook.react.bridge.JSBundleLoader; -import com.facebook.react.bridge.ReactMarker; -import com.facebook.react.bridge.ReactMarkerConstants; -import com.facebook.react.bridge.UiThreadUtil; -import com.facebook.react.common.SurfaceDelegateFactory; -import com.facebook.react.common.annotations.internal.LegacyArchitecture; -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; -import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; -import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; -import com.facebook.react.devsupport.interfaces.DevLoadingViewManager; -import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback; -import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager; -import com.facebook.react.devsupport.interfaces.RedBoxHandler; -import com.facebook.react.packagerconnection.RequestHandler; -import java.util.Map; - -/** - * Interface for accessing and interacting with development features. Following features - * are supported through this manager class: - * 1) Displaying JS errors (aka RedBox) - * 2) Displaying developers menu (Reload JS, Debug JS) - * 3) Communication with developer server in order to download updated JS bundle - * 4) Starting/stopping broadcast receiver for js reload signals - * 5) Starting/stopping motion sensor listener that recognize shake gestures which in turn may - * trigger developers menu. - * 6) Launching developers settings view - * - * This class automatically monitors the state of registered views and activities to which they are - * bound to make sure that we don't display overlay or that we we don't listen for sensor events - * when app is backgrounded. - * - * {@link com.facebook.react.ReactInstanceManager} implementation is responsible for instantiating - * this class as well as for populating with a reference to {@link CatalystInstance} whenever - * instance manager recreates it (through {@link #onNewReactContextCreated). Also, instance manager - * is responsible for enabling/disabling dev support in case when app is backgrounded or when all - * the views has been detached from the instance (through {@link #setDevSupportEnabled} method). - */ -@LegacyArchitecture -public final class BridgeDevSupportManager extends DevSupportManagerBase { - - static { - LegacyArchitectureLogger.assertLegacyArchitecture( - "BridgeDevSupportManager", LegacyArchitectureLogLevel.WARNING); - } - - private boolean mIsSamplingProfilerEnabled = false; - - public BridgeDevSupportManager( - Context applicationContext, - ReactInstanceDevHelper reactInstanceManagerHelper, - @Nullable String packagerPathForJSBundleName, - boolean enableOnCreate, - @Nullable RedBoxHandler redBoxHandler, - @Nullable DevBundleDownloadListener devBundleDownloadListener, - int minNumShakes, - @Nullable Map customPackagerCommandHandlers, - @Nullable SurfaceDelegateFactory surfaceDelegateFactory, - @Nullable DevLoadingViewManager devLoadingViewManager, - @Nullable PausedInDebuggerOverlayManager pausedInDebuggerOverlayManager) { - super( - applicationContext, - reactInstanceManagerHelper, - packagerPathForJSBundleName, - enableOnCreate, - redBoxHandler, - devBundleDownloadListener, - minNumShakes, - customPackagerCommandHandlers, - surfaceDelegateFactory, - devLoadingViewManager, - pausedInDebuggerOverlayManager); - } - - @Override - protected String getUniqueTag() { - return "Bridge"; - } - - @Override - public void loadSplitBundleFromServer( - final String bundlePath, final DevSplitBundleCallback callback) { - fetchSplitBundleAndCreateBundleLoader( - bundlePath, - new CallbackWithBundleLoader() { - @Override - public void onSuccess(JSBundleLoader bundleLoader) { - bundleLoader.loadScript(getCurrentReactContext().getCatalystInstance()); - getCurrentReactContext() - .getJSModule(HMRClient.class) - .registerBundle(getDevServerHelper().getDevServerSplitBundleURL(bundlePath)); - callback.onSuccess(); - } - - @Override - public void onError(String url, Throwable cause) { - callback.onError(url, cause); - } - }); - } - - @Override - public void handleReloadJS() { - - UiThreadUtil.assertOnUiThread(); - - ReactMarker.logMarker( - ReactMarkerConstants.RELOAD, - getDevSettings().getPackagerConnectionSettings().getDebugServerHost()); - - // dismiss redbox if exists - hideRedboxDialog(); - - PrinterHolder.getPrinter() - .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: load from Server"); - String bundleURL = - getDevServerHelper().getDevServerBundleURL(Assertions.assertNotNull(getJSAppBundleName())); - reloadJSFromServer( - bundleURL, - () -> - UiThreadUtil.runOnUiThread( - () -> getReactInstanceDevHelper().onJSBundleLoadedFromServer())); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.kt new file mode 100644 index 00000000000000..e24c4fd2c69ba2 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgeDevSupportManager.kt @@ -0,0 +1,119 @@ +/* + * 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.devsupport + +import android.content.Context +import com.facebook.debug.holder.PrinterHolder.printer +import com.facebook.debug.tags.ReactDebugOverlayTags +import com.facebook.infer.annotation.Assertions +import com.facebook.react.bridge.JSBundleLoader +import com.facebook.react.bridge.ReactMarker +import com.facebook.react.bridge.ReactMarkerConstants +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.common.SurfaceDelegateFactory +import com.facebook.react.common.annotations.internal.LegacyArchitecture +import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel +import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger.assertLegacyArchitecture +import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener +import com.facebook.react.devsupport.interfaces.DevLoadingViewManager +import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback +import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager +import com.facebook.react.devsupport.interfaces.RedBoxHandler +import com.facebook.react.packagerconnection.RequestHandler + +/** + * Interface for accessing and interacting with development features. Following features are + * supported through this manager class: + * 1) Displaying JS errors (aka RedBox) + * 2) Displaying developers menu (Reload JS, Debug JS) + * 3) Communication with developer server in order to download updated JS bundle + * 4) Starting/stopping broadcast receiver for js reload signals + * 5) Starting/stopping motion sensor listener that recognize shake gestures which in turn may + * trigger developers menu. + * 6) Launching developers settings view + * + * This class automatically monitors the state of registered views and activities to which they are + * bound to make sure that we don't display overlay or that we we don't listen for sensor events + * when app is backgrounded. + * + * [com.facebook.react.ReactInstanceManager] implementation is responsible for instantiating this + * class as well as for populating with a reference to [com.facebook.react.bridge.CatalystInstance] + * whenever instance manager recreates it (through [onNewReactContextCreated]). Also, instance + * manager is responsible for enabling/disabling dev support in case when app is backgrounded or + * when all the views has been detached from the instance (through `setDevSupportEnabled` method). + */ +@LegacyArchitecture +public class BridgeDevSupportManager( + applicationContext: Context, + reactInstanceManagerHelper: ReactInstanceDevHelper, + packagerPathForJSBundleName: String?, + enableOnCreate: Boolean, + redBoxHandler: RedBoxHandler?, + devBundleDownloadListener: DevBundleDownloadListener?, + minNumShakes: Int, + customPackagerCommandHandlers: Map?, + surfaceDelegateFactory: SurfaceDelegateFactory?, + devLoadingViewManager: DevLoadingViewManager?, + pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager? +) : + DevSupportManagerBase( + applicationContext, + reactInstanceManagerHelper, + packagerPathForJSBundleName, + enableOnCreate, + redBoxHandler, + devBundleDownloadListener, + minNumShakes, + customPackagerCommandHandlers, + surfaceDelegateFactory, + devLoadingViewManager, + pausedInDebuggerOverlayManager) { + + override fun getUniqueTag(): String = "Bridge" + + override fun loadSplitBundleFromServer(bundlePath: String, callback: DevSplitBundleCallback) { + fetchSplitBundleAndCreateBundleLoader( + bundlePath, + object : CallbackWithBundleLoader { + override fun onSuccess(bundleLoader: JSBundleLoader) { + val context = + requireNotNull(currentReactContext) { + "Failed to load split bundle from server due to DevSupportManager.currentReactContext being null" + } + bundleLoader.loadScript(context.catalystInstance) + context + .getJSModule(HMRClient::class.java) + .registerBundle(devServerHelper.getDevServerSplitBundleURL(bundlePath)) + callback.onSuccess() + } + + override fun onError(url: String, cause: Throwable) = callback.onError(url, cause) + }) + } + + override fun handleReloadJS() { + UiThreadUtil.assertOnUiThread() + ReactMarker.logMarker( + ReactMarkerConstants.RELOAD, devSettings.packagerConnectionSettings.debugServerHost) + + // dismiss redbox if exists + hideRedboxDialog() + + printer.logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: load from Server") + val bundleURL = devServerHelper.getDevServerBundleURL(Assertions.assertNotNull(jsAppBundleName)) + reloadJSFromServer(bundleURL) { + UiThreadUtil.runOnUiThread { reactInstanceDevHelper.onJSBundleLoadedFromServer() } + } + } + + private companion object { + init { + assertLegacyArchitecture("BridgeDevSupportManager", LegacyArchitectureLogLevel.WARNING) + } + } +} From 5d948a4349def2144d0d565747dc366e3112d9a0 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 7 Apr 2025 08:46:14 -0700 Subject: [PATCH 7/7] Migrate to Kotlin - MultipartStreamReader (#50519) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50519 This diff migrates the following file to Kotlin - MultipartStreamReader as part of our ongoing effort of migrating the codebase to Kotlin Changelog: [Internal] [Changed] - MultipartStreamReader to Kotlin Differential Revision: D72561124 --- .../devsupport/MultipartStreamReader.java | 170 ------------------ .../react/devsupport/MultipartStreamReader.kt | 168 +++++++++++++++++ 2 files changed, 168 insertions(+), 170 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java deleted file mode 100644 index 4c99f9bee75fbb..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java +++ /dev/null @@ -1,170 +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.devsupport; - -import com.facebook.infer.annotation.Nullsafe; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import okio.Buffer; -import okio.BufferedSource; -import okio.ByteString; - -/** Utility class to parse the body of a response of type multipart/mixed. */ -@Nullsafe(Nullsafe.Mode.LOCAL) -class MultipartStreamReader { - // Standard line separator for HTTP. - private static final String CRLF = "\r\n"; - - private final BufferedSource mSource; - private final String mBoundary; - private long mLastProgressEvent; - - public interface ChunkListener { - /** Invoked when a chunk of a multipart response is fully downloaded. */ - void onChunkComplete(Map headers, Buffer body, boolean isLastChunk) - throws IOException; - - /** Invoked as bytes of the current chunk are read. */ - void onChunkProgress(Map headers, long loaded, long total) throws IOException; - } - - public MultipartStreamReader(BufferedSource source, String boundary) { - mSource = source; - mBoundary = boundary; - } - - private Map parseHeaders(Buffer data) { - Map headers = new HashMap<>(); - - String text = data.readUtf8(); - String[] lines = text.split(CRLF); - for (String line : lines) { - int indexOfSeparator = line.indexOf(":"); - if (indexOfSeparator == -1) { - continue; - } - - String key = line.substring(0, indexOfSeparator).trim(); - String value = line.substring(indexOfSeparator + 1).trim(); - headers.put(key, value); - } - - return headers; - } - - private void emitChunk(Buffer chunk, boolean done, ChunkListener listener) throws IOException { - ByteString marker = ByteString.encodeUtf8(CRLF + CRLF); - long indexOfMarker = chunk.indexOf(marker); - if (indexOfMarker == -1) { - listener.onChunkComplete(Collections.emptyMap(), chunk, done); - } else { - Buffer headers = new Buffer(); - Buffer body = new Buffer(); - chunk.read(headers, indexOfMarker); - chunk.skip(marker.size()); - chunk.readAll(body); - listener.onChunkComplete(parseHeaders(headers), body, done); - } - } - - private void emitProgress( - Map headers, long contentLength, boolean isFinal, ChunkListener listener) - throws IOException { - if (headers == null || listener == null) { - return; - } - - long currentTime = System.currentTimeMillis(); - if (currentTime - mLastProgressEvent > 16 || isFinal) { - mLastProgressEvent = currentTime; - long headersContentLength = - headers.get("Content-Length") != null ? Long.parseLong(headers.get("Content-Length")) : 0; - listener.onChunkProgress(headers, contentLength, headersContentLength); - } - } - - /** - * Reads all parts of the multipart response and execute the listener for each chunk received. - * - * @param listener Listener invoked when chunks are received. - * @return If the read was successful - */ - public boolean readAllParts(ChunkListener listener) throws IOException { - ByteString delimiter = ByteString.encodeUtf8(CRLF + "--" + mBoundary + CRLF); - ByteString closeDelimiter = ByteString.encodeUtf8(CRLF + "--" + mBoundary + "--" + CRLF); - ByteString headersDelimiter = ByteString.encodeUtf8(CRLF + CRLF); - - int bufferLen = 4 * 1024; - long chunkStart = 0; - long bytesSeen = 0; - Buffer content = new Buffer(); - Map currentHeaders = null; - long currentHeadersLength = 0; - - while (true) { - boolean isCloseDelimiter = false; - - // Search only a subset of chunk that we haven't seen before + few bytes - // to allow for the edge case when the delimiter is cut by read call. - long searchStart = Math.max(bytesSeen - closeDelimiter.size(), chunkStart); - long indexOfDelimiter = content.indexOf(delimiter, searchStart); - if (indexOfDelimiter == -1) { - isCloseDelimiter = true; - indexOfDelimiter = content.indexOf(closeDelimiter, searchStart); - } - - if (indexOfDelimiter == -1) { - bytesSeen = content.size(); - - if (currentHeaders == null) { - long indexOfHeaders = content.indexOf(headersDelimiter, searchStart); - if (indexOfHeaders >= 0) { - mSource.read(content, indexOfHeaders); - Buffer headers = new Buffer(); - content.copyTo(headers, searchStart, indexOfHeaders - searchStart); - currentHeadersLength = headers.size() + headersDelimiter.size(); - currentHeaders = parseHeaders(headers); - } - } else { - emitProgress(currentHeaders, content.size() - currentHeadersLength, false, listener); - } - - long bytesRead = mSource.read(content, bufferLen); - if (bytesRead <= 0) { - return false; - } - continue; - } - - long chunkEnd = indexOfDelimiter; - long length = chunkEnd - chunkStart; - - // Ignore preamble - if (chunkStart > 0) { - Buffer chunk = new Buffer(); - content.skip(chunkStart); - content.read(chunk, length); - // NULLSAFE_FIXME[Parameter Not Nullable] - emitProgress(currentHeaders, chunk.size() - currentHeadersLength, true, listener); - emitChunk(chunk, isCloseDelimiter, listener); - currentHeaders = null; - currentHeadersLength = 0; - } else { - content.skip(chunkEnd); - } - - if (isCloseDelimiter) { - return true; - } - - bytesSeen = chunkStart = delimiter.size(); - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.kt new file mode 100644 index 00000000000000..502e68c629189b --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.kt @@ -0,0 +1,168 @@ +/* + * 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. + */ + +@file:Suppress("DEPRECATION_ERROR") // Conflicting okio versions + +package com.facebook.react.devsupport + +import java.io.IOException +import kotlin.math.max +import okio.Buffer +import okio.BufferedSource +import okio.ByteString + +/** Utility class to parse the body of a response of type multipart/mixed. */ +internal class MultipartStreamReader( + private val source: BufferedSource, + private val boundary: String +) { + private var lastProgressEvent: Long = 0 + + interface ChunkListener { + /** Invoked when a chunk of a multipart response is fully downloaded. */ + @Throws(IOException::class) + fun onChunkComplete(headers: Map, body: Buffer?, isLastChunk: Boolean) + + /** Invoked as bytes of the current chunk are read. */ + @Throws(IOException::class) + fun onChunkProgress(headers: Map, loaded: Long, total: Long) + } + + /** + * Reads all parts of the multipart response and execute the listener for each chunk received. + * + * @param listener Listener invoked when chunks are received. + * @return If the read was successful + */ + @Throws(IOException::class) + fun readAllParts(listener: ChunkListener): Boolean { + val delimiter: ByteString = ByteString.encodeUtf8("$CRLF--$boundary$CRLF") + val closeDelimiter: ByteString = ByteString.encodeUtf8("$CRLF--$boundary--$CRLF") + val headersDelimiter: ByteString = ByteString.encodeUtf8(CRLF + CRLF) + + val bufferLen = 4 * 1024 + var chunkStart: Long = 0 + var bytesSeen: Long = 0 + val content = Buffer() + var currentHeaders: Map? = null + var currentHeadersLength: Long = 0 + + while (true) { + var isCloseDelimiter = false + + // Search only a subset of chunk that we haven't seen before + few bytes + // to allow for the edge case when the delimiter is cut by read call. + val searchStart = + max((bytesSeen - closeDelimiter.size()).toDouble(), chunkStart.toDouble()).toLong() + var indexOfDelimiter = content.indexOf(delimiter, searchStart) + if (indexOfDelimiter == -1L) { + isCloseDelimiter = true + indexOfDelimiter = content.indexOf(closeDelimiter, searchStart) + } + + if (indexOfDelimiter == -1L) { + bytesSeen = content.size() + + if (currentHeaders == null) { + val indexOfHeaders = content.indexOf(headersDelimiter, searchStart) + if (indexOfHeaders >= 0) { + source.read(content, indexOfHeaders) + val headers = Buffer() + content.copyTo(headers, searchStart, indexOfHeaders - searchStart) + currentHeadersLength = headers.size() + headersDelimiter.size() + currentHeaders = parseHeaders(headers) + } + } else { + emitProgress(currentHeaders, content.size() - currentHeadersLength, false, listener) + } + + val bytesRead = source.read(content, bufferLen.toLong()) + if (bytesRead <= 0) { + return false + } + continue + } + + val chunkEnd = indexOfDelimiter + val length = chunkEnd - chunkStart + + // Ignore preamble + if (chunkStart > 0) { + val chunk = Buffer() + content.skip(chunkStart) + content.read(chunk, length) + // NULLSAFE_FIXME[Parameter Not Nullable] + emitProgress(currentHeaders, chunk.size() - currentHeadersLength, true, listener) + emitChunk(chunk, isCloseDelimiter, listener) + currentHeaders = null + currentHeadersLength = 0 + } else { + content.skip(chunkEnd) + } + if (isCloseDelimiter) { + return true + } + chunkStart = delimiter.size().toLong() + bytesSeen = chunkStart + } + } + + private fun parseHeaders(data: Buffer): Map { + val headers: MutableMap = mutableMapOf() + val text = data.readUtf8() + val lines = text.split(CRLF.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (line in lines) { + val indexOfSeparator = line.indexOf(":") + if (indexOfSeparator == -1) { + continue + } + val key = line.substring(0, indexOfSeparator).trim { it <= ' ' } + val value = line.substring(indexOfSeparator + 1).trim { it <= ' ' } + headers[key] = value + } + return headers + } + + @Throws(IOException::class) + private fun emitChunk(chunk: Buffer, done: Boolean, listener: ChunkListener) { + val marker: ByteString = ByteString.encodeUtf8(CRLF + CRLF) + val indexOfMarker = chunk.indexOf(marker) + if (indexOfMarker == -1L) { + listener.onChunkComplete(emptyMap(), chunk, done) + } else { + val headers = Buffer() + val body = Buffer() + chunk.read(headers, indexOfMarker) + chunk.skip(marker.size().toLong()) + chunk.readAll(body) + listener.onChunkComplete(parseHeaders(headers), body, done) + } + } + + @Throws(IOException::class) + private fun emitProgress( + headers: Map?, + contentLength: Long, + isFinal: Boolean, + listener: ChunkListener? + ) { + if (listener == null || headers == null) { + return + } + val currentTime = System.currentTimeMillis() + if (currentTime - lastProgressEvent > 16 || isFinal) { + lastProgressEvent = currentTime + val headersContentLength = headers.getOrDefault("Content-Length", "0").toLong() + listener.onChunkProgress(headers, contentLength, headersContentLength) + } + } + + companion object { + // Standard line separator for HTTP. + private const val CRLF = "\r\n" + } +}