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