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