1 package expo.modules.devmenu.devtools
2 
3 import android.content.Context
4 import android.content.Intent
5 import android.net.Uri
6 import android.provider.Settings
7 import android.util.Log
8 import com.facebook.react.ReactInstanceManager
9 import com.facebook.react.bridge.UiThreadUtil
10 import com.facebook.react.devsupport.DevMenuInternalSettingsWrapper
11 import expo.interfaces.devmenu.DevMenuManagerInterface
12 import expo.modules.devmenu.DEV_MENU_TAG
13 import expo.modules.devmenu.DevMenuManager
14 import kotlinx.coroutines.launch
15 import java.lang.ref.WeakReference
16 
17 class DevMenuDevToolsDelegate(
18   private val manager: DevMenuManagerInterface,
19   reactInstanceManager: ReactInstanceManager
20 ) {
21   private val _reactDevManager = WeakReference(
22     reactInstanceManager.devSupportManager
23   )
24   private val _reactContext = WeakReference(
25     reactInstanceManager.currentReactContext
26   )
27 
28   val reactDevManager
29     get() = _reactDevManager.get()
30 
31   val devSettings
32     get() = reactDevManager?.devSettings
33 
34   internal val devInternalSettings: DevMenuInternalSettingsWrapper?
35     get() {
36       val devSettings = this.devSettings ?: return null
37       return if (devSettings.javaClass.canonicalName == "com.facebook.react.DevInternalSettings") DevMenuInternalSettingsWrapper(devSettings) else null
38     }
39 
40   val reactContext
41     get() = _reactContext.get()
42 
reloadnull43   fun reload() {
44     val reactDevManager = reactDevManager ?: return
45     manager.closeMenu()
46     UiThreadUtil.runOnUiThread {
47       reactDevManager.handleReloadJS()
48     }
49   }
50 
<lambda>null51   fun toggleElementInspector() = runWithDevSupportEnabled {
52     reactDevManager?.toggleElementInspector()
53   }
54 
<lambda>null55   fun toggleRemoteDebugging() = runWithDevSupportEnabled {
56     val reactDevManager = reactDevManager ?: return
57     val devSettings = devSettings ?: return
58     manager.closeMenu()
59     UiThreadUtil.runOnUiThread {
60       devSettings.isRemoteJSDebugEnabled = !devSettings.isRemoteJSDebugEnabled
61       reactDevManager.handleReloadJS()
62     }
63   }
64 
togglePerformanceMonitornull65   fun togglePerformanceMonitor(context: Context) {
66     val reactDevManager = reactDevManager ?: return
67     val devSettings = devSettings ?: return
68 
69     requestOverlaysPermission(context)
70     runWithDevSupportEnabled {
71       reactDevManager.setFpsDebugEnabled(!devSettings.isFpsDebugEnabled)
72     }
73   }
74 
<lambda>null75   fun openJSInspector() = runWithDevSupportEnabled {
76     val devSettings = devInternalSettings ?: return
77     val reactContext = reactContext ?: return
78     val metroHost = "http://${devSettings.packagerConnectionSettings.debugServerHost}"
79 
80     manager.coroutineScope.launch {
81       try {
82         DevMenuManager.metroClient.openJSInspector(metroHost, reactContext.packageName)
83       } catch (e: Exception) {
84         Log.w(DEV_MENU_TAG, "Unable to open js inspector: ${e.message}", e)
85       }
86     }
87   }
88 
89   /**
90    * RN will temporary disable `devSupport` if the current activity isn't active.
91    * Because of that we can't call some functions like `toggleElementInspector`.
92    * However, we can temporary set the `devSupport` flag to true and run needed methods.
93    */
runWithDevSupportEnablednull94   private inline fun runWithDevSupportEnabled(action: () -> Unit) {
95     val reactDevManager = reactDevManager ?: return
96     val currentSetting = reactDevManager.devSupportEnabled
97     reactDevManager.devSupportEnabled = true
98     action()
99     reactDevManager.devSupportEnabled = currentSetting
100   }
101 
102   /**
103    * Requests for the permission that allows the app to draw overlays on other apps.
104    * Such permission is required to enable performance monitor.
105    */
requestOverlaysPermissionnull106   private fun requestOverlaysPermission(context: Context) {
107     if (!Settings.canDrawOverlays(context)) {
108       val uri = Uri.parse("package:" + context.applicationContext.packageName)
109       val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, uri).apply {
110         flags = Intent.FLAG_ACTIVITY_NEW_TASK
111       }
112       if (intent.resolveActivity(context.packageManager) != null) {
113         context.startActivity(intent)
114       }
115     }
116   }
117 }
118