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