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