1 // Copyright 2015-present 650 Industries. All rights reserved.
2 package host.exp.exponent.experience
3 
4 import android.Manifest
5 import android.content.Intent
6 import android.content.pm.PackageManager
7 import android.os.Bundle
8 import android.os.Debug
9 import android.view.View
10 import androidx.core.content.ContextCompat
11 import com.facebook.react.ReactRootView
12 import com.facebook.soloader.SoLoader
13 import com.squareup.leakcanary.LeakCanary
14 import de.greenrobot.event.EventBus
15 import expo.modules.barcodescanner.BarCodeScannerModule
16 import expo.modules.barcodescanner.BarCodeScannerPackage
17 import expo.modules.blur.BlurModule
18 import expo.modules.camera.CameraViewModule
19 import expo.modules.clipboard.ClipboardModule
20 import expo.modules.constants.ConstantsModule
21 import expo.modules.constants.ConstantsPackage
22 import expo.modules.core.interfaces.Package
23 import expo.modules.device.DeviceModule
24 import expo.modules.easclient.EASClientModule
25 import expo.modules.facedetector.FaceDetectorPackage
26 import expo.modules.filesystem.FileSystemModule
27 import expo.modules.filesystem.FileSystemPackage
28 import expo.modules.haptics.HapticsModule
29 import expo.modules.keepawake.KeepAwakeModule
30 import expo.modules.keepawake.KeepAwakePackage
31 import expo.modules.kotlin.ModulesProvider
32 import expo.modules.kotlin.modules.Module
33 import expo.modules.lineargradient.LinearGradientModule
34 import expo.modules.notifications.NotificationsPackage
35 import expo.modules.splashscreen.SplashScreenImageResizeMode
36 import expo.modules.splashscreen.SplashScreenModule
37 import expo.modules.splashscreen.SplashScreenPackage
38 import expo.modules.splashscreen.singletons.SplashScreen
39 import expo.modules.taskManager.TaskManagerPackage
40 import expo.modules.webbrowser.WebBrowserModule
41 import host.exp.exponent.Constants
42 import host.exp.exponent.ExponentManifest
43 import host.exp.exponent.RNObject
44 import host.exp.exponent.di.NativeModuleDepsProvider
45 import host.exp.exponent.kernel.ExperienceKey
46 import host.exp.exponent.kernel.Kernel.KernelStartedRunningEvent
47 import host.exp.exponent.utils.ExperienceActivityUtils
48 import host.exp.exponent.utils.ExperienceRTLManager
49 import host.exp.expoview.BuildConfig
50 import org.json.JSONException
51 import javax.inject.Inject
52 
53 open class HomeActivity : BaseExperienceActivity() {
54   @Inject
55   lateinit var exponentManifest: ExponentManifest
56 
57   //region Activity Lifecycle
onCreatenull58   override fun onCreate(savedInstanceState: Bundle?) {
59     super.onCreate(savedInstanceState)
60     NativeModuleDepsProvider.instance.inject(HomeActivity::class.java, this)
61 
62     sdkVersion = RNObject.UNVERSIONED
63     manifest = exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest
64     experienceKey = try {
65       ExperienceKey.fromManifest(manifest!!)
66     } catch (e: JSONException) {
67       ExperienceKey("")
68     }
69 
70     // @sjchmiela, @lukmccall: We are consciously not overriding UI mode in Home, because it has no effect.
71     // `ExpoAppearanceModule` with which `ExperienceActivityUtils#overrideUiMode` is compatible
72     // is disabled in Home as of end of 2020, to fix some issues with dev menu, see:
73     // https://github.com/expo/expo/blob/eb9bd274472e646a730fd535a4bcf360039cbd49/android/expoview/src/main/java/versioned/host/exp/exponent/ExponentPackage.java#L200-L207
74     // ExperienceActivityUtils.overrideUiMode(mExponentManifest.getKernelManifest(), this);
75     ExperienceActivityUtils.configureStatusBar(exponentManifest.getKernelManifestAndAssetRequestHeaders().manifest, this)
76 
77     EventBus.getDefault().registerSticky(this)
78     kernel.startJSKernel(this)
79 
80     ExperienceRTLManager.setSupportsRTL(this, false)
81 
82     SplashScreen.show(this, SplashScreenImageResizeMode.NATIVE, ReactRootView::class.java, true)
83 
84     tryInstallLeakCanary(true)
85   }
86 
shouldCreateLoadingViewnull87   override fun shouldCreateLoadingView(): Boolean {
88     // Home app shouldn't show LoadingView as it indicates state when the app's manifest is being
89     // downloaded and Splash info is not yet available and this is not the case for Home app
90     // (Splash info is known from the start).
91     return false
92   }
93 
onResumenull94   override fun onResume() {
95     super.onResume()
96     SoLoader.init(this, false)
97   }
98   //endregion Activity Lifecycle
99   /**
100    * This method has been split out from onDestroy lifecycle method to [ReactNativeActivity.destroyReactInstanceManager]
101    * and overridden here as we want to prevent destroying react instance manager when HomeActivity gets destroyed.
102    * It needs to continue to live since it is needed for DevMenu to work as expected (it relies on ExponentKernelModule from that react context).
103    */
destroyReactInstanceManagernull104   override fun destroyReactInstanceManager() {}
105 
tryInstallLeakCanarynull106   private fun tryInstallLeakCanary(shouldAskForPermissions: Boolean) {
107     if (BuildConfig.DEBUG && Constants.ENABLE_LEAK_CANARY) {
108       // Leak canary needs WRITE_EXTERNAL_STORAGE permission
109       if (shouldAskForPermissions && ContextCompat.checkSelfPermission(
110           this,
111           Manifest.permission.WRITE_EXTERNAL_STORAGE
112         ) != PackageManager.PERMISSION_GRANTED
113       ) {
114         requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1248919246)
115       } else {
116         LeakCanary.install(application)
117       }
118     }
119   }
120 
onRequestPermissionsResultnull121   override fun onRequestPermissionsResult(
122     requestCode: Int,
123     permissions: Array<String>,
124     grantResults: IntArray
125   ) {
126     super.onRequestPermissionsResult(requestCode, permissions, grantResults)
127     tryInstallLeakCanary(false)
128   }
129 
onEventMainThreadnull130   fun onEventMainThread(event: KernelStartedRunningEvent?) {
131     reactInstanceManager.assign(kernel.reactInstanceManager)
132     reactRootView.assign(kernel.reactRootView)
133     reactInstanceManager.onHostResume(this, this)
134     setReactRootView((reactRootView.get() as View))
135     finishLoading()
136 
137     if (Constants.DEBUG_COLD_START_METHOD_TRACING) {
138       Debug.stopMethodTracing()
139     }
140   }
141 
onErrornull142   override fun onError(intent: Intent) {
143     intent.putExtra(ErrorActivity.IS_HOME_KEY, true)
144     kernel.setHasError()
145   }
146 
147   companion object : ModulesProvider {
homeExpoPackagesnull148     fun homeExpoPackages(): List<Package> {
149       return listOf(
150         ConstantsPackage(),
151         FileSystemPackage(),
152         BarCodeScannerPackage(),
153         KeepAwakePackage(),
154         FaceDetectorPackage(),
155         NotificationsPackage(), // home doesn't use notifications, but we want the singleton modules created
156         TaskManagerPackage(), // load expo-task-manager to restore tasks once the client is opened
157         SplashScreenPackage()
158       )
159     }
160 
getModulesListnull161     override fun getModulesList(): List<Class<out Module>> {
162       return listOf(
163         BarCodeScannerModule::class.java,
164         BlurModule::class.java,
165         CameraViewModule::class.java,
166         ClipboardModule::class.java,
167         ConstantsModule::class.java,
168         DeviceModule::class.java,
169         EASClientModule::class.java,
170         FileSystemModule::class.java,
171         HapticsModule::class.java,
172         KeepAwakeModule::class.java,
173         LinearGradientModule::class.java,
174         SplashScreenModule::class.java,
175         WebBrowserModule::class.java,
176       )
177     }
178   }
179 }
180