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