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