<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