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