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.constants.ConstantsPackage
18 import expo.modules.core.interfaces.Package
19 import expo.modules.facedetector.FaceDetectorPackage
20 import expo.modules.filesystem.FileSystemPackage
21 import expo.modules.font.FontLoaderPackage
22 import expo.modules.keepawake.KeepAwakePackage
23 import expo.modules.medialibrary.MediaLibraryPackage
24 import expo.modules.notifications.NotificationsPackage
25 import expo.modules.permissions.PermissionsPackage
26 import expo.modules.splashscreen.SplashScreenImageResizeMode
27 import expo.modules.splashscreen.SplashScreenPackage
28 import expo.modules.splashscreen.singletons.SplashScreen
29 import expo.modules.taskManager.TaskManagerPackage
30 import host.exp.exponent.Constants
31 import host.exp.exponent.ExponentManifest
32 import host.exp.exponent.RNObject
33 import host.exp.exponent.analytics.Analytics
34 import host.exp.exponent.di.NativeModuleDepsProvider
35 import host.exp.exponent.kernel.ExperienceKey
36 import host.exp.exponent.kernel.Kernel.KernelStartedRunningEvent
37 import host.exp.exponent.utils.ExperienceActivityUtils
38 import host.exp.exponent.utils.ExperienceRTLManager
39 import host.exp.expoview.BuildConfig
40 import org.json.JSONException
41 import javax.inject.Inject
42 
43 open class HomeActivity : BaseExperienceActivity() {
44   @Inject
45   lateinit var exponentManifest: ExponentManifest
46 
47   //region Activity Lifecycle
48   override fun onCreate(savedInstanceState: Bundle?) {
49     super.onCreate(savedInstanceState)
50     NativeModuleDepsProvider.instance.inject(HomeActivity::class.java, this)
51 
52     sdkVersion = RNObject.UNVERSIONED
53     manifest = exponentManifest.getKernelManifest()
54     experienceKey = try {
55       ExperienceKey.fromManifest(manifest!!)
56     } catch (e: JSONException) {
57       ExperienceKey("")
58     }
59 
60     // @sjchmiela, @lukmccall: We are consciously not overriding UI mode in Home, because it has no effect.
61     // `ExpoAppearanceModule` with which `ExperienceActivityUtils#overrideUiMode` is compatible
62     // is disabled in Home as of end of 2020, to fix some issues with dev menu, see:
63     // https://github.com/expo/expo/blob/eb9bd274472e646a730fd535a4bcf360039cbd49/android/expoview/src/main/java/versioned/host/exp/exponent/ExponentPackage.java#L200-L207
64     // ExperienceActivityUtils.overrideUiMode(mExponentManifest.getKernelManifest(), this);
65     ExperienceActivityUtils.configureStatusBar(exponentManifest.getKernelManifest(), this)
66 
67     EventBus.getDefault().registerSticky(this)
68     kernel.startJSKernel(this)
69 
70     ExperienceRTLManager.setSupportsRTL(this, false)
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         FaceDetectorPackage(),
152         MediaLibraryPackage(),
153         NotificationsPackage(), // home doesn't use notifications, but we want the singleton modules created
154         TaskManagerPackage(), // load expo-task-manager to restore tasks once the client is opened
155         SplashScreenPackage()
156       )
157     }
158   }
159 }
160