1 // Copyright 2020-present 650 Industries. All rights reserved.
2 package versioned.host.exp.exponent
3 
4 import com.facebook.react.ReactInstanceManager
5 import com.facebook.react.ReactRootView
6 import com.facebook.react.TurboReactPackage
7 import com.facebook.react.bridge.NativeModule
8 import com.facebook.react.bridge.ReactApplicationContext
9 import com.facebook.react.bridge.ReactMarker
10 import com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_END
11 import com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_START
12 import com.facebook.react.module.annotations.ReactModule
13 import com.facebook.react.module.annotations.ReactModuleList
14 import com.facebook.react.module.model.ReactModuleInfo
15 import com.facebook.react.module.model.ReactModuleInfoProvider
16 import com.facebook.react.modules.intent.IntentModule
17 import com.facebook.react.turbomodule.core.interfaces.TurboModule
18 import com.facebook.react.uimanager.ReanimatedUIManager
19 import com.facebook.react.uimanager.UIManagerModule
20 import com.facebook.react.uimanager.ViewManager
21 import com.facebook.systrace.Systrace
22 import com.swmansion.reanimated.ReanimatedModule
23 import com.swmansion.reanimated.ReanimatedUIManagerFactory
24 import expo.modules.manifests.core.Manifest
25 import host.exp.exponent.experience.ReactNativeActivity
26 import host.exp.exponent.kernel.KernelConstants
27 import host.exp.expoview.Exponent
28 import versioned.host.exp.exponent.modules.internal.ExponentIntentModule
29 
30 /** Package defining basic modules and view managers.  */
31 @ReactModuleList(
32   nativeModules = [
33     ExponentIntentModule::class,
34     ReanimatedModule::class,
35     ReanimatedUIManager::class,
36   ]
37 )
38 class ExpoTurboPackage(
39   private val experienceProperties: Map<String, Any?>,
40   private val manifest: Manifest
41 ) : TurboReactPackage() {
42   // Get the hosted `ReactInstanceManager` by current Activity
43   private val reactInstanceManager: ReactInstanceManager?
44     get() {
45       val currentActivity = Exponent.instance.currentActivity as? ReactNativeActivity ?: return null
46       return (currentActivity.rootView as? ReactRootView)?.reactInstanceManager
47     }
48 
49   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
50     return listOf()
51   }
52 
53   override fun getModule(name: String, context: ReactApplicationContext): NativeModule? {
54     val isVerified = manifest.isVerified()
55     return when (name) {
56       IntentModule.NAME -> ExponentIntentModule(
57         context,
58         experienceProperties
59       )
60       ReanimatedModule.NAME -> ReanimatedModule(context)
61       ReanimatedUIManager.NAME -> createReanimatedUIManager(context) ?: throw RuntimeException("Cannot create reanimated uimanager")
62       else -> null
63     }
64   }
65 
66   override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
67     return try {
68       // TODO(Bacon): Does this need to reflect ExpoTurboPackage$$ReactModuleInfoProvider ?
69       val reactModuleInfoProviderClass = Class.forName("com.facebook.react.shell.MainReactPackage$\$ReactModuleInfoProvider")
70       reactModuleInfoProviderClass.newInstance() as ReactModuleInfoProvider
71     } catch (e: ClassNotFoundException) {
72       // In OSS case, the annotation processor does not run. We fall back on creating this by hand
73       val moduleList: Array<Class<out NativeModule?>> = arrayOf(
74         ExponentIntentModule::class.java,
75         ReanimatedModule::class.java,
76         ReanimatedUIManager::class.java,
77       )
78       val reactModuleInfoMap = mutableMapOf<String, ReactModuleInfo>()
79       for (moduleClass in moduleList) {
80         val reactModule = moduleClass.getAnnotation(ReactModule::class.java)!!
81         var canOverrideExistingModule = reactModule.canOverrideExistingModule
82         val isTurbo = TurboModule::class.java.isAssignableFrom(moduleClass)
83         if (reactModule.name == ReanimatedUIManager.NAME) {
84           if (!shouldOverrideUIManagerForReanimated()) {
85             continue
86           }
87           canOverrideExistingModule = true
88         }
89         reactModuleInfoMap[reactModule.name] = ReactModuleInfo(
90           reactModule.name,
91           moduleClass.name,
92           canOverrideExistingModule,
93           reactModule.needsEagerInit,
94           reactModule.hasConstants,
95           reactModule.isCxxModule,
96           isTurbo
97         )
98       }
99       ReactModuleInfoProvider { reactModuleInfoMap }
100     } catch (e: InstantiationException) {
101       throw RuntimeException(
102         "No ReactModuleInfoProvider for CoreModulesPackage$\$ReactModuleInfoProvider", e
103       )
104     } catch (e: IllegalAccessException) {
105       throw RuntimeException(
106         "No ReactModuleInfoProvider for CoreModulesPackage$\$ReactModuleInfoProvider", e
107       )
108     }
109   }
110 
111   private fun createReanimatedUIManager(reactContext: ReactApplicationContext): UIManagerModule? {
112     val reactInstanceManager = this.reactInstanceManager ?: return null
113     ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_START)
114     Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createUIManagerModule")
115     val minTimeLeftInFrameForNonBatchedOperationMs = -1
116     return try {
117       ReanimatedUIManagerFactory.create(
118         reactContext,
119         reactInstanceManager.getOrCreateViewManagers(reactContext),
120         minTimeLeftInFrameForNonBatchedOperationMs
121       )
122     } finally {
123       Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE)
124       ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_END)
125     }
126   }
127 
128   // Reanimated UIManager requires `ReactInstanceManager`,
129   // for headless mode we cannot get the hosted `ReactInstanceManager` from current Activity,
130   // in this case, we don't override UIManager for reanimated.
131   // besides, we should not override in remote debugging mode because reanimated does not support it.
132   private fun shouldOverrideUIManagerForReanimated(): Boolean {
133     this.reactInstanceManager?.run {
134       return !(devSupportManager.devSettings?.isRemoteJSDebugEnabled ?: false)
135     }
136 
137     return false
138   }
139 
140   companion object {
141     private val TAG = ExpoTurboPackage::class.java.simpleName
142 
143     fun kernelExpoTurboPackage(manifest: Manifest, initialURL: String?): ExpoTurboPackage {
144       val kernelExperienceProperties = mutableMapOf(
145         KernelConstants.LINKING_URI_KEY to "exp://",
146         KernelConstants.IS_HEADLESS_KEY to false,
147       ).apply {
148         if (initialURL != null) {
149           this[KernelConstants.INTENT_URI_KEY] = initialURL
150         }
151       }
152       return ExpoTurboPackage(kernelExperienceProperties, manifest)
153     }
154   }
155 }
156