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