<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