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