<lambda>null1 // Copyright 2015-present 650 Industries. All rights reserved.
2 package versioned.host.exp.exponent
3
4 import android.content.Context
5 import android.content.Intent
6 import android.net.Uri
7 import android.provider.Settings
8 import com.facebook.common.logging.FLog
9 import com.facebook.hermes.reactexecutor.HermesExecutorFactory
10 import com.facebook.react.ReactInstanceManager
11 import com.facebook.react.ReactInstanceManagerBuilder
12 import com.facebook.react.bridge.JavaScriptContextHolder
13 import com.facebook.react.bridge.JavaScriptExecutorFactory
14 import com.facebook.react.bridge.ReactApplicationContext
15 import com.facebook.react.common.LifecycleState
16 import com.facebook.react.common.ReactConstants
17 import com.facebook.react.jscexecutor.JSCExecutorFactory
18 import com.facebook.react.modules.systeminfo.AndroidInfoHelpers
19 import com.facebook.react.packagerconnection.NotificationOnlyHandler
20 import com.facebook.react.packagerconnection.RequestHandler
21 import com.facebook.react.shell.MainReactPackage
22 import expo.modules.jsonutils.getNullable
23 import host.exp.exponent.RNObject
24 import host.exp.exponent.experience.ExperienceActivity
25 import host.exp.exponent.experience.ReactNativeActivity
26 import host.exp.expoview.Exponent
27 import host.exp.expoview.Exponent.InstanceManagerBuilderProperties
28 import org.json.JSONObject
29 import java.util.*
30
31 object VersionedUtils {
32 private fun toggleExpoDevMenu() {
33 val currentActivity = Exponent.instance.currentActivity
34 if (currentActivity is ExperienceActivity) {
35 currentActivity.toggleDevMenu()
36 } else {
37 FLog.e(
38 ReactConstants.TAG,
39 "Unable to toggle the Expo dev menu because the current activity could not be found."
40 )
41 }
42 }
43
44 private fun reloadExpoApp() {
45 val currentActivity = Exponent.instance.currentActivity as? ReactNativeActivity ?: return run {
46 FLog.e(
47 ReactConstants.TAG,
48 "Unable to reload the app because the current activity could not be found."
49 )
50 }
51 val devSupportManager = currentActivity.devSupportManager ?: return run {
52 FLog.e(
53 ReactConstants.TAG,
54 "Unable to get the DevSupportManager from current activity."
55 )
56 }
57
58 devSupportManager.callRecursive("reloadExpoApp")
59 }
60
61 private fun toggleElementInspector() {
62 val currentActivity = Exponent.instance.currentActivity as? ReactNativeActivity ?: return run {
63 FLog.e(
64 ReactConstants.TAG,
65 "Unable to toggle the element inspector because the current activity could not be found."
66 )
67 }
68 val devSupportManager = currentActivity.devSupportManager ?: return run {
69 FLog.e(
70 ReactConstants.TAG,
71 "Unable to get the DevSupportManager from current activity."
72 )
73 }
74
75 devSupportManager.callRecursive("toggleElementInspector")
76 }
77
78 private fun requestOverlayPermission(context: Context) {
79 // From the unexposed DebugOverlayController static helper
80 // Get permission to show debug overlay in dev builds.
81 if (!Settings.canDrawOverlays(context)) {
82 val intent = Intent(
83 Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
84 Uri.parse("package:" + context.packageName)
85 ).apply {
86 flags = Intent.FLAG_ACTIVITY_NEW_TASK
87 }
88 FLog.w(
89 ReactConstants.TAG,
90 "Overlay permissions needs to be granted in order for React Native apps to run in development mode"
91 )
92 if (intent.resolveActivity(context.packageManager) != null) {
93 context.startActivity(intent)
94 }
95 }
96 }
97
98 private fun togglePerformanceMonitor() {
99 val currentActivity = Exponent.instance.currentActivity as? ReactNativeActivity ?: return run {
100 FLog.e(
101 ReactConstants.TAG,
102 "Unable to toggle the performance monitor because the current activity could not be found."
103 )
104 }
105 val devSupportManager = currentActivity.devSupportManager ?: return run {
106 FLog.e(
107 ReactConstants.TAG,
108 "Unable to get the DevSupportManager from current activity."
109 )
110 }
111
112 val devSettings = devSupportManager.callRecursive("getDevSettings")
113 if (devSettings != null) {
114 val isFpsDebugEnabled = devSettings.call("isFpsDebugEnabled") as Boolean
115 if (!isFpsDebugEnabled) {
116 // Request overlay permission if needed when "Show Perf Monitor" option is selected
117 requestOverlayPermission(currentActivity)
118 }
119 devSettings.call("setFpsDebugEnabled", !isFpsDebugEnabled)
120 }
121 }
122
123 private fun toggleRemoteJSDebugging() {
124 val currentActivity = Exponent.instance.currentActivity as? ReactNativeActivity ?: return run {
125 FLog.e(
126 ReactConstants.TAG,
127 "Unable to toggle remote JS debugging because the current activity could not be found."
128 )
129 }
130 val devSupportManager = currentActivity.devSupportManager ?: return run {
131 FLog.e(
132 ReactConstants.TAG,
133 "Unable to get the DevSupportManager from current activity."
134 )
135 }
136
137 val devSettings = devSupportManager.callRecursive("getDevSettings")
138 if (devSettings != null) {
139 val isRemoteJSDebugEnabled = devSettings.call("isRemoteJSDebugEnabled") as Boolean
140 devSettings.call("setRemoteJSDebugEnabled", !isRemoteJSDebugEnabled)
141 }
142 }
143
144 private fun reconnectReactDevTools() {
145 val currentActivity = Exponent.instance.currentActivity as? ReactNativeActivity ?: return run {
146 FLog.e(
147 ReactConstants.TAG,
148 "Unable to get the current activity."
149 )
150 }
151 // Emit the `RCTDevMenuShown` for the app to reconnect react-devtools
152 // https://github.com/facebook/react-native/blob/22ba1e45c52edcc345552339c238c1f5ef6dfc65/Libraries/Core/setUpReactDevTools.js#L80
153 currentActivity.emitRCTNativeAppEvent("RCTDevMenuShown", null)
154 }
155
156 private fun createPackagerCommandHelpers(): Map<String, RequestHandler> {
157 // Attach listeners to the bundler's dev server web socket connection.
158 // This enables tools to automatically reload the client remotely (i.e. in expo-cli).
159 val packagerCommandHandlers = mutableMapOf<String, RequestHandler>()
160
161 // Enable a lot of tools under the same command namespace
162 packagerCommandHandlers["sendDevCommand"] = object : NotificationOnlyHandler() {
163 override fun onNotification(params: Any?) {
164 if (params != null && params is JSONObject) {
165 when (params.getNullable<String>("name")) {
166 "reload" -> reloadExpoApp()
167 "toggleDevMenu" -> toggleExpoDevMenu()
168 "toggleRemoteDebugging" -> {
169 toggleRemoteJSDebugging()
170 // Reload the app after toggling debugging, this is based on what we do in DevSupportManagerBase.
171 reloadExpoApp()
172 }
173 "toggleElementInspector" -> toggleElementInspector()
174 "togglePerformanceMonitor" -> togglePerformanceMonitor()
175 "reconnectReactDevTools" -> reconnectReactDevTools()
176 }
177 }
178 }
179 }
180
181 // These commands (reload and devMenu) are here to match RN dev tooling.
182
183 // Reload the app on "reload"
184 packagerCommandHandlers["reload"] = object : NotificationOnlyHandler() {
185 override fun onNotification(params: Any?) {
186 reloadExpoApp()
187 }
188 }
189
190 // Open the dev menu on "devMenu"
191 packagerCommandHandlers["devMenu"] = object : NotificationOnlyHandler() {
192 override fun onNotification(params: Any?) {
193 toggleExpoDevMenu()
194 }
195 }
196
197 return packagerCommandHandlers
198 }
199
200 @JvmStatic fun getReactInstanceManagerBuilder(instanceManagerBuilderProperties: InstanceManagerBuilderProperties): ReactInstanceManagerBuilder {
201 // Build the instance manager
202 var builder = ReactInstanceManager.builder()
203 .setApplication(instanceManagerBuilderProperties.application)
204 .setJSIModulesPackage { reactApplicationContext: ReactApplicationContext, jsContext: JavaScriptContextHolder? ->
205 emptyList()
206 }
207 .addPackage(MainReactPackage())
208 .addPackage(
209 ExponentPackage(
210 instanceManagerBuilderProperties.experienceProperties,
211 instanceManagerBuilderProperties.manifest,
212 // DO NOT EDIT THIS COMMENT - used by versioning scripts
213 // When distributing change the following two arguments to nulls
214 instanceManagerBuilderProperties.expoPackages,
215 instanceManagerBuilderProperties.exponentPackageDelegate,
216 instanceManagerBuilderProperties.singletonModules
217 )
218 )
219 .addPackage(
220 ExpoTurboPackage(
221 instanceManagerBuilderProperties.experienceProperties,
222 instanceManagerBuilderProperties.manifest
223 )
224 )
225 .setMinNumShakes(100) // disable the RN dev menu
226 .setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
227 .setCustomPackagerCommandHandlers(createPackagerCommandHelpers())
228 .setJavaScriptExecutorFactory(createJSExecutorFactory(instanceManagerBuilderProperties))
229 if (instanceManagerBuilderProperties.jsBundlePath != null && instanceManagerBuilderProperties.jsBundlePath!!.isNotEmpty()) {
230 builder = builder.setJSBundleFile(instanceManagerBuilderProperties.jsBundlePath)
231 }
232 return builder
233 }
234
235 private fun getDevSupportManager(reactApplicationContext: ReactApplicationContext): RNObject? {
236 val currentActivity = Exponent.instance.currentActivity
237 return if (currentActivity != null) {
238 if (currentActivity is ReactNativeActivity) {
239 currentActivity.devSupportManager
240 } else {
241 null
242 }
243 } else try {
244 val devSettingsModule = reactApplicationContext.catalystInstance.getNativeModule("DevSettings")
245 val devSupportManagerField = devSettingsModule!!.javaClass.getDeclaredField("mDevSupportManager")
246 devSupportManagerField.isAccessible = true
247 RNObject.wrap(devSupportManagerField[devSettingsModule]!!)
248 } catch (e: Throwable) {
249 e.printStackTrace()
250 null
251 }
252 }
253
254 private fun createJSExecutorFactory(
255 instanceManagerBuilderProperties: InstanceManagerBuilderProperties
256 ): JavaScriptExecutorFactory? {
257 val appName = instanceManagerBuilderProperties.manifest.getName() ?: ""
258 val deviceName = AndroidInfoHelpers.getFriendlyDeviceName()
259
260 val jsEngineFromManifest = instanceManagerBuilderProperties.manifest.jsEngine
261 return if (jsEngineFromManifest == "hermes") HermesExecutorFactory() else JSCExecutorFactory(
262 appName,
263 deviceName
264 )
265 }
266 }
267