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