<lambda>null1aa3c92eaSWill Schurman // Copyright 2015-present 650 Industries. All rights reserved.
2aa3c92eaSWill Schurman package host.exp.exponent.kernel
3aa3c92eaSWill Schurman
4aa3c92eaSWill Schurman import android.app.Activity
5aa3c92eaSWill Schurman import android.app.ActivityManager
6aa3c92eaSWill Schurman import android.app.ActivityManager.AppTask
7aa3c92eaSWill Schurman import android.app.ActivityManager.RecentTaskInfo
8aa3c92eaSWill Schurman import android.app.Application
9aa3c92eaSWill Schurman import android.app.RemoteInput
10aa3c92eaSWill Schurman import android.content.Context
11aa3c92eaSWill Schurman import android.content.Intent
12aa3c92eaSWill Schurman import android.net.Uri
13aa3c92eaSWill Schurman import android.nfc.NfcAdapter
14aa3c92eaSWill Schurman import android.os.Bundle
15aa3c92eaSWill Schurman import android.util.Log
16aa3c92eaSWill Schurman import android.widget.Toast
17ff9f745fSKudo Chien import com.facebook.hermes.reactexecutor.HermesExecutorFactory
18aa3c92eaSWill Schurman import com.facebook.proguard.annotations.DoNotStrip
19aa3c92eaSWill Schurman import com.facebook.react.ReactInstanceManager
20aa3c92eaSWill Schurman import com.facebook.react.ReactRootView
21aa3c92eaSWill Schurman import com.facebook.react.bridge.Arguments
22ff9f745fSKudo Chien import com.facebook.react.bridge.JavaScriptExecutorFactory
23aa3c92eaSWill Schurman import com.facebook.react.bridge.ReadableMap
24aa3c92eaSWill Schurman import com.facebook.react.common.LifecycleState
25ff9f745fSKudo Chien import com.facebook.react.jscexecutor.JSCExecutorFactory
26aa3c92eaSWill Schurman import com.facebook.react.modules.network.ReactCookieJarContainer
27ff9f745fSKudo Chien import com.facebook.react.modules.systeminfo.AndroidInfoHelpers
28aa3c92eaSWill Schurman import com.facebook.react.shell.MainReactPackage
29aa3c92eaSWill Schurman import com.facebook.soloader.SoLoader
30aa3c92eaSWill Schurman import de.greenrobot.event.EventBus
315f54863aSWill Schurman import expo.modules.jsonutils.require
32b90b2e78SKudo Chien import expo.modules.notifications.service.NotificationsService.Companion.getNotificationResponseFromOpenIntent
33aa3c92eaSWill Schurman import expo.modules.notifications.service.delegates.ExpoHandlingDelegate
3450661f5cSWill Schurman import expo.modules.manifests.core.Manifest
355f54863aSWill Schurman import expo.modules.manifests.core.NewManifest
36aa3c92eaSWill Schurman import host.exp.exponent.*
37aa3c92eaSWill Schurman import host.exp.exponent.ExpoUpdatesAppLoader.AppLoaderCallback
38aa3c92eaSWill Schurman import host.exp.exponent.ExpoUpdatesAppLoader.AppLoaderStatus
39aa3c92eaSWill Schurman import host.exp.exponent.analytics.EXL
40aa3c92eaSWill Schurman import host.exp.exponent.di.NativeModuleDepsProvider
41aa3c92eaSWill Schurman import host.exp.exponent.exceptions.ExceptionUtils
42aa3c92eaSWill Schurman import host.exp.exponent.experience.BaseExperienceActivity
43aa3c92eaSWill Schurman import host.exp.exponent.experience.ErrorActivity
44aa3c92eaSWill Schurman import host.exp.exponent.experience.ExperienceActivity
45aa3c92eaSWill Schurman import host.exp.exponent.experience.HomeActivity
46aa3c92eaSWill Schurman import host.exp.exponent.headless.InternalHeadlessAppLoader
47aa3c92eaSWill Schurman import host.exp.exponent.kernel.ExponentErrorMessage.Companion.developerErrorMessage
48aa3c92eaSWill Schurman import host.exp.exponent.kernel.ExponentKernelModuleProvider.KernelEventCallback
49aa3c92eaSWill Schurman import host.exp.exponent.kernel.ExponentKernelModuleProvider.queueEvent
50aa3c92eaSWill Schurman import host.exp.exponent.kernel.ExponentUrls.toHttp
51aa3c92eaSWill Schurman import host.exp.exponent.kernel.KernelConstants.ExperienceOptions
52aa3c92eaSWill Schurman import host.exp.exponent.network.ExponentNetwork
53aa3c92eaSWill Schurman import host.exp.exponent.notifications.ExponentNotification
54aa3c92eaSWill Schurman import host.exp.exponent.notifications.ExponentNotificationManager
55aa3c92eaSWill Schurman import host.exp.exponent.notifications.NotificationActionCenter
56aa3c92eaSWill Schurman import host.exp.exponent.notifications.ScopedNotificationsUtils
57aa3c92eaSWill Schurman import host.exp.exponent.storage.ExponentDB
58aa3c92eaSWill Schurman import host.exp.exponent.storage.ExponentSharedPreferences
59aa3c92eaSWill Schurman import host.exp.exponent.utils.AsyncCondition
60aa3c92eaSWill Schurman import host.exp.exponent.utils.AsyncCondition.AsyncConditionListener
613ba32fe9SKudo Chien import host.exp.exponent.utils.BundleJSONConverter
62aa3c92eaSWill Schurman import host.exp.expoview.BuildConfig
63aa3c92eaSWill Schurman import host.exp.expoview.ExpoViewBuildConfig
64aa3c92eaSWill Schurman import host.exp.expoview.Exponent
65aa3c92eaSWill Schurman import host.exp.expoview.Exponent.BundleListener
66aa3c92eaSWill Schurman import okhttp3.OkHttpClient
67aa3c92eaSWill Schurman import org.json.JSONException
68aa3c92eaSWill Schurman import org.json.JSONObject
69aa3c92eaSWill Schurman import versioned.host.exp.exponent.ExpoTurboPackage
70aa3c92eaSWill Schurman import versioned.host.exp.exponent.ExponentPackage
71aa3c92eaSWill Schurman import versioned.host.exp.exponent.ReactUnthemedRootView
72aa3c92eaSWill Schurman import java.lang.ref.WeakReference
73aa3c92eaSWill Schurman import java.util.*
74aa3c92eaSWill Schurman import java.util.concurrent.TimeUnit
75aa3c92eaSWill Schurman import javax.inject.Inject
76aa3c92eaSWill Schurman
77aa3c92eaSWill Schurman // TOOD: need to figure out when we should reload the kernel js. Do we do it every time you visit
78aa3c92eaSWill Schurman // the home screen? only when the app gets kicked out of memory?
79aa3c92eaSWill Schurman class Kernel : KernelInterface() {
80aa3c92eaSWill Schurman class KernelStartedRunningEvent
81aa3c92eaSWill Schurman
82aa3c92eaSWill Schurman class ExperienceActivityTask(val manifestUrl: String) {
83aa3c92eaSWill Schurman var taskId = 0
84aa3c92eaSWill Schurman var experienceActivity: WeakReference<ExperienceActivity>? = null
85aa3c92eaSWill Schurman var activityId = 0
86aa3c92eaSWill Schurman var bundleUrl: String? = null
87aa3c92eaSWill Schurman }
88aa3c92eaSWill Schurman
89aa3c92eaSWill Schurman // React
90aa3c92eaSWill Schurman var reactInstanceManager: ReactInstanceManager? = null
91aa3c92eaSWill Schurman private set
92aa3c92eaSWill Schurman
93aa3c92eaSWill Schurman // Contexts
94aa3c92eaSWill Schurman @Inject
95aa3c92eaSWill Schurman lateinit var context: Context
96aa3c92eaSWill Schurman
97aa3c92eaSWill Schurman @Inject
98aa3c92eaSWill Schurman lateinit var applicationContext: Application
99aa3c92eaSWill Schurman
100aa3c92eaSWill Schurman @Inject
101aa3c92eaSWill Schurman lateinit var exponentManifest: ExponentManifest
102aa3c92eaSWill Schurman
103aa3c92eaSWill Schurman @Inject
104aa3c92eaSWill Schurman lateinit var exponentSharedPreferences: ExponentSharedPreferences
105aa3c92eaSWill Schurman
106aa3c92eaSWill Schurman @Inject
107aa3c92eaSWill Schurman lateinit var exponentNetwork: ExponentNetwork
108aa3c92eaSWill Schurman
109aa3c92eaSWill Schurman var activityContext: Activity? = null
110aa3c92eaSWill Schurman set(value) {
111aa3c92eaSWill Schurman if (value != null) {
112aa3c92eaSWill Schurman field = value
113aa3c92eaSWill Schurman }
114aa3c92eaSWill Schurman }
115aa3c92eaSWill Schurman
116aa3c92eaSWill Schurman private var optimisticActivity: ExperienceActivity? = null
117aa3c92eaSWill Schurman
118aa3c92eaSWill Schurman private var optimisticTaskId: Int? = null
119aa3c92eaSWill Schurman
120aa3c92eaSWill Schurman private fun experienceActivityTaskForTaskId(taskId: Int): ExperienceActivityTask? {
121aa3c92eaSWill Schurman return manifestUrlToExperienceActivityTask.values.find { it.taskId == taskId }
122aa3c92eaSWill Schurman }
123aa3c92eaSWill Schurman
124aa3c92eaSWill Schurman // Misc
125aa3c92eaSWill Schurman var isStarted = false
126aa3c92eaSWill Schurman private set
127aa3c92eaSWill Schurman private var hasError = false
128aa3c92eaSWill Schurman
129aa3c92eaSWill Schurman private fun updateKernelRNOkHttp() {
130aa3c92eaSWill Schurman val client = OkHttpClient.Builder()
131aa3c92eaSWill Schurman .connectTimeout(0, TimeUnit.MILLISECONDS)
132aa3c92eaSWill Schurman .readTimeout(0, TimeUnit.MILLISECONDS)
133aa3c92eaSWill Schurman .writeTimeout(0, TimeUnit.MILLISECONDS)
134aa3c92eaSWill Schurman .cookieJar(ReactCookieJarContainer())
135aa3c92eaSWill Schurman .cache(exponentNetwork.cache)
136aa3c92eaSWill Schurman
137aa3c92eaSWill Schurman if (BuildConfig.DEBUG) {
138aa3c92eaSWill Schurman // FIXME: 8/9/17
139aa3c92eaSWill Schurman // broke with lib versioning
140aa3c92eaSWill Schurman // clientBuilder.addNetworkInterceptor(new StethoInterceptor());
141aa3c92eaSWill Schurman }
142aa3c92eaSWill Schurman ReactNativeStaticHelpers.setExponentNetwork(exponentNetwork)
143aa3c92eaSWill Schurman }
144aa3c92eaSWill Schurman
145aa3c92eaSWill Schurman private val kernelInitialURL: String?
146aa3c92eaSWill Schurman get() {
147aa3c92eaSWill Schurman val activity = activityContext ?: return null
148aa3c92eaSWill Schurman val intent = activity.intent ?: return null
149aa3c92eaSWill Schurman val action = intent.action
150aa3c92eaSWill Schurman val uri = intent.data
151aa3c92eaSWill Schurman return if ((
152aa3c92eaSWill Schurman uri != null &&
153aa3c92eaSWill Schurman ((Intent.ACTION_VIEW == action) || (NfcAdapter.ACTION_NDEF_DISCOVERED == action))
154aa3c92eaSWill Schurman )
155aa3c92eaSWill Schurman ) {
156aa3c92eaSWill Schurman uri.toString()
157aa3c92eaSWill Schurman } else null
158aa3c92eaSWill Schurman }
159aa3c92eaSWill Schurman
160aa3c92eaSWill Schurman // Don't call this until a loading screen is up, since it has to do some work on the main thread.
161aa3c92eaSWill Schurman fun startJSKernel(activity: Activity?) {
162aa3c92eaSWill Schurman if (Constants.isStandaloneApp()) {
163aa3c92eaSWill Schurman return
164aa3c92eaSWill Schurman }
165aa3c92eaSWill Schurman activityContext = activity
166aa3c92eaSWill Schurman SoLoader.init(context, false)
167aa3c92eaSWill Schurman synchronized(this) {
168aa3c92eaSWill Schurman if (isStarted && !hasError) {
169aa3c92eaSWill Schurman return
170aa3c92eaSWill Schurman }
171aa3c92eaSWill Schurman isStarted = true
172aa3c92eaSWill Schurman }
173aa3c92eaSWill Schurman hasError = false
17419a0af8dSWill Schurman if (!exponentSharedPreferences.shouldUseEmbeddedKernel()) {
175aa3c92eaSWill Schurman try {
176aa3c92eaSWill Schurman // Make sure we can get the manifest successfully. This can fail in dev mode
177aa3c92eaSWill Schurman // if the kernel packager is not running.
1785f54863aSWill Schurman exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest
179aa3c92eaSWill Schurman } catch (e: Throwable) {
1807113164bSWill Schurman Exponent.instance
181aa3c92eaSWill Schurman .runOnUiThread { // Hack to make this show up for a while. Can't use an Alert because LauncherActivity has a transparent theme. This should only be seen by internal developers.
182aa3c92eaSWill Schurman var i = 0
183aa3c92eaSWill Schurman while (i < 3) {
184aa3c92eaSWill Schurman Toast.makeText(
185aa3c92eaSWill Schurman activityContext,
186aa3c92eaSWill Schurman "Kernel manifest invalid. Make sure `expo start` is running inside of exponent/home and rebuild the app.",
187aa3c92eaSWill Schurman Toast.LENGTH_LONG
188aa3c92eaSWill Schurman ).show()
189aa3c92eaSWill Schurman i++
190aa3c92eaSWill Schurman }
191aa3c92eaSWill Schurman }
192aa3c92eaSWill Schurman return
193aa3c92eaSWill Schurman }
194aa3c92eaSWill Schurman }
195aa3c92eaSWill Schurman
196aa3c92eaSWill Schurman // On first run use the embedded kernel js but fire off a request for the new js in the background.
197aa3c92eaSWill Schurman val bundleUrlToLoad =
198aa3c92eaSWill Schurman bundleUrl + (if (ExpoViewBuildConfig.DEBUG) "" else "?versionName=" + ExpoViewKernel.instance.versionName)
19919a0af8dSWill Schurman if (exponentSharedPreferences.shouldUseEmbeddedKernel()) {
200aa3c92eaSWill Schurman kernelBundleListener().onBundleLoaded(Constants.EMBEDDED_KERNEL_PATH)
201aa3c92eaSWill Schurman } else {
202aa3c92eaSWill Schurman var shouldNotUseKernelCache =
20388711250SWill Schurman exponentSharedPreferences.getBoolean(ExponentSharedPreferences.ExponentSharedPreferencesKey.SHOULD_NOT_USE_KERNEL_CACHE)
204aa3c92eaSWill Schurman if (!ExpoViewBuildConfig.DEBUG) {
205aa3c92eaSWill Schurman val oldKernelRevisionId =
20688711250SWill Schurman exponentSharedPreferences.getString(ExponentSharedPreferences.ExponentSharedPreferencesKey.KERNEL_REVISION_ID, "")
207aa3c92eaSWill Schurman if (oldKernelRevisionId != kernelRevisionId) {
208aa3c92eaSWill Schurman shouldNotUseKernelCache = true
209aa3c92eaSWill Schurman }
210aa3c92eaSWill Schurman }
2117113164bSWill Schurman Exponent.instance.loadJSBundle(
212aa3c92eaSWill Schurman null,
213aa3c92eaSWill Schurman bundleUrlToLoad,
2145f54863aSWill Schurman bundleAssetRequestHeaders,
215aa3c92eaSWill Schurman KernelConstants.KERNEL_BUNDLE_ID,
216aa3c92eaSWill Schurman RNObject.UNVERSIONED,
217aa3c92eaSWill Schurman kernelBundleListener(),
218aa3c92eaSWill Schurman shouldNotUseKernelCache
219aa3c92eaSWill Schurman )
220aa3c92eaSWill Schurman }
221aa3c92eaSWill Schurman }
222aa3c92eaSWill Schurman
223aa3c92eaSWill Schurman private fun kernelBundleListener(): BundleListener {
224aa3c92eaSWill Schurman return object : BundleListener {
225aa3c92eaSWill Schurman override fun onBundleLoaded(localBundlePath: String) {
226aa3c92eaSWill Schurman if (!ExpoViewBuildConfig.DEBUG) {
227aa3c92eaSWill Schurman exponentSharedPreferences.setString(
22888711250SWill Schurman ExponentSharedPreferences.ExponentSharedPreferencesKey.KERNEL_REVISION_ID,
229aa3c92eaSWill Schurman kernelRevisionId
230aa3c92eaSWill Schurman )
231aa3c92eaSWill Schurman }
2327113164bSWill Schurman Exponent.instance.runOnUiThread {
233aa3c92eaSWill Schurman val initialURL = kernelInitialURL
234aa3c92eaSWill Schurman val builder = ReactInstanceManager.builder()
235aa3c92eaSWill Schurman .setApplication(applicationContext)
236aa3c92eaSWill Schurman .setCurrentActivity(activityContext)
237aa3c92eaSWill Schurman .setJSBundleFile(localBundlePath)
238ff9f745fSKudo Chien .setJavaScriptExecutorFactory(jsExecutorFactory)
239aa3c92eaSWill Schurman .addPackage(MainReactPackage())
240aa3c92eaSWill Schurman .addPackage(
241aa3c92eaSWill Schurman ExponentPackage.kernelExponentPackage(
242aa3c92eaSWill Schurman context,
2435f54863aSWill Schurman exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest,
244aa3c92eaSWill Schurman HomeActivity.homeExpoPackages(),
245894c0196SŁukasz Kosmaty HomeActivity.Companion,
246aa3c92eaSWill Schurman initialURL
247aa3c92eaSWill Schurman )
248aa3c92eaSWill Schurman )
249aa3c92eaSWill Schurman .addPackage(
250aa3c92eaSWill Schurman ExpoTurboPackage.kernelExpoTurboPackage(
2515f54863aSWill Schurman exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest, initialURL
252aa3c92eaSWill Schurman )
253aa3c92eaSWill Schurman )
254aa3c92eaSWill Schurman .setInitialLifecycleState(LifecycleState.RESUMED)
2555f54863aSWill Schurman if (!KernelConfig.FORCE_NO_KERNEL_DEBUG_MODE && exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest.isDevelopmentMode()) {
256aa3c92eaSWill Schurman Exponent.enableDeveloperSupport(
2577113164bSWill Schurman kernelDebuggerHost, kernelMainModuleName,
2587113164bSWill Schurman RNObject.wrap(builder)
259aa3c92eaSWill Schurman )
260aa3c92eaSWill Schurman }
261aa3c92eaSWill Schurman reactInstanceManager = builder.build()
262aa3c92eaSWill Schurman reactInstanceManager!!.createReactContextInBackground()
263aa3c92eaSWill Schurman reactInstanceManager!!.onHostResume(activityContext, null)
264aa3c92eaSWill Schurman isRunning = true
265aa3c92eaSWill Schurman EventBus.getDefault().postSticky(KernelStartedRunningEvent())
266aa3c92eaSWill Schurman EXL.d(TAG, "Kernel started running.")
267aa3c92eaSWill Schurman
268aa3c92eaSWill Schurman // Reset this flag if we crashed
269aa3c92eaSWill Schurman exponentSharedPreferences.setBoolean(
27088711250SWill Schurman ExponentSharedPreferences.ExponentSharedPreferencesKey.SHOULD_NOT_USE_KERNEL_CACHE,
271aa3c92eaSWill Schurman false
272aa3c92eaSWill Schurman )
273aa3c92eaSWill Schurman }
274aa3c92eaSWill Schurman }
275aa3c92eaSWill Schurman
276aa3c92eaSWill Schurman override fun onError(e: Exception) {
277aa3c92eaSWill Schurman setHasError()
278aa3c92eaSWill Schurman if (ExpoViewBuildConfig.DEBUG) {
279aa3c92eaSWill Schurman handleError("Can't load kernel. Are you sure your packager is running and your phone is on the same wifi? " + e.message)
280aa3c92eaSWill Schurman } else {
281aa3c92eaSWill Schurman handleError("Expo requires an internet connection.")
282aa3c92eaSWill Schurman EXL.d(TAG, "Expo requires an internet connection." + e.message)
283aa3c92eaSWill Schurman }
284aa3c92eaSWill Schurman }
285aa3c92eaSWill Schurman }
286aa3c92eaSWill Schurman }
287aa3c92eaSWill Schurman
288aa3c92eaSWill Schurman private val kernelDebuggerHost: String
2895f54863aSWill Schurman get() = exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest.getDebuggerHost()
290aa3c92eaSWill Schurman private val kernelMainModuleName: String
2915f54863aSWill Schurman get() = exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest.getMainModuleName()
292aa3c92eaSWill Schurman private val bundleUrl: String?
293aa3c92eaSWill Schurman get() {
294aa3c92eaSWill Schurman return try {
2955f54863aSWill Schurman exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest.getBundleURL()
296aa3c92eaSWill Schurman } catch (e: JSONException) {
297aa3c92eaSWill Schurman KernelProvider.instance.handleError(e)
298aa3c92eaSWill Schurman null
299aa3c92eaSWill Schurman }
300aa3c92eaSWill Schurman }
3015f54863aSWill Schurman private val bundleAssetRequestHeaders: JSONObject
3025f54863aSWill Schurman get() {
3035f54863aSWill Schurman return try {
3045f54863aSWill Schurman val manifestAndAssetRequestHeaders = exponentManifest.getKernelManifestAndAssetRequestHeaders()
3055f54863aSWill Schurman val manifest = manifestAndAssetRequestHeaders.manifest
3065f54863aSWill Schurman if (manifest is NewManifest) {
3075f54863aSWill Schurman val bundleKey = manifest.getLaunchAsset().getString("key")
3085f54863aSWill Schurman val map: Map<String, JSONObject> = manifestAndAssetRequestHeaders.assetRequestHeaders.let { it.keys().asSequence().associateWith { key -> it.require(key) } } ?: mapOf()
3095f54863aSWill Schurman map[bundleKey] ?: JSONObject()
3105f54863aSWill Schurman } else {
3115f54863aSWill Schurman JSONObject()
3125f54863aSWill Schurman }
3135f54863aSWill Schurman } catch (e: JSONException) {
3145f54863aSWill Schurman KernelProvider.instance.handleError(e)
3155f54863aSWill Schurman JSONObject()
3165f54863aSWill Schurman }
3175f54863aSWill Schurman }
318aa3c92eaSWill Schurman private val kernelRevisionId: String?
319aa3c92eaSWill Schurman get() {
320aa3c92eaSWill Schurman return try {
3215f54863aSWill Schurman exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest.getRevisionId()
322aa3c92eaSWill Schurman } catch (e: JSONException) {
323aa3c92eaSWill Schurman KernelProvider.instance.handleError(e)
324aa3c92eaSWill Schurman null
325aa3c92eaSWill Schurman }
326aa3c92eaSWill Schurman }
327aa3c92eaSWill Schurman var isRunning: Boolean = false
328aa3c92eaSWill Schurman get() = field && !hasError
329aa3c92eaSWill Schurman private set
330aa3c92eaSWill Schurman
331aa3c92eaSWill Schurman val reactRootView: ReactRootView
332aa3c92eaSWill Schurman get() {
333570504b8SKudo Chien val reactRootView: ReactRootView = ReactUnthemedRootView(activityContext)
334aa3c92eaSWill Schurman reactRootView.startReactApplication(
335aa3c92eaSWill Schurman reactInstanceManager,
336aa3c92eaSWill Schurman KernelConstants.HOME_MODULE_NAME,
337aa3c92eaSWill Schurman kernelLaunchOptions
338aa3c92eaSWill Schurman )
339aa3c92eaSWill Schurman return reactRootView
340aa3c92eaSWill Schurman }
341aa3c92eaSWill Schurman private val kernelLaunchOptions: Bundle
342aa3c92eaSWill Schurman get() {
343aa3c92eaSWill Schurman val exponentProps = JSONObject()
34488711250SWill Schurman val referrer = exponentSharedPreferences.getString(ExponentSharedPreferences.ExponentSharedPreferencesKey.REFERRER_KEY)
345aa3c92eaSWill Schurman if (referrer != null) {
346aa3c92eaSWill Schurman try {
347aa3c92eaSWill Schurman exponentProps.put("referrer", referrer)
348aa3c92eaSWill Schurman } catch (e: JSONException) {
349aa3c92eaSWill Schurman EXL.e(TAG, e)
350aa3c92eaSWill Schurman }
351aa3c92eaSWill Schurman }
352aa3c92eaSWill Schurman val bundle = Bundle()
353aa3c92eaSWill Schurman try {
354aa3c92eaSWill Schurman bundle.putBundle("exp", BundleJSONConverter.convertToBundle(exponentProps))
355aa3c92eaSWill Schurman } catch (e: JSONException) {
356aa3c92eaSWill Schurman throw Error("JSONObject failed to be converted to Bundle", e)
357aa3c92eaSWill Schurman }
358aa3c92eaSWill Schurman return bundle
359aa3c92eaSWill Schurman }
360ff9f745fSKudo Chien private val jsExecutorFactory: JavaScriptExecutorFactory
361ff9f745fSKudo Chien get() {
3625f54863aSWill Schurman val manifest = exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest
363ff9f745fSKudo Chien val appName = manifest.getName() ?: ""
364ff9f745fSKudo Chien val deviceName = AndroidInfoHelpers.getFriendlyDeviceName()
365ff9f745fSKudo Chien
366ff9f745fSKudo Chien val jsEngineFromManifest = manifest.jsEngine
367ff9f745fSKudo Chien return if (jsEngineFromManifest == "hermes") HermesExecutorFactory() else JSCExecutorFactory(
368ff9f745fSKudo Chien appName,
369ff9f745fSKudo Chien deviceName
370ff9f745fSKudo Chien )
371ff9f745fSKudo Chien }
372aa3c92eaSWill Schurman
373aa3c92eaSWill Schurman fun hasOptionsForManifestUrl(manifestUrl: String?): Boolean {
374aa3c92eaSWill Schurman return manifestUrlToOptions.containsKey(manifestUrl)
375aa3c92eaSWill Schurman }
376aa3c92eaSWill Schurman
377aa3c92eaSWill Schurman fun popOptionsForManifestUrl(manifestUrl: String?): ExperienceOptions? {
378aa3c92eaSWill Schurman return manifestUrlToOptions.remove(manifestUrl)
379aa3c92eaSWill Schurman }
380aa3c92eaSWill Schurman
381aa3c92eaSWill Schurman fun addAppLoaderForManifestUrl(manifestUrl: String, appLoader: ExpoUpdatesAppLoader) {
382aa3c92eaSWill Schurman manifestUrlToAppLoader[manifestUrl] = appLoader
383aa3c92eaSWill Schurman }
384aa3c92eaSWill Schurman
385aa3c92eaSWill Schurman override fun getAppLoaderForManifestUrl(manifestUrl: String?): ExpoUpdatesAppLoader? {
386aa3c92eaSWill Schurman return manifestUrlToAppLoader[manifestUrl]
387aa3c92eaSWill Schurman }
388aa3c92eaSWill Schurman
389aa3c92eaSWill Schurman fun getExperienceActivityTask(manifestUrl: String): ExperienceActivityTask {
390aa3c92eaSWill Schurman var task = manifestUrlToExperienceActivityTask[manifestUrl]
391aa3c92eaSWill Schurman if (task != null) {
392aa3c92eaSWill Schurman return task
393aa3c92eaSWill Schurman }
394aa3c92eaSWill Schurman task = ExperienceActivityTask(manifestUrl)
395aa3c92eaSWill Schurman manifestUrlToExperienceActivityTask[manifestUrl] = task
396aa3c92eaSWill Schurman return task
397aa3c92eaSWill Schurman }
398aa3c92eaSWill Schurman
399aa3c92eaSWill Schurman fun removeExperienceActivityTask(manifestUrl: String?) {
400aa3c92eaSWill Schurman if (manifestUrl != null) {
401aa3c92eaSWill Schurman manifestUrlToExperienceActivityTask.remove(manifestUrl)
402aa3c92eaSWill Schurman }
403aa3c92eaSWill Schurman }
404aa3c92eaSWill Schurman
405aa3c92eaSWill Schurman fun openHomeActivity() {
406aa3c92eaSWill Schurman val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
407aa3c92eaSWill Schurman for (task: AppTask in manager.appTasks) {
408aa3c92eaSWill Schurman val baseIntent = task.taskInfo.baseIntent
409aa3c92eaSWill Schurman if ((HomeActivity::class.java.name == baseIntent.component!!.className)) {
410aa3c92eaSWill Schurman task.moveToFront()
411aa3c92eaSWill Schurman return
412aa3c92eaSWill Schurman }
413aa3c92eaSWill Schurman }
414aa3c92eaSWill Schurman val intent = Intent(activityContext, HomeActivity::class.java)
415aa3c92eaSWill Schurman addIntentDocumentFlags(intent)
416aa3c92eaSWill Schurman activityContext!!.startActivity(intent)
417aa3c92eaSWill Schurman }
418aa3c92eaSWill Schurman
419aa3c92eaSWill Schurman private fun openShellAppActivity(forceCache: Boolean) {
420aa3c92eaSWill Schurman try {
421aa3c92eaSWill Schurman val activityClass = Class.forName("host.exp.exponent.MainActivity")
422aa3c92eaSWill Schurman val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
423aa3c92eaSWill Schurman for (task: AppTask in manager.appTasks) {
424aa3c92eaSWill Schurman val baseIntent = task.taskInfo.baseIntent
425aa3c92eaSWill Schurman if ((activityClass.name == baseIntent.component!!.className)) {
426aa3c92eaSWill Schurman moveTaskToFront(task.taskInfo.id)
427aa3c92eaSWill Schurman return
428aa3c92eaSWill Schurman }
429aa3c92eaSWill Schurman }
430aa3c92eaSWill Schurman val intent = Intent(activityContext, activityClass)
431aa3c92eaSWill Schurman addIntentDocumentFlags(intent)
432aa3c92eaSWill Schurman if (forceCache) {
433aa3c92eaSWill Schurman intent.putExtra(KernelConstants.LOAD_FROM_CACHE_KEY, true)
434aa3c92eaSWill Schurman }
435aa3c92eaSWill Schurman activityContext!!.startActivity(intent)
436aa3c92eaSWill Schurman } catch (e: ClassNotFoundException) {
437aa3c92eaSWill Schurman throw IllegalStateException("Could not find activity to open (MainActivity is not present).")
438aa3c92eaSWill Schurman }
439aa3c92eaSWill Schurman }
440aa3c92eaSWill Schurman
441aa3c92eaSWill Schurman /*
442aa3c92eaSWill Schurman *
443aa3c92eaSWill Schurman * Manifests
444aa3c92eaSWill Schurman *
445aa3c92eaSWill Schurman */
446aa3c92eaSWill Schurman fun handleIntent(activity: Activity, intent: Intent) {
447aa3c92eaSWill Schurman try {
448aa3c92eaSWill Schurman if (intent.getBooleanExtra("EXKernelDisableNuxDefaultsKey", false)) {
449aa3c92eaSWill Schurman Constants.DISABLE_NUX = true
450aa3c92eaSWill Schurman }
451aa3c92eaSWill Schurman } catch (e: Throwable) {
452aa3c92eaSWill Schurman }
453aa3c92eaSWill Schurman activityContext = activity
454aa3c92eaSWill Schurman if (intent.action != null && (ExpoHandlingDelegate.OPEN_APP_INTENT_ACTION == intent.action)) {
455aa3c92eaSWill Schurman if (!openExperienceFromNotificationIntent(intent)) {
456aa3c92eaSWill Schurman openDefaultUrl()
457aa3c92eaSWill Schurman }
458aa3c92eaSWill Schurman return
459aa3c92eaSWill Schurman }
460aa3c92eaSWill Schurman val bundle = intent.extras
461aa3c92eaSWill Schurman val uri = intent.data
462aa3c92eaSWill Schurman val intentUri = uri?.toString()
463aa3c92eaSWill Schurman if (bundle != null) {
464aa3c92eaSWill Schurman // Notification
465aa3c92eaSWill Schurman val notification = bundle.getString(KernelConstants.NOTIFICATION_KEY) // deprecated
466aa3c92eaSWill Schurman val notificationObject = bundle.getString(KernelConstants.NOTIFICATION_OBJECT_KEY)
467aa3c92eaSWill Schurman val notificationManifestUrl = bundle.getString(KernelConstants.NOTIFICATION_MANIFEST_URL_KEY)
468aa3c92eaSWill Schurman if (notificationManifestUrl != null) {
469aa3c92eaSWill Schurman val exponentNotification = ExponentNotification.fromJSONObjectString(notificationObject)
470aa3c92eaSWill Schurman if (exponentNotification != null) {
471aa3c92eaSWill Schurman // Add action type
472aa3c92eaSWill Schurman if (bundle.containsKey(KernelConstants.NOTIFICATION_ACTION_TYPE_KEY)) {
473cd4bd26bSWill Schurman exponentNotification.actionType = bundle.getString(KernelConstants.NOTIFICATION_ACTION_TYPE_KEY)
474aa3c92eaSWill Schurman val manager = ExponentNotificationManager(context)
475aa3c92eaSWill Schurman val experienceKey = ExperienceKey(exponentNotification.experienceScopeKey)
476aa3c92eaSWill Schurman manager.cancel(experienceKey, exponentNotification.notificationId)
477aa3c92eaSWill Schurman }
478aa3c92eaSWill Schurman // Add remote input
479aa3c92eaSWill Schurman val remoteInput = RemoteInput.getResultsFromIntent(intent)
480aa3c92eaSWill Schurman if (remoteInput != null) {
481cd4bd26bSWill Schurman exponentNotification.inputText = remoteInput.getString(NotificationActionCenter.KEY_TEXT_REPLY)
482aa3c92eaSWill Schurman }
483aa3c92eaSWill Schurman }
484aa3c92eaSWill Schurman openExperience(
485aa3c92eaSWill Schurman ExperienceOptions(
486aa3c92eaSWill Schurman notificationManifestUrl,
487aa3c92eaSWill Schurman intentUri ?: notificationManifestUrl,
488aa3c92eaSWill Schurman notification,
489aa3c92eaSWill Schurman exponentNotification
490aa3c92eaSWill Schurman )
491aa3c92eaSWill Schurman )
492aa3c92eaSWill Schurman return
493aa3c92eaSWill Schurman }
494aa3c92eaSWill Schurman
495aa3c92eaSWill Schurman // Shortcut
496aa3c92eaSWill Schurman // TODO: Remove once we decide to stop supporting shortcuts to experiences.
497aa3c92eaSWill Schurman val shortcutManifestUrl = bundle.getString(KernelConstants.SHORTCUT_MANIFEST_URL_KEY)
498aa3c92eaSWill Schurman if (shortcutManifestUrl != null) {
499aa3c92eaSWill Schurman openExperience(ExperienceOptions(shortcutManifestUrl, intentUri, null))
500aa3c92eaSWill Schurman return
501aa3c92eaSWill Schurman }
502aa3c92eaSWill Schurman }
503aa3c92eaSWill Schurman if (uri != null && shouldOpenUrl(uri)) {
504aa3c92eaSWill Schurman if (Constants.INITIAL_URL == null) {
505aa3c92eaSWill Schurman // We got an "exp://", "exps://", "http://", or "https://" app link
506aa3c92eaSWill Schurman openExperience(ExperienceOptions(uri.toString(), uri.toString(), null))
507aa3c92eaSWill Schurman return
508aa3c92eaSWill Schurman } else {
509aa3c92eaSWill Schurman // We got a custom scheme link
510aa3c92eaSWill Schurman // TODO: we still might want to parse this if we're running a different experience inside a
511aa3c92eaSWill Schurman // shell app. For example, we are running Brighten in the List shell and go to Twitter login.
512aa3c92eaSWill Schurman // We might want to set the return uri to thelistapp://exp.host/@brighten/brighten+deeplink
513aa3c92eaSWill Schurman // But we also can't break thelistapp:// deep links that look like thelistapp://l/listid
514aa3c92eaSWill Schurman openExperience(ExperienceOptions(Constants.INITIAL_URL, uri.toString(), null))
515aa3c92eaSWill Schurman return
516aa3c92eaSWill Schurman }
517aa3c92eaSWill Schurman }
518aa3c92eaSWill Schurman openDefaultUrl()
519aa3c92eaSWill Schurman }
520aa3c92eaSWill Schurman
521aa3c92eaSWill Schurman // Certain links (i.e. 'expo.io/expo-go') should just open the HomeScreen
522aa3c92eaSWill Schurman private fun shouldOpenUrl(uri: Uri): Boolean {
523aa3c92eaSWill Schurman val host = uri.host ?: ""
524aa3c92eaSWill Schurman val path = uri.path ?: ""
525aa3c92eaSWill Schurman return !(((host == "expo.io") || (host == "expo.dev")) && (path == "/expo-go"))
526aa3c92eaSWill Schurman }
527aa3c92eaSWill Schurman
528aa3c92eaSWill Schurman private fun openExperienceFromNotificationIntent(intent: Intent): Boolean {
529b90b2e78SKudo Chien val response = getNotificationResponseFromOpenIntent(intent)
5304d53947fSWill Schurman val experienceScopeKey = ScopedNotificationsUtils.getExperienceScopeKey(response) ?: return false
5314d53947fSWill Schurman val exponentDBObject = try {
5324d53947fSWill Schurman val exponentDBObjectInner = ExponentDB.experienceScopeKeyToExperienceSync(experienceScopeKey)
5334d53947fSWill Schurman if (exponentDBObjectInner == null) {
534aa3c92eaSWill Schurman Log.w("expo-notifications", "Couldn't find experience from scopeKey: $experienceScopeKey")
535aa3c92eaSWill Schurman }
5364d53947fSWill Schurman exponentDBObjectInner
5374d53947fSWill Schurman } catch (e: JSONException) {
5384d53947fSWill Schurman Log.w("expo-notifications", "Couldn't deserialize experience from scopeKey: $experienceScopeKey")
5394d53947fSWill Schurman null
5404d53947fSWill Schurman } ?: return false
5414d53947fSWill Schurman
5424d53947fSWill Schurman val manifestUrl = exponentDBObject.manifestUrl
543aa3c92eaSWill Schurman openExperience(ExperienceOptions(manifestUrl, manifestUrl, null))
544aa3c92eaSWill Schurman return true
545aa3c92eaSWill Schurman }
546aa3c92eaSWill Schurman
547aa3c92eaSWill Schurman private fun openDefaultUrl() {
548aa3c92eaSWill Schurman val defaultUrl =
549aa3c92eaSWill Schurman if (Constants.INITIAL_URL == null) KernelConstants.HOME_MANIFEST_URL else Constants.INITIAL_URL
550aa3c92eaSWill Schurman openExperience(ExperienceOptions(defaultUrl, defaultUrl, null))
551aa3c92eaSWill Schurman }
552aa3c92eaSWill Schurman
553aa3c92eaSWill Schurman override fun openExperience(options: ExperienceOptions) {
554aa3c92eaSWill Schurman openManifestUrl(getManifestUrlFromFullUri(options.manifestUri), options, true)
555aa3c92eaSWill Schurman }
556aa3c92eaSWill Schurman
557aa3c92eaSWill Schurman private fun getManifestUrlFromFullUri(uriString: String?): String? {
558aa3c92eaSWill Schurman if (uriString == null) {
559aa3c92eaSWill Schurman return null
560aa3c92eaSWill Schurman }
561aa3c92eaSWill Schurman
562aa3c92eaSWill Schurman val uri = Uri.parse(uriString)
563aa3c92eaSWill Schurman val builder = uri.buildUpon()
564aa3c92eaSWill Schurman val deepLinkPositionDashes =
565aa3c92eaSWill Schurman uriString.indexOf(ExponentManifest.DEEP_LINK_SEPARATOR_WITH_SLASH)
566aa3c92eaSWill Schurman if (deepLinkPositionDashes >= 0) {
567aa3c92eaSWill Schurman // do this safely so we preserve any query string
568aa3c92eaSWill Schurman val pathSegments = uri.pathSegments
569aa3c92eaSWill Schurman builder.path(null)
570aa3c92eaSWill Schurman for (segment: String in pathSegments) {
571aa3c92eaSWill Schurman if ((ExponentManifest.DEEP_LINK_SEPARATOR == segment)) {
572aa3c92eaSWill Schurman break
573aa3c92eaSWill Schurman }
574aa3c92eaSWill Schurman builder.appendEncodedPath(segment)
575aa3c92eaSWill Schurman }
576aa3c92eaSWill Schurman }
577aa3c92eaSWill Schurman
578aa3c92eaSWill Schurman // transfer the release-channel param to the built URL as this will cause Expo Go to treat
579aa3c92eaSWill Schurman // this as a different project
580aa3c92eaSWill Schurman var releaseChannel = uri.getQueryParameter(ExponentManifest.QUERY_PARAM_KEY_RELEASE_CHANNEL)
581aa3c92eaSWill Schurman builder.query(null)
582aa3c92eaSWill Schurman if (releaseChannel != null) {
583aa3c92eaSWill Schurman // release channels cannot contain the ' ' character, so if this is present,
584aa3c92eaSWill Schurman // it must be an encoded form of '+' which indicated a deep link in SDK <27.
585aa3c92eaSWill Schurman // therefore, nothing after this is part of the release channel name so we should strip it.
586aa3c92eaSWill Schurman // TODO: remove this check once SDK 26 and below are no longer supported
587aa3c92eaSWill Schurman val releaseChannelDeepLinkPosition = releaseChannel.indexOf(' ')
588aa3c92eaSWill Schurman if (releaseChannelDeepLinkPosition > -1) {
589aa3c92eaSWill Schurman releaseChannel = releaseChannel.substring(0, releaseChannelDeepLinkPosition)
590aa3c92eaSWill Schurman }
591aa3c92eaSWill Schurman builder.appendQueryParameter(
592aa3c92eaSWill Schurman ExponentManifest.QUERY_PARAM_KEY_RELEASE_CHANNEL,
593aa3c92eaSWill Schurman releaseChannel
594aa3c92eaSWill Schurman )
595aa3c92eaSWill Schurman }
596aa3c92eaSWill Schurman
597aa3c92eaSWill Schurman // transfer the expo-updates query params: runtime-version, channel-name
598aa3c92eaSWill Schurman val expoUpdatesQueryParameters = listOf(
599aa3c92eaSWill Schurman ExponentManifest.QUERY_PARAM_KEY_EXPO_UPDATES_RUNTIME_VERSION,
600aa3c92eaSWill Schurman ExponentManifest.QUERY_PARAM_KEY_EXPO_UPDATES_CHANNEL_NAME
601aa3c92eaSWill Schurman )
602aa3c92eaSWill Schurman for (queryParameter: String in expoUpdatesQueryParameters) {
603aa3c92eaSWill Schurman val queryParameterValue = uri.getQueryParameter(queryParameter)
604aa3c92eaSWill Schurman if (queryParameterValue != null) {
605aa3c92eaSWill Schurman builder.appendQueryParameter(queryParameter, queryParameterValue)
606aa3c92eaSWill Schurman }
607aa3c92eaSWill Schurman }
608aa3c92eaSWill Schurman
609aa3c92eaSWill Schurman // ignore fragments as well (e.g. those added by auth-session)
610aa3c92eaSWill Schurman builder.fragment(null)
611aa3c92eaSWill Schurman var newUriString = builder.build().toString()
612aa3c92eaSWill Schurman val deepLinkPositionPlus = newUriString.indexOf('+')
613aa3c92eaSWill Schurman if (deepLinkPositionPlus >= 0 && deepLinkPositionDashes < 0) {
614aa3c92eaSWill Schurman // need to keep this for backwards compatibility
615aa3c92eaSWill Schurman newUriString = newUriString.substring(0, deepLinkPositionPlus)
616aa3c92eaSWill Schurman }
617aa3c92eaSWill Schurman
618aa3c92eaSWill Schurman // manifest url doesn't have a trailing slash
619aa3c92eaSWill Schurman if (newUriString.isNotEmpty()) {
620aa3c92eaSWill Schurman val lastUrlChar = newUriString[newUriString.length - 1]
621aa3c92eaSWill Schurman if (lastUrlChar == '/') {
622aa3c92eaSWill Schurman newUriString = newUriString.substring(0, newUriString.length - 1)
623aa3c92eaSWill Schurman }
624aa3c92eaSWill Schurman }
625aa3c92eaSWill Schurman return newUriString
626aa3c92eaSWill Schurman }
627aa3c92eaSWill Schurman
628aa3c92eaSWill Schurman private fun openManifestUrl(
629aa3c92eaSWill Schurman manifestUrl: String?,
630aa3c92eaSWill Schurman options: ExperienceOptions?,
631aa3c92eaSWill Schurman isOptimistic: Boolean,
632aa3c92eaSWill Schurman forceCache: Boolean = false
633aa3c92eaSWill Schurman ) {
634aa3c92eaSWill Schurman SoLoader.init(context, false)
635aa3c92eaSWill Schurman if (options == null) {
636aa3c92eaSWill Schurman manifestUrlToOptions.remove(manifestUrl)
637aa3c92eaSWill Schurman } else {
638aa3c92eaSWill Schurman manifestUrlToOptions[manifestUrl] = options
639aa3c92eaSWill Schurman }
640aa3c92eaSWill Schurman if (manifestUrl == null || (manifestUrl == KernelConstants.HOME_MANIFEST_URL)) {
641aa3c92eaSWill Schurman openHomeActivity()
642aa3c92eaSWill Schurman return
643aa3c92eaSWill Schurman }
644aa3c92eaSWill Schurman if (Constants.isStandaloneApp()) {
645aa3c92eaSWill Schurman openShellAppActivity(forceCache)
646aa3c92eaSWill Schurman return
647aa3c92eaSWill Schurman }
648aa3c92eaSWill Schurman ErrorActivity.clearErrorList()
649aa3c92eaSWill Schurman val tasks: List<AppTask> = experienceActivityTasks
65040772498SKudo Chien var existingTask: AppTask? = run {
651aa3c92eaSWill Schurman for (i in tasks.indices) {
652aa3c92eaSWill Schurman val task = tasks[i]
65340772498SKudo Chien // When deep linking from `NotificationForwarderActivity`, the task will finish immediately.
65440772498SKudo Chien // There is race condition to retrieve the taskInfo from the finishing task.
65540772498SKudo Chien // Uses try-catch to handle the cases.
65640772498SKudo Chien try {
657aa3c92eaSWill Schurman val baseIntent = task.taskInfo.baseIntent
658aa3c92eaSWill Schurman if (baseIntent.hasExtra(KernelConstants.MANIFEST_URL_KEY) && (
659aa3c92eaSWill Schurman baseIntent.getStringExtra(
660aa3c92eaSWill Schurman KernelConstants.MANIFEST_URL_KEY
661aa3c92eaSWill Schurman ) == manifestUrl
662aa3c92eaSWill Schurman )
663aa3c92eaSWill Schurman ) {
66440772498SKudo Chien return@run task
665aa3c92eaSWill Schurman }
66640772498SKudo Chien } catch (e: Exception) {}
667aa3c92eaSWill Schurman }
66840772498SKudo Chien return@run null
66940772498SKudo Chien }
67040772498SKudo Chien
671aa3c92eaSWill Schurman if (isOptimistic && existingTask == null) {
672aa3c92eaSWill Schurman openOptimisticExperienceActivity(manifestUrl)
673aa3c92eaSWill Schurman }
674aa3c92eaSWill Schurman if (existingTask != null) {
675aa3c92eaSWill Schurman try {
676aa3c92eaSWill Schurman moveTaskToFront(existingTask.taskInfo.id)
677aa3c92eaSWill Schurman } catch (e: IllegalArgumentException) {
678aa3c92eaSWill Schurman // Sometimes task can't be found.
679aa3c92eaSWill Schurman existingTask = null
680aa3c92eaSWill Schurman openOptimisticExperienceActivity(manifestUrl)
681aa3c92eaSWill Schurman }
682aa3c92eaSWill Schurman }
683aa3c92eaSWill Schurman val finalExistingTask = existingTask
684aa3c92eaSWill Schurman if (existingTask == null) {
685aa3c92eaSWill Schurman ExpoUpdatesAppLoader(
686aa3c92eaSWill Schurman manifestUrl,
687aa3c92eaSWill Schurman object : AppLoaderCallback {
68850661f5cSWill Schurman override fun onOptimisticManifest(optimisticManifest: Manifest) {
6897113164bSWill Schurman Exponent.instance
690aa3c92eaSWill Schurman .runOnUiThread { sendOptimisticManifestToExperienceActivity(optimisticManifest) }
691aa3c92eaSWill Schurman }
692aa3c92eaSWill Schurman
69350661f5cSWill Schurman override fun onManifestCompleted(manifest: Manifest) {
6947113164bSWill Schurman Exponent.instance.runOnUiThread {
695aa3c92eaSWill Schurman try {
696aa3c92eaSWill Schurman openManifestUrlStep2(manifestUrl, manifest, finalExistingTask)
697aa3c92eaSWill Schurman } catch (e: JSONException) {
698aa3c92eaSWill Schurman handleError(e)
699aa3c92eaSWill Schurman }
700aa3c92eaSWill Schurman }
701aa3c92eaSWill Schurman }
702aa3c92eaSWill Schurman
703031e1c70SWill Schurman override fun onBundleCompleted(localBundlePath: String) {
7047113164bSWill Schurman Exponent.instance.runOnUiThread { sendBundleToExperienceActivity(localBundlePath) }
705aa3c92eaSWill Schurman }
706aa3c92eaSWill Schurman
707aa3c92eaSWill Schurman override fun emitEvent(params: JSONObject) {
708aa3c92eaSWill Schurman val task = manifestUrlToExperienceActivityTask[manifestUrl]
709aa3c92eaSWill Schurman if (task != null) {
710aa3c92eaSWill Schurman val experienceActivity = task.experienceActivity!!.get()
711aa3c92eaSWill Schurman experienceActivity?.emitUpdatesEvent(params)
712aa3c92eaSWill Schurman }
713aa3c92eaSWill Schurman }
714aa3c92eaSWill Schurman
715aa3c92eaSWill Schurman override fun updateStatus(status: AppLoaderStatus) {
716aa3c92eaSWill Schurman if (optimisticActivity != null) {
717aa3c92eaSWill Schurman optimisticActivity!!.setLoadingProgressStatusIfEnabled(status)
718aa3c92eaSWill Schurman }
719aa3c92eaSWill Schurman }
720aa3c92eaSWill Schurman
721aa3c92eaSWill Schurman override fun onError(e: Exception) {
7227113164bSWill Schurman Exponent.instance.runOnUiThread { handleError(e) }
723aa3c92eaSWill Schurman }
724aa3c92eaSWill Schurman },
725aa3c92eaSWill Schurman forceCache
726aa3c92eaSWill Schurman ).start(context)
727aa3c92eaSWill Schurman }
728aa3c92eaSWill Schurman }
729aa3c92eaSWill Schurman
730aa3c92eaSWill Schurman @Throws(JSONException::class)
731aa3c92eaSWill Schurman private fun openManifestUrlStep2(
732aa3c92eaSWill Schurman manifestUrl: String,
73350661f5cSWill Schurman manifest: Manifest,
734aa3c92eaSWill Schurman existingTask: AppTask?
735aa3c92eaSWill Schurman ) {
736aa3c92eaSWill Schurman val bundleUrl = toHttp(manifest.getBundleURL())
737aa3c92eaSWill Schurman val task = getExperienceActivityTask(manifestUrl)
738aa3c92eaSWill Schurman task.bundleUrl = bundleUrl
73950661f5cSWill Schurman ExponentManifest.normalizeManifestInPlace(manifest, manifestUrl)
740aa3c92eaSWill Schurman if (existingTask == null) {
7417113164bSWill Schurman sendManifestToExperienceActivity(manifestUrl, manifest, bundleUrl)
742aa3c92eaSWill Schurman }
743aa3c92eaSWill Schurman val params = Arguments.createMap().apply {
744aa3c92eaSWill Schurman putString("manifestUrl", manifestUrl)
745aa3c92eaSWill Schurman putString("manifestString", manifest.toString())
746aa3c92eaSWill Schurman }
747aa3c92eaSWill Schurman queueEvent(
748aa3c92eaSWill Schurman "ExponentKernel.addHistoryItem", params,
749aa3c92eaSWill Schurman object : KernelEventCallback {
750aa3c92eaSWill Schurman override fun onEventSuccess(result: ReadableMap) {
751aa3c92eaSWill Schurman EXL.d(TAG, "Successfully called ExponentKernel.addHistoryItem in kernel JS.")
752aa3c92eaSWill Schurman }
753aa3c92eaSWill Schurman
754aa3c92eaSWill Schurman override fun onEventFailure(errorMessage: String?) {
755aa3c92eaSWill Schurman EXL.e(TAG, "Error calling ExponentKernel.addHistoryItem in kernel JS: $errorMessage")
756aa3c92eaSWill Schurman }
757aa3c92eaSWill Schurman }
758aa3c92eaSWill Schurman )
759aa3c92eaSWill Schurman killOrphanedLauncherActivities()
760aa3c92eaSWill Schurman }
761aa3c92eaSWill Schurman
762aa3c92eaSWill Schurman /*
763aa3c92eaSWill Schurman *
764aa3c92eaSWill Schurman * Optimistic experiences
765aa3c92eaSWill Schurman *
766aa3c92eaSWill Schurman */
767aa3c92eaSWill Schurman private fun openOptimisticExperienceActivity(manifestUrl: String?) {
768aa3c92eaSWill Schurman try {
769aa3c92eaSWill Schurman val intent = Intent(activityContext, ExperienceActivity::class.java).apply {
770aa3c92eaSWill Schurman addIntentDocumentFlags(this)
771aa3c92eaSWill Schurman putExtra(KernelConstants.MANIFEST_URL_KEY, manifestUrl)
772aa3c92eaSWill Schurman putExtra(KernelConstants.IS_OPTIMISTIC_KEY, true)
773aa3c92eaSWill Schurman }
774aa3c92eaSWill Schurman activityContext!!.startActivity(intent)
775aa3c92eaSWill Schurman } catch (e: Throwable) {
776aa3c92eaSWill Schurman EXL.e(TAG, e)
777aa3c92eaSWill Schurman }
778aa3c92eaSWill Schurman }
779aa3c92eaSWill Schurman
780aa3c92eaSWill Schurman fun setOptimisticActivity(experienceActivity: ExperienceActivity, taskId: Int) {
781aa3c92eaSWill Schurman optimisticActivity = experienceActivity
782aa3c92eaSWill Schurman optimisticTaskId = taskId
783aa3c92eaSWill Schurman AsyncCondition.notify(KernelConstants.OPEN_OPTIMISTIC_EXPERIENCE_ACTIVITY_KEY)
784aa3c92eaSWill Schurman AsyncCondition.notify(KernelConstants.OPEN_EXPERIENCE_ACTIVITY_KEY)
785aa3c92eaSWill Schurman }
786aa3c92eaSWill Schurman
78750661f5cSWill Schurman fun sendOptimisticManifestToExperienceActivity(optimisticManifest: Manifest) {
788aa3c92eaSWill Schurman AsyncCondition.wait(
789aa3c92eaSWill Schurman KernelConstants.OPEN_OPTIMISTIC_EXPERIENCE_ACTIVITY_KEY,
790aa3c92eaSWill Schurman object : AsyncConditionListener {
791aa3c92eaSWill Schurman override fun isReady(): Boolean {
792aa3c92eaSWill Schurman return optimisticActivity != null && optimisticTaskId != null
793aa3c92eaSWill Schurman }
794aa3c92eaSWill Schurman
795aa3c92eaSWill Schurman override fun execute() {
796aa3c92eaSWill Schurman optimisticActivity!!.setOptimisticManifest(optimisticManifest)
797aa3c92eaSWill Schurman }
798aa3c92eaSWill Schurman }
799aa3c92eaSWill Schurman )
800aa3c92eaSWill Schurman }
801aa3c92eaSWill Schurman
802aa3c92eaSWill Schurman private fun sendManifestToExperienceActivity(
8037113164bSWill Schurman manifestUrl: String,
80450661f5cSWill Schurman manifest: Manifest,
8057113164bSWill Schurman bundleUrl: String,
806aa3c92eaSWill Schurman ) {
807aa3c92eaSWill Schurman AsyncCondition.wait(
808aa3c92eaSWill Schurman KernelConstants.OPEN_EXPERIENCE_ACTIVITY_KEY,
809aa3c92eaSWill Schurman object : AsyncConditionListener {
810aa3c92eaSWill Schurman override fun isReady(): Boolean {
811aa3c92eaSWill Schurman return optimisticActivity != null && optimisticTaskId != null
812aa3c92eaSWill Schurman }
813aa3c92eaSWill Schurman
814aa3c92eaSWill Schurman override fun execute() {
8157113164bSWill Schurman optimisticActivity!!.setManifest(manifestUrl, manifest, bundleUrl)
816aa3c92eaSWill Schurman AsyncCondition.notify(KernelConstants.LOAD_BUNDLE_FOR_EXPERIENCE_ACTIVITY_KEY)
817aa3c92eaSWill Schurman }
818aa3c92eaSWill Schurman }
819aa3c92eaSWill Schurman )
820aa3c92eaSWill Schurman }
821aa3c92eaSWill Schurman
822031e1c70SWill Schurman private fun sendBundleToExperienceActivity(localBundlePath: String) {
823aa3c92eaSWill Schurman AsyncCondition.wait(
824aa3c92eaSWill Schurman KernelConstants.LOAD_BUNDLE_FOR_EXPERIENCE_ACTIVITY_KEY,
825aa3c92eaSWill Schurman object : AsyncConditionListener {
826aa3c92eaSWill Schurman override fun isReady(): Boolean {
827aa3c92eaSWill Schurman return optimisticActivity != null && optimisticTaskId != null
828aa3c92eaSWill Schurman }
829aa3c92eaSWill Schurman
830aa3c92eaSWill Schurman override fun execute() {
831aa3c92eaSWill Schurman optimisticActivity!!.setBundle(localBundlePath)
832aa3c92eaSWill Schurman optimisticActivity = null
833aa3c92eaSWill Schurman optimisticTaskId = null
834aa3c92eaSWill Schurman }
835aa3c92eaSWill Schurman }
836aa3c92eaSWill Schurman )
837aa3c92eaSWill Schurman }
838aa3c92eaSWill Schurman
839aa3c92eaSWill Schurman /*
840aa3c92eaSWill Schurman *
841aa3c92eaSWill Schurman * Tasks
842aa3c92eaSWill Schurman *
843aa3c92eaSWill Schurman */
844aa3c92eaSWill Schurman val tasks: List<AppTask>
845aa3c92eaSWill Schurman get() {
846aa3c92eaSWill Schurman val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
847aa3c92eaSWill Schurman return manager.appTasks
848aa3c92eaSWill Schurman }
849aa3c92eaSWill Schurman
850aa3c92eaSWill Schurman // Get list of tasks in our format.
851aa3c92eaSWill Schurman val experienceActivityTasks: List<AppTask>
852aa3c92eaSWill Schurman get() = tasks
853aa3c92eaSWill Schurman
854aa3c92eaSWill Schurman // Sometimes LauncherActivity.finish() doesn't close the activity and task. Not sure why exactly.
855aa3c92eaSWill Schurman // Thought it was related to launchMode="singleTask" but other launchModes seem to have the same problem.
856aa3c92eaSWill Schurman // This can be reproduced by creating a shortcut, exiting app, clicking on shortcut, refreshing, pressing
857aa3c92eaSWill Schurman // home, clicking on shortcut, click recent apps button. There will be a blank LauncherActivity behind
858aa3c92eaSWill Schurman // the ExperienceActivity. killOrphanedLauncherActivities solves this but would be nice to figure out
859aa3c92eaSWill Schurman // the root cause.
860aa3c92eaSWill Schurman private fun killOrphanedLauncherActivities() {
861aa3c92eaSWill Schurman try {
862aa3c92eaSWill Schurman // Crash with NoSuchFieldException instead of hard crashing at taskInfo.numActivities
863aa3c92eaSWill Schurman RecentTaskInfo::class.java.getDeclaredField("numActivities")
864aa3c92eaSWill Schurman for (task: AppTask in tasks) {
865aa3c92eaSWill Schurman val taskInfo = task.taskInfo
866aa3c92eaSWill Schurman if (taskInfo.numActivities == 0 && (taskInfo.baseIntent.action == Intent.ACTION_MAIN)) {
867aa3c92eaSWill Schurman task.finishAndRemoveTask()
868aa3c92eaSWill Schurman return
869aa3c92eaSWill Schurman }
870aa3c92eaSWill Schurman if (taskInfo.numActivities == 1 && (taskInfo.topActivity!!.className == LauncherActivity::class.java.name)) {
871aa3c92eaSWill Schurman task.finishAndRemoveTask()
872aa3c92eaSWill Schurman return
873aa3c92eaSWill Schurman }
874aa3c92eaSWill Schurman }
875aa3c92eaSWill Schurman } catch (e: NoSuchFieldException) {
876aa3c92eaSWill Schurman // Don't EXL here because this isn't actually a problem
877aa3c92eaSWill Schurman Log.e(TAG, e.toString())
878aa3c92eaSWill Schurman } catch (e: Throwable) {
879aa3c92eaSWill Schurman EXL.e(TAG, e)
880aa3c92eaSWill Schurman }
881aa3c92eaSWill Schurman }
882aa3c92eaSWill Schurman
883aa3c92eaSWill Schurman fun moveTaskToFront(taskId: Int) {
884aa3c92eaSWill Schurman tasks.find { it.taskInfo.id == taskId }?.also { task ->
885aa3c92eaSWill Schurman // If we have the task in memory, tell the ExperienceActivity to check for new options.
886aa3c92eaSWill Schurman // Otherwise options will be added in initialProps when the Experience starts.
887aa3c92eaSWill Schurman val exponentTask = experienceActivityTaskForTaskId(taskId)
888aa3c92eaSWill Schurman if (exponentTask != null) {
889aa3c92eaSWill Schurman val experienceActivity = exponentTask.experienceActivity!!.get()
890aa3c92eaSWill Schurman experienceActivity?.shouldCheckOptions()
891aa3c92eaSWill Schurman }
892aa3c92eaSWill Schurman task.moveToFront()
893aa3c92eaSWill Schurman }
894aa3c92eaSWill Schurman }
895aa3c92eaSWill Schurman
896aa3c92eaSWill Schurman fun killActivityStack(activity: Activity) {
897aa3c92eaSWill Schurman val exponentTask = experienceActivityTaskForTaskId(activity.taskId)
898aa3c92eaSWill Schurman if (exponentTask != null) {
899aa3c92eaSWill Schurman removeExperienceActivityTask(exponentTask.manifestUrl)
900aa3c92eaSWill Schurman }
901aa3c92eaSWill Schurman
902aa3c92eaSWill Schurman // Kill the current task.
903aa3c92eaSWill Schurman val manager = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
904aa3c92eaSWill Schurman manager.appTasks.find { it.taskInfo.id == activity.taskId }?.also { task -> task.finishAndRemoveTask() }
905aa3c92eaSWill Schurman }
906aa3c92eaSWill Schurman
907aa3c92eaSWill Schurman override fun reloadVisibleExperience(manifestUrl: String, forceCache: Boolean): Boolean {
908aa3c92eaSWill Schurman var activity: ExperienceActivity? = null
909aa3c92eaSWill Schurman for (experienceActivityTask: ExperienceActivityTask in manifestUrlToExperienceActivityTask.values) {
910aa3c92eaSWill Schurman if (manifestUrl == experienceActivityTask.manifestUrl) {
911aa3c92eaSWill Schurman val weakActivity =
912aa3c92eaSWill Schurman if (experienceActivityTask.experienceActivity == null) {
913aa3c92eaSWill Schurman null
914aa3c92eaSWill Schurman } else {
915aa3c92eaSWill Schurman experienceActivityTask.experienceActivity!!.get()
916aa3c92eaSWill Schurman }
917aa3c92eaSWill Schurman activity = weakActivity
918aa3c92eaSWill Schurman if (weakActivity == null) {
919aa3c92eaSWill Schurman // No activity, just force a reload
920aa3c92eaSWill Schurman break
921aa3c92eaSWill Schurman }
9227113164bSWill Schurman Exponent.instance.runOnUiThread { weakActivity.startLoading() }
923aa3c92eaSWill Schurman break
924aa3c92eaSWill Schurman }
925aa3c92eaSWill Schurman }
926aa3c92eaSWill Schurman activity?.let { killActivityStack(it) }
927aa3c92eaSWill Schurman openManifestUrl(manifestUrl, null, true, forceCache)
928aa3c92eaSWill Schurman return true
929aa3c92eaSWill Schurman }
930aa3c92eaSWill Schurman
931aa3c92eaSWill Schurman override fun handleError(errorMessage: String) {
932aa3c92eaSWill Schurman handleReactNativeError(developerErrorMessage(errorMessage), null, -1, true)
933aa3c92eaSWill Schurman }
934aa3c92eaSWill Schurman
935aa3c92eaSWill Schurman override fun handleError(exception: Exception) {
936*05bcf8e7SWojciech Dróżdż handleReactNativeError(ExceptionUtils.exceptionToErrorMessage(exception), null, -1, true, ExceptionUtils.exceptionToErrorHeader(exception))
937aa3c92eaSWill Schurman }
938aa3c92eaSWill Schurman
939aa3c92eaSWill Schurman // TODO: probably need to call this from other places.
940aa3c92eaSWill Schurman fun setHasError() {
941aa3c92eaSWill Schurman hasError = true
942aa3c92eaSWill Schurman }
943aa3c92eaSWill Schurman
944aa3c92eaSWill Schurman companion object {
945aa3c92eaSWill Schurman private val TAG = Kernel::class.java.simpleName
946aa3c92eaSWill Schurman private lateinit var instance: Kernel
947aa3c92eaSWill Schurman
948aa3c92eaSWill Schurman // Activities/Tasks
949aa3c92eaSWill Schurman private val manifestUrlToExperienceActivityTask = mutableMapOf<String, ExperienceActivityTask>()
950aa3c92eaSWill Schurman private val manifestUrlToOptions = mutableMapOf<String?, ExperienceOptions>()
951aa3c92eaSWill Schurman private val manifestUrlToAppLoader = mutableMapOf<String?, ExpoUpdatesAppLoader>()
952aa3c92eaSWill Schurman
953aa3c92eaSWill Schurman private fun addIntentDocumentFlags(intent: Intent) = intent.apply {
954aa3c92eaSWill Schurman addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
955aa3c92eaSWill Schurman addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
956aa3c92eaSWill Schurman addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
957aa3c92eaSWill Schurman }
958aa3c92eaSWill Schurman
959aa3c92eaSWill Schurman @JvmStatic
960aa3c92eaSWill Schurman @DoNotStrip
961aa3c92eaSWill Schurman fun reloadVisibleExperience(activityId: Int) {
962aa3c92eaSWill Schurman val manifestUrl = getManifestUrlForActivityId(activityId)
963aa3c92eaSWill Schurman if (manifestUrl != null) {
964aa3c92eaSWill Schurman instance.reloadVisibleExperience(manifestUrl, false)
965aa3c92eaSWill Schurman }
966aa3c92eaSWill Schurman }
967aa3c92eaSWill Schurman
968aa3c92eaSWill Schurman // Called from DevServerHelper via ReactNativeStaticHelpers
969aa3c92eaSWill Schurman @JvmStatic
970aa3c92eaSWill Schurman @DoNotStrip
971aa3c92eaSWill Schurman fun getManifestUrlForActivityId(activityId: Int): String? {
972aa3c92eaSWill Schurman return manifestUrlToExperienceActivityTask.values.find { it.activityId == activityId }?.manifestUrl
973aa3c92eaSWill Schurman }
974aa3c92eaSWill Schurman
975aa3c92eaSWill Schurman // Called from DevServerHelper via ReactNativeStaticHelpers
976aa3c92eaSWill Schurman @JvmStatic
977aa3c92eaSWill Schurman @DoNotStrip
978aa3c92eaSWill Schurman fun getBundleUrlForActivityId(
979aa3c92eaSWill Schurman activityId: Int,
980aa3c92eaSWill Schurman host: String,
981aa3c92eaSWill Schurman mainModuleId: String?,
982aa3c92eaSWill Schurman bundleTypeId: String?,
983aa3c92eaSWill Schurman devMode: Boolean,
984aa3c92eaSWill Schurman jsMinify: Boolean
985aa3c92eaSWill Schurman ): String? {
986aa3c92eaSWill Schurman // NOTE: This current implementation doesn't look at the bundleTypeId (see RN's private
987aa3c92eaSWill Schurman // BundleType enum for the possible values) but may need to
988aa3c92eaSWill Schurman if (activityId == -1) {
989aa3c92eaSWill Schurman // This is the kernel
990aa3c92eaSWill Schurman return instance.bundleUrl
991aa3c92eaSWill Schurman }
992aa3c92eaSWill Schurman if (InternalHeadlessAppLoader.hasBundleUrlForActivityId(activityId)) {
993aa3c92eaSWill Schurman return InternalHeadlessAppLoader.getBundleUrlForActivityId(activityId)
994aa3c92eaSWill Schurman }
995aa3c92eaSWill Schurman return manifestUrlToExperienceActivityTask.values.find { it.activityId == activityId }?.bundleUrl
996aa3c92eaSWill Schurman }
997aa3c92eaSWill Schurman
998aa3c92eaSWill Schurman // <= SDK 25
999aa3c92eaSWill Schurman @DoNotStrip
1000aa3c92eaSWill Schurman fun getBundleUrlForActivityId(
1001aa3c92eaSWill Schurman activityId: Int,
1002aa3c92eaSWill Schurman host: String,
1003aa3c92eaSWill Schurman jsModulePath: String?,
1004aa3c92eaSWill Schurman devMode: Boolean,
1005aa3c92eaSWill Schurman jsMinify: Boolean
1006aa3c92eaSWill Schurman ): String? {
1007aa3c92eaSWill Schurman if (activityId == -1) {
1008aa3c92eaSWill Schurman // This is the kernel
1009aa3c92eaSWill Schurman return instance.bundleUrl
1010aa3c92eaSWill Schurman }
1011aa3c92eaSWill Schurman return manifestUrlToExperienceActivityTask.values.find { it.activityId == activityId }?.bundleUrl
1012aa3c92eaSWill Schurman }
1013aa3c92eaSWill Schurman
1014aa3c92eaSWill Schurman // <= SDK 21
1015aa3c92eaSWill Schurman @DoNotStrip
1016aa3c92eaSWill Schurman fun getBundleUrlForActivityId(
1017aa3c92eaSWill Schurman activityId: Int,
1018aa3c92eaSWill Schurman host: String,
1019aa3c92eaSWill Schurman jsModulePath: String?,
1020aa3c92eaSWill Schurman devMode: Boolean,
1021aa3c92eaSWill Schurman hmr: Boolean,
1022aa3c92eaSWill Schurman jsMinify: Boolean
1023aa3c92eaSWill Schurman ): String? {
1024aa3c92eaSWill Schurman if (activityId == -1) {
1025aa3c92eaSWill Schurman // This is the kernel
1026aa3c92eaSWill Schurman return instance.bundleUrl
1027aa3c92eaSWill Schurman }
1028aa3c92eaSWill Schurman return manifestUrlToExperienceActivityTask.values.find { it.activityId == activityId }?.let { task ->
1029aa3c92eaSWill Schurman var url = task.bundleUrl ?: return null
1030aa3c92eaSWill Schurman if (hmr) {
1031aa3c92eaSWill Schurman url = if (url.contains("hot=false")) {
1032aa3c92eaSWill Schurman url.replace("hot=false", "hot=true")
1033aa3c92eaSWill Schurman } else {
1034aa3c92eaSWill Schurman "$url&hot=true"
1035aa3c92eaSWill Schurman }
1036aa3c92eaSWill Schurman }
1037aa3c92eaSWill Schurman return url
1038aa3c92eaSWill Schurman }
1039aa3c92eaSWill Schurman }
1040aa3c92eaSWill Schurman
1041aa3c92eaSWill Schurman /*
1042aa3c92eaSWill Schurman *
1043aa3c92eaSWill Schurman * Error handling
1044aa3c92eaSWill Schurman *
1045aa3c92eaSWill Schurman */
1046aa3c92eaSWill Schurman // Called using reflection from ReactAndroid.
1047aa3c92eaSWill Schurman @DoNotStrip
1048aa3c92eaSWill Schurman fun handleReactNativeError(
1049aa3c92eaSWill Schurman errorMessage: String?,
1050aa3c92eaSWill Schurman detailsUnversioned: Any?,
1051aa3c92eaSWill Schurman exceptionId: Int?,
1052aa3c92eaSWill Schurman isFatal: Boolean
1053aa3c92eaSWill Schurman ) {
1054aa3c92eaSWill Schurman handleReactNativeError(
1055aa3c92eaSWill Schurman developerErrorMessage(errorMessage),
1056aa3c92eaSWill Schurman detailsUnversioned,
1057aa3c92eaSWill Schurman exceptionId,
1058aa3c92eaSWill Schurman isFatal
1059aa3c92eaSWill Schurman )
1060aa3c92eaSWill Schurman }
1061aa3c92eaSWill Schurman
1062aa3c92eaSWill Schurman // Called using reflection from ReactAndroid.
1063aa3c92eaSWill Schurman @DoNotStrip
1064aa3c92eaSWill Schurman fun handleReactNativeError(
1065aa3c92eaSWill Schurman throwable: Throwable?,
1066aa3c92eaSWill Schurman errorMessage: String?,
1067aa3c92eaSWill Schurman detailsUnversioned: Any?,
1068aa3c92eaSWill Schurman exceptionId: Int?,
1069aa3c92eaSWill Schurman isFatal: Boolean
1070aa3c92eaSWill Schurman ) {
1071aa3c92eaSWill Schurman handleReactNativeError(
1072aa3c92eaSWill Schurman developerErrorMessage(errorMessage),
1073aa3c92eaSWill Schurman detailsUnversioned,
1074aa3c92eaSWill Schurman exceptionId,
1075aa3c92eaSWill Schurman isFatal
1076aa3c92eaSWill Schurman )
1077aa3c92eaSWill Schurman }
1078aa3c92eaSWill Schurman
1079aa3c92eaSWill Schurman private fun handleReactNativeError(
1080aa3c92eaSWill Schurman errorMessage: ExponentErrorMessage,
1081aa3c92eaSWill Schurman detailsUnversioned: Any?,
1082aa3c92eaSWill Schurman exceptionId: Int?,
1083*05bcf8e7SWojciech Dróżdż isFatal: Boolean,
1084*05bcf8e7SWojciech Dróżdż errorHeader: String? = null,
1085aa3c92eaSWill Schurman ) {
1086aa3c92eaSWill Schurman val stackList = ArrayList<Bundle>()
1087aa3c92eaSWill Schurman if (detailsUnversioned != null) {
1088aa3c92eaSWill Schurman val details = RNObject.wrap(detailsUnversioned)
1089aa3c92eaSWill Schurman val arguments = RNObject("com.facebook.react.bridge.Arguments")
1090aa3c92eaSWill Schurman arguments.loadVersion(details.version())
1091aa3c92eaSWill Schurman for (i in 0 until details.call("size") as Int) {
1092aa3c92eaSWill Schurman try {
1093aa3c92eaSWill Schurman val bundle = arguments.callStatic("toBundle", details.call("getMap", i)) as Bundle
1094aa3c92eaSWill Schurman stackList.add(bundle)
1095aa3c92eaSWill Schurman } catch (e: Exception) {
1096aa3c92eaSWill Schurman e.printStackTrace()
1097aa3c92eaSWill Schurman }
1098aa3c92eaSWill Schurman }
1099aa3c92eaSWill Schurman } else if (BuildConfig.DEBUG) {
1100aa3c92eaSWill Schurman val stackTraceElements = Thread.currentThread().stackTrace
1101aa3c92eaSWill Schurman // stackTraceElements starts with a bunch of stuff we don't care about.
1102aa3c92eaSWill Schurman for (i in 2 until stackTraceElements.size) {
1103aa3c92eaSWill Schurman val element = stackTraceElements[i]
1104aa3c92eaSWill Schurman if ((
1105aa3c92eaSWill Schurman (element.fileName != null) && element.fileName.startsWith(Kernel::class.java.simpleName) &&
1106aa3c92eaSWill Schurman ((element.methodName == "handleReactNativeError") || (element.methodName == "handleError"))
1107aa3c92eaSWill Schurman )
1108aa3c92eaSWill Schurman ) {
1109aa3c92eaSWill Schurman // Ignore these base error handling methods.
1110aa3c92eaSWill Schurman continue
1111aa3c92eaSWill Schurman }
1112aa3c92eaSWill Schurman val bundle = Bundle().apply {
1113aa3c92eaSWill Schurman putInt("column", 0)
1114aa3c92eaSWill Schurman putInt("lineNumber", element.lineNumber)
1115aa3c92eaSWill Schurman putString("methodName", element.methodName)
1116aa3c92eaSWill Schurman putString("file", element.fileName)
1117aa3c92eaSWill Schurman }
1118aa3c92eaSWill Schurman stackList.add(bundle)
1119aa3c92eaSWill Schurman }
1120aa3c92eaSWill Schurman }
1121aa3c92eaSWill Schurman val stack = stackList.toTypedArray()
1122aa3c92eaSWill Schurman BaseExperienceActivity.addError(
1123aa3c92eaSWill Schurman ExponentError(
1124*05bcf8e7SWojciech Dróżdż errorMessage, errorHeader, stack,
1125aa3c92eaSWill Schurman getExceptionId(exceptionId), isFatal
1126aa3c92eaSWill Schurman )
1127aa3c92eaSWill Schurman )
1128aa3c92eaSWill Schurman }
1129aa3c92eaSWill Schurman
1130aa3c92eaSWill Schurman private fun getExceptionId(originalId: Int?): Int {
1131aa3c92eaSWill Schurman return if (originalId == null || originalId == -1) {
1132aa3c92eaSWill Schurman (-(Math.random() * Int.MAX_VALUE)).toInt()
1133aa3c92eaSWill Schurman } else originalId
1134aa3c92eaSWill Schurman }
1135aa3c92eaSWill Schurman }
1136aa3c92eaSWill Schurman
1137aa3c92eaSWill Schurman init {
1138094c4dddSWill Schurman NativeModuleDepsProvider.instance.inject(Kernel::class.java, this)
1139aa3c92eaSWill Schurman instance = this
1140aa3c92eaSWill Schurman updateKernelRNOkHttp()
1141aa3c92eaSWill Schurman }
1142aa3c92eaSWill Schurman }
1143