<lambda>null1 // Copyright 2015-present 650 Industries. All rights reserved.
2 package host.exp.exponent.experience
3 
4 import android.app.Activity
5 import android.content.Intent
6 import android.content.pm.PackageManager
7 import android.graphics.Color
8 import android.net.Uri
9 import android.os.Bundle
10 import android.os.Handler
11 import android.os.Process
12 import android.view.KeyEvent
13 import android.view.View
14 import android.view.ViewGroup
15 import android.widget.FrameLayout
16 import androidx.annotation.UiThread
17 import androidx.appcompat.app.AppCompatActivity
18 import androidx.core.content.ContextCompat
19 import com.facebook.infer.annotation.Assertions
20 import com.facebook.react.devsupport.DoubleTapReloadRecognizer
21 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
22 import com.facebook.react.modules.core.PermissionAwareActivity
23 import com.facebook.react.modules.core.PermissionListener
24 import de.greenrobot.event.EventBus
25 import expo.modules.core.interfaces.Package
26 import expo.modules.manifests.core.Manifest
27 import host.exp.exponent.Constants
28 import host.exp.exponent.ExponentManifest
29 import host.exp.exponent.RNObject
30 import host.exp.exponent.analytics.EXL
31 import host.exp.exponent.di.NativeModuleDepsProvider
32 import host.exp.exponent.experience.BaseExperienceActivity.ExperienceContentLoaded
33 import host.exp.exponent.experience.splashscreen.LoadingView
34 import host.exp.exponent.kernel.*
35 import host.exp.exponent.kernel.KernelConstants.AddedExperienceEventEvent
36 import host.exp.exponent.kernel.services.ErrorRecoveryManager
37 import host.exp.exponent.kernel.services.ExpoKernelServiceRegistry
38 import host.exp.exponent.notifications.ExponentNotification
39 import host.exp.exponent.storage.ExponentSharedPreferences
40 import host.exp.exponent.utils.BundleJSONConverter
41 import host.exp.exponent.utils.ExperienceActivityUtils
42 import host.exp.exponent.utils.ScopedPermissionsRequester
43 import host.exp.expoview.Exponent
44 import host.exp.expoview.Exponent.InstanceManagerBuilderProperties
45 import host.exp.expoview.Exponent.StartReactInstanceDelegate
46 import host.exp.expoview.R
47 import org.json.JSONException
48 import org.json.JSONObject
49 import versioned.host.exp.exponent.ExponentPackage
50 import java.util.*
51 import javax.inject.Inject
52 
53 abstract class ReactNativeActivity :
54   AppCompatActivity(),
55   DefaultHardwareBackBtnHandler,
56   PermissionAwareActivity {
57 
58   class ExperienceDoneLoadingEvent internal constructor(val activity: Activity)
59 
60   open fun initialProps(expBundle: Bundle?): Bundle? {
61     return expBundle
62   }
63 
64   protected open fun onDoneLoading() {}
65 
66   // Will be called after waitForDrawOverOtherAppPermission
67   protected open fun startReactInstance() {}
68 
69   protected var reactInstanceManager: RNObject =
70     RNObject("com.facebook.react.ReactInstanceManager")
71   protected var isCrashed = false
72 
73   protected var manifestUrl: String? = null
74   var experienceKey: ExperienceKey? = null
75   protected var sdkVersion: String? = null
76   protected var activityId = 0
77 
78   // In detach we want UNVERSIONED most places. We still need the numbered sdk version
79   // when creating cache keys.
80   protected var detachSdkVersion: String? = null
81 
82   protected lateinit var reactRootView: RNObject
83   private lateinit var doubleTapReloadRecognizer: DoubleTapReloadRecognizer
84   var isLoading = true
85     protected set
86   protected var jsBundlePath: String? = null
87   protected var manifest: Manifest? = null
88   var isInForeground = false
89     protected set
90   private var scopedPermissionsRequester: ScopedPermissionsRequester? = null
91 
92   @Inject
93   protected lateinit var exponentSharedPreferences: ExponentSharedPreferences
94 
95   @Inject
96   lateinit var expoKernelServiceRegistry: ExpoKernelServiceRegistry
97 
98   private lateinit var containerView: FrameLayout
99 
100   /**
101    * This view is optional and available only when the app runs in Expo Go.
102    */
103   private var loadingView: LoadingView? = null
104   private lateinit var reactContainerView: FrameLayout
105   private val handler = Handler()
106 
107   protected open fun shouldCreateLoadingView(): Boolean {
108     return !Constants.isStandaloneApp() || Constants.SHOW_LOADING_VIEW_IN_SHELL_APP
109   }
110 
111   val rootView: View?
112     get() = reactRootView.get() as View?
113 
114   override fun onCreate(savedInstanceState: Bundle?) {
115     super.onCreate(null)
116 
117     containerView = FrameLayout(this)
118     setContentView(containerView)
119 
120     reactContainerView = FrameLayout(this)
121     containerView.addView(reactContainerView)
122 
123     if (shouldCreateLoadingView()) {
124       containerView.setBackgroundColor(
125         ContextCompat.getColor(
126           this,
127           R.color.splashscreen_background
128         )
129       )
130       loadingView = LoadingView(this)
131       loadingView!!.show()
132       containerView.addView(loadingView)
133     }
134 
135     doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
136     Exponent.initialize(this, application)
137     NativeModuleDepsProvider.instance.inject(ReactNativeActivity::class.java, this)
138 
139     // Can't call this here because subclasses need to do other initialization
140     // before their listener methods are called.
141     // EventBus.getDefault().registerSticky(this);
142   }
143 
144   protected fun setReactRootView(reactRootView: View) {
145     reactContainerView.removeAllViews()
146     addReactViewToContentContainer(reactRootView)
147   }
148 
149   fun addReactViewToContentContainer(reactView: View) {
150     if (reactView.parent != null) {
151       (reactView.parent as ViewGroup).removeView(reactView)
152     }
153     reactContainerView.addView(reactView)
154   }
155 
156   fun hasReactView(reactView: View): Boolean {
157     return reactView.parent === reactContainerView
158   }
159 
160   protected fun hideLoadingView() {
161     loadingView?.let {
162       val viewGroup = it.parent as ViewGroup?
163       viewGroup?.removeView(it)
164       it.hide()
165     }
166     loadingView = null
167   }
168 
169   protected fun removeAllViewsFromContainer() {
170     containerView.removeAllViews()
171   }
172   // region Loading
173   /**
174    * Successfully finished loading
175    */
176   @UiThread
177   protected fun finishLoading() {
178     waitForReactAndFinishLoading()
179   }
180 
181   /**
182    * There was an error during loading phase
183    */
184   protected fun interruptLoading() {
185     handler.removeCallbacksAndMessages(null)
186   }
187 
188   // Loop until a view is added to the ReactRootView and once it happens run callback
189   private fun waitForReactRootViewToHaveChildrenAndRunCallback(callback: Runnable) {
190     if (reactRootView.isNull) {
191       return
192     }
193 
194     if (reactRootView.call("getChildCount") as Int > 0) {
195       callback.run()
196     } else {
197       handler.postDelayed(
198         { waitForReactRootViewToHaveChildrenAndRunCallback(callback) },
199         VIEW_TEST_INTERVAL_MS
200       )
201     }
202   }
203 
204   /**
205    * Waits for JS side of React to be launched and then performs final launching actions.
206    */
207   private fun waitForReactAndFinishLoading() {
208     if (Constants.isStandaloneApp() && Constants.SHOW_LOADING_VIEW_IN_SHELL_APP) {
209       val layoutParams = containerView.layoutParams
210       layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT
211       containerView.layoutParams = layoutParams
212     }
213 
214     try {
215       // NOTE(evanbacon): Use the same view as the `expo-system-ui` module.
216       // Set before the application code runs to ensure immediate SystemUI calls overwrite the app.json value.
217       var rootView = this.window.decorView
218       ExperienceActivityUtils.setRootViewBackgroundColor(manifest!!, rootView)
219     } catch (e: Exception) {
220       EXL.e(TAG, e)
221     }
222 
223     waitForReactRootViewToHaveChildrenAndRunCallback {
224       onDoneLoading()
225       try {
226         // NOTE(evanbacon): The hierarchy at this point looks like:
227         // window.decorView > [4 other views] > containerView > reactContainerView > rootView > [RN App]
228         // This can be inspected using Android Studio: View > Tool Windows > Layout Inspector.
229         // Container background color is set for "loading" view state, we need to set it to transparent to prevent obstructing the root view.
230         containerView!!.setBackgroundColor(Color.TRANSPARENT)
231       } catch (e: Exception) {
232         EXL.e(TAG, e)
233       }
234       ErrorRecoveryManager.getInstance(experienceKey!!).markExperienceLoaded()
235       pollForEventsToSendToRN()
236       EventBus.getDefault().post(ExperienceDoneLoadingEvent(this))
237       isLoading = false
238     }
239   }
240   // endregion
241   // region SplashScreen
242   /**
243    * Get what version (among versioned classes) of ReactRootView.class SplashScreen module should be looking for.
244    */
245   protected fun getRootViewClass(manifest: Manifest): Class<out ViewGroup> {
246     val reactRootViewRNClass = reactRootView.rnClass()
247     if (reactRootViewRNClass != null) {
248       return reactRootViewRNClass as Class<out ViewGroup>
249     }
250     var sdkVersion = manifest.getExpoGoSDKVersion()
251     if (Constants.TEMPORARY_ABI_VERSION != null && Constants.TEMPORARY_ABI_VERSION == this.sdkVersion) {
252       sdkVersion = RNObject.UNVERSIONED
253     }
254     sdkVersion = if (Constants.isStandaloneApp()) RNObject.UNVERSIONED else sdkVersion
255     return RNObject("com.facebook.react.ReactRootView").loadVersion(sdkVersion!!).rnClass() as Class<out ViewGroup>
256   }
257 
258   // endregion
259   override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
260     devSupportManager?.let { devSupportManager ->
261       if (!isCrashed && devSupportManager.call("getDevSupportEnabled") as Boolean) {
262         val didDoubleTapR = Assertions.assertNotNull(doubleTapReloadRecognizer)
263           .didDoubleTapR(keyCode, currentFocus)
264         if (didDoubleTapR) {
265           devSupportManager.call("reloadExpoApp")
266           return true
267         }
268       }
269     }
270 
271     return super.onKeyUp(keyCode, event)
272   }
273 
274   override fun onBackPressed() {
275     if (reactInstanceManager.isNotNull && !isCrashed) {
276       reactInstanceManager.call("onBackPressed")
277     } else {
278       super.onBackPressed()
279     }
280   }
281 
282   override fun invokeDefaultOnBackPressed() {
283     super.onBackPressed()
284   }
285 
286   override fun onPause() {
287     super.onPause()
288     if (reactInstanceManager.isNotNull && !isCrashed) {
289       KernelNetworkInterceptor.onPause()
290       reactInstanceManager.onHostPause()
291       // TODO: use onHostPause(activity)
292     }
293   }
294 
295   override fun onResume() {
296     super.onResume()
297     if (reactInstanceManager.isNotNull && !isCrashed) {
298       reactInstanceManager.onHostResume(this, this)
299       KernelNetworkInterceptor.onResume(reactInstanceManager.get())
300     }
301   }
302 
303   override fun onDestroy() {
304     super.onDestroy()
305     destroyReactInstanceManager()
306     handler.removeCallbacksAndMessages(null)
307     EventBus.getDefault().unregister(this)
308   }
309 
310   public override fun onNewIntent(intent: Intent) {
311     if (reactInstanceManager.isNotNull && !isCrashed) {
312       try {
313         reactInstanceManager.call("onNewIntent", intent)
314       } catch (e: Throwable) {
315         EXL.e(TAG, e.toString())
316         super.onNewIntent(intent)
317       }
318     } else {
319       super.onNewIntent(intent)
320     }
321   }
322 
323   open val isDebugModeEnabled: Boolean
324     get() = manifest?.isDevelopmentMode() ?: false
325 
326   protected open fun destroyReactInstanceManager() {
327     if (reactInstanceManager.isNotNull && !isCrashed) {
328       reactInstanceManager.call("destroy")
329     }
330   }
331 
332   public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
333     super.onActivityResult(requestCode, resultCode, data)
334 
335     Exponent.instance.onActivityResult(requestCode, resultCode, data)
336 
337     if (reactInstanceManager.isNotNull && !isCrashed) {
338       reactInstanceManager.call("onActivityResult", this, requestCode, resultCode, data)
339     }
340 
341     // Have permission to draw over other apps. Resume loading.
342     if (requestCode == KernelConstants.OVERLAY_PERMISSION_REQUEST_CODE) {
343       // startReactInstance() checks isInForeground and onActivityResult is called before onResume,
344       // so manually set this here.
345       isInForeground = true
346       startReactInstance()
347     }
348   }
349 
350   fun startReactInstance(
351     delegate: StartReactInstanceDelegate,
352     intentUri: String?,
353     sdkVersion: String?,
354     notification: ExponentNotification?,
355     isShellApp: Boolean,
356     extraNativeModules: List<Any>?,
357     extraExpoPackages: List<Package>?,
358     progressListener: DevBundleDownloadProgressListener
359   ): RNObject {
360     if (isCrashed || !delegate.isInForeground) {
361       // Can sometimes get here after an error has occurred. Return early or else we'll hit
362       // a null pointer at mReactRootView.startReactApplication
363       return RNObject("com.facebook.react.ReactInstanceManager")
364     }
365 
366     val experienceProperties = mapOf<String, Any?>(
367       KernelConstants.MANIFEST_URL_KEY to manifestUrl,
368       KernelConstants.LINKING_URI_KEY to linkingUri,
369       KernelConstants.INTENT_URI_KEY to intentUri,
370       KernelConstants.IS_HEADLESS_KEY to false
371     )
372 
373     val instanceManagerBuilderProperties = InstanceManagerBuilderProperties(
374       application = application,
375       jsBundlePath = jsBundlePath,
376       experienceProperties = experienceProperties,
377       expoPackages = extraExpoPackages,
378       exponentPackageDelegate = delegate.exponentPackageDelegate,
379       manifest = manifest!!,
380       singletonModules = ExponentPackage.getOrCreateSingletonModules(applicationContext, manifest, extraExpoPackages)
381     )
382 
383     val versionedUtils = RNObject("host.exp.exponent.VersionedUtils").loadVersion(sdkVersion!!)
384     val builder = versionedUtils.callRecursive(
385       "getReactInstanceManagerBuilder",
386       instanceManagerBuilderProperties
387     )!!
388 
389     builder.call("setCurrentActivity", this)
390 
391     // ReactNativeInstance is considered to be resumed when it has its activity attached, which is expected to be the case here
392     builder.call(
393       "setInitialLifecycleState",
394       RNObject.versionedEnum(sdkVersion, "com.facebook.react.common.LifecycleState", "RESUMED")
395     )
396 
397     if (extraNativeModules != null) {
398       for (nativeModule in extraNativeModules) {
399         builder.call("addPackage", nativeModule)
400       }
401     }
402 
403     if (delegate.isDebugModeEnabled) {
404       val debuggerHost = manifest!!.getDebuggerHost()
405       val mainModuleName = manifest!!.getMainModuleName()
406       Exponent.enableDeveloperSupport(debuggerHost, mainModuleName, builder)
407 
408       val devLoadingView =
409         RNObject("com.facebook.react.devsupport.DevLoadingViewController").loadVersion(sdkVersion)
410       devLoadingView.callRecursive("setDevLoadingEnabled", false)
411 
412       val devBundleDownloadListener =
413         RNObject("host.exp.exponent.ExponentDevBundleDownloadListener")
414           .loadVersion(sdkVersion)
415           .construct(progressListener)
416       builder.callRecursive("setDevBundleDownloadListener", devBundleDownloadListener.get())
417     } else {
418       waitForReactAndFinishLoading()
419     }
420 
421     val bundle = Bundle()
422     val exponentProps = JSONObject()
423     if (notification != null) {
424       bundle.putString("notification", notification.body) // Deprecated
425       try {
426         exponentProps.put("notification", notification.toJSONObject("selected"))
427       } catch (e: JSONException) {
428         e.printStackTrace()
429       }
430     }
431 
432     try {
433       exponentProps.put("manifestString", manifest.toString())
434       exponentProps.put("shell", isShellApp)
435       exponentProps.put("initialUri", intentUri)
436     } catch (e: JSONException) {
437       EXL.e(TAG, e)
438     }
439 
440     val metadata = exponentSharedPreferences.getExperienceMetadata(experienceKey!!)
441     if (metadata != null) {
442       // TODO: fix this. this is the only place that EXPERIENCE_METADATA_UNREAD_REMOTE_NOTIFICATIONS is sent to the experience,
443       // we need to send them with the standard notification events so that you can get all the unread notification through an event
444       // Copy unreadNotifications into exponentProps
445       if (metadata.has(ExponentSharedPreferences.EXPERIENCE_METADATA_UNREAD_REMOTE_NOTIFICATIONS)) {
446         try {
447           val unreadNotifications =
448             metadata.getJSONArray(ExponentSharedPreferences.EXPERIENCE_METADATA_UNREAD_REMOTE_NOTIFICATIONS)
449           delegate.handleUnreadNotifications(unreadNotifications)
450         } catch (e: JSONException) {
451           e.printStackTrace()
452         }
453         metadata.remove(ExponentSharedPreferences.EXPERIENCE_METADATA_UNREAD_REMOTE_NOTIFICATIONS)
454       }
455       exponentSharedPreferences.updateExperienceMetadata(experienceKey!!, metadata)
456     }
457 
458     try {
459       bundle.putBundle("exp", BundleJSONConverter.convertToBundle(exponentProps))
460     } catch (e: JSONException) {
461       throw Error("JSONObject failed to be converted to Bundle", e)
462     }
463 
464     if (!delegate.isInForeground) {
465       return RNObject("com.facebook.react.ReactInstanceManager")
466     }
467 
468     val mReactInstanceManager = builder.callRecursive("build")!!
469     val devSettings =
470       mReactInstanceManager.callRecursive("getDevSupportManager")!!.callRecursive("getDevSettings")
471     if (devSettings != null) {
472       devSettings.setField("exponentActivityId", activityId)
473       if (devSettings.call("isRemoteJSDebugEnabled") as Boolean) {
474         if (manifest?.jsEngine == "hermes") {
475           // Disable remote debugging when running on Hermes
476           devSettings.call("setRemoteJSDebugEnabled", false)
477         }
478         waitForReactAndFinishLoading()
479       }
480     }
481 
482     mReactInstanceManager.onHostResume(this, this)
483     val appKey = manifest!!.getAppKey()
484     reactRootView.call(
485       "startReactApplication",
486       mReactInstanceManager.get(),
487       appKey ?: KernelConstants.DEFAULT_APPLICATION_KEY,
488       initialProps(bundle)
489     )
490 
491     KernelNetworkInterceptor.start(manifest!!, mReactInstanceManager.get())
492 
493     // Requesting layout to make sure {@link ReactRootView} attached to {@link ReactInstanceManager}
494     // Otherwise, {@link ReactRootView} will hang in {@link waitForReactRootViewToHaveChildrenAndRunCallback}.
495     // Originally react-native will automatically attach after `startReactApplication`.
496     // After https://github.com/facebook/react-native/commit/2c896d35782cd04c8,
497     // the only remaining path is by `onMeasure`.
498     reactRootView.call("requestLayout")
499 
500     return mReactInstanceManager
501   }
502 
503   protected fun shouldShowErrorScreen(errorMessage: ExponentErrorMessage): Boolean {
504     if (isLoading) {
505       // Don't hit ErrorRecoveryManager until bridge is initialized.
506       // This is the same on iOS.
507       return true
508     }
509 
510     val errorRecoveryManager = experienceKey?.let { ErrorRecoveryManager.getInstance(it) }
511     errorRecoveryManager?.markErrored()
512     if (errorRecoveryManager?.shouldReloadOnError() != true) {
513       return true
514     }
515 
516     manifestUrl?.let {
517       // Kernel couldn't reload, show error screen
518       if (!KernelProvider.instance.reloadVisibleExperience(it)) {
519         return true
520       }
521     }
522 
523     errorQueue.clear()
524 
525     return false
526   }
527 
528   fun onEventMainThread(event: AddedExperienceEventEvent) {
529     if (manifestUrl != null && manifestUrl == event.manifestUrl) {
530       pollForEventsToSendToRN()
531     }
532   }
533 
534   fun onEvent(event: ExperienceContentLoaded?) {}
535 
536   private fun pollForEventsToSendToRN() {
537     if (manifestUrl == null) {
538       return
539     }
540 
541     try {
542       val rctDeviceEventEmitter =
543         RNObject("com.facebook.react.modules.core.DeviceEventManagerModule\$RCTDeviceEventEmitter")
544       rctDeviceEventEmitter.loadVersion(detachSdkVersion!!)
545       val existingEmitter = reactInstanceManager.callRecursive("getCurrentReactContext")!!
546         .callRecursive("getJSModule", rctDeviceEventEmitter.rnClass())
547       if (existingEmitter != null) {
548         val events = KernelProvider.instance.consumeExperienceEvents(manifestUrl!!)
549         for ((eventName, eventPayload) in events) {
550           existingEmitter.call("emit", eventName, eventPayload)
551         }
552       }
553     } catch (e: Throwable) {
554       EXL.e(TAG, e)
555     }
556   }
557 
558   /**
559    * Emits events to `RCTNativeAppEventEmitter`
560    */
561   fun emitRCTNativeAppEvent(eventName: String, eventArgs: Map<String, String>?) {
562     try {
563       val nativeAppEventEmitter =
564         RNObject("com.facebook.react.modules.core.RCTNativeAppEventEmitter")
565       nativeAppEventEmitter.loadVersion(detachSdkVersion!!)
566       val emitter = reactInstanceManager.callRecursive("getCurrentReactContext")!!
567         .callRecursive("getJSModule", nativeAppEventEmitter.rnClass())
568       emitter?.call("emit", eventName, eventArgs)
569     } catch (e: Throwable) {
570       EXL.e(TAG, e)
571     }
572   }
573 
574   // for getting global permission
575   override fun checkSelfPermission(permission: String): Int {
576     return super.checkPermission(permission, Process.myPid(), Process.myUid())
577   }
578 
579   override fun shouldShowRequestPermissionRationale(permission: String): Boolean {
580     // in scoped application we don't have `don't ask again` button
581     return if (!Constants.isStandaloneApp() && checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
582       true
583     } else super.shouldShowRequestPermissionRationale(permission)
584   }
585 
586   override fun requestPermissions(
587     permissions: Array<String>,
588     requestCode: Int,
589     listener: PermissionListener
590   ) {
591     if (requestCode == ScopedPermissionsRequester.EXPONENT_PERMISSIONS_REQUEST) {
592       val name = manifest!!.getName()
593       scopedPermissionsRequester = ScopedPermissionsRequester(experienceKey!!)
594       scopedPermissionsRequester!!.requestPermissions(this, name ?: "", permissions, listener)
595     } else {
596       super.requestPermissions(permissions, requestCode)
597     }
598   }
599 
600   override fun onRequestPermissionsResult(
601     requestCode: Int,
602     permissions: Array<String>,
603     grantResults: IntArray
604   ) {
605     if (requestCode == ScopedPermissionsRequester.EXPONENT_PERMISSIONS_REQUEST) {
606       if (permissions.isNotEmpty() && grantResults.size == permissions.size && scopedPermissionsRequester != null) {
607         if (scopedPermissionsRequester!!.onRequestPermissionsResult(permissions, grantResults)) {
608           scopedPermissionsRequester = null
609         }
610       }
611     } else {
612       super.onRequestPermissionsResult(requestCode, permissions, grantResults)
613     }
614   }
615 
616   // for getting scoped permission
617   override fun checkPermission(permission: String, pid: Int, uid: Int): Int {
618     val globalResult = super.checkPermission(permission, pid, uid)
619     return expoKernelServiceRegistry.permissionsKernelService.getPermissions(
620       globalResult,
621       packageManager,
622       permission,
623       experienceKey!!
624     )
625   }
626 
627   val devSupportManager: RNObject?
628     get() = reactInstanceManager.takeIf { it.isNotNull }?.callRecursive("getDevSupportManager")
629 
630   val jsExecutorName: String?
631     get() = reactInstanceManager.takeIf { it.isNotNull }?.callRecursive("getJsExecutorName")?.get() as? String
632 
633   // deprecated in favor of Expo.Linking.makeUrl
634   // TODO: remove this
635   private val linkingUri: String?
636     get() = if (Constants.SHELL_APP_SCHEME != null) {
637       Constants.SHELL_APP_SCHEME + "://"
638     } else {
639       val uri = Uri.parse(manifestUrl)
640       val host = uri.host
641       if (host != null && (
642         host == "exp.host" || host == "expo.io" || host == "exp.direct" || host == "expo.test" ||
643           host.endsWith(".exp.host") || host.endsWith(".expo.io") || host.endsWith(".exp.direct") || host.endsWith(
644             ".expo.test"
645           )
646         )
647       ) {
648         val pathSegments = uri.pathSegments
649         val builder = uri.buildUpon()
650         builder.path(null)
651         for (segment in pathSegments) {
652           if (ExponentManifest.DEEP_LINK_SEPARATOR == segment) {
653             break
654           }
655           builder.appendEncodedPath(segment)
656         }
657         builder.appendEncodedPath(ExponentManifest.DEEP_LINK_SEPARATOR_WITH_SLASH).build()
658           .toString()
659       } else {
660         manifestUrl
661       }
662     }
663 
664   companion object {
665     private val TAG = ReactNativeActivity::class.java.simpleName
666     private const val VIEW_TEST_INTERVAL_MS: Long = 20
667     @JvmStatic protected var errorQueue: Queue<ExponentError> = LinkedList()
668   }
669 }
670