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.device.DevicePackage
20 import expo.modules.facedetector.FaceDetectorPackage
21 import expo.modules.filesystem.FileSystemPackage
22 import expo.modules.font.FontLoaderPackage
23 import expo.modules.haptics.HapticsPackage
24 import expo.modules.keepawake.KeepAwakePackage
25 import expo.modules.medialibrary.MediaLibraryPackage
26 import expo.modules.notifications.NotificationsPackage
27 import expo.modules.permissions.PermissionsPackage
28 import expo.modules.splashscreen.SplashScreenImageResizeMode
29 import expo.modules.splashscreen.SplashScreenPackage
30 import expo.modules.splashscreen.singletons.SplashScreen
31 import expo.modules.taskManager.TaskManagerPackage
32 import host.exp.exponent.Constants
33 import host.exp.exponent.ExponentManifest
34 import host.exp.exponent.RNObject
35 import host.exp.exponent.analytics.Analytics
36 import host.exp.exponent.di.NativeModuleDepsProvider
37 import host.exp.exponent.kernel.ExperienceKey
38 import host.exp.exponent.kernel.Kernel.KernelStartedRunningEvent
39 import host.exp.exponent.utils.ExperienceActivityUtils
40 import host.exp.exponent.utils.ExperienceRTLManager
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     ExperienceRTLManager.setSupportsRTL(this, false)
73 
74     SplashScreen.show(this, SplashScreenImageResizeMode.NATIVE, ReactRootView::class.java, true)
75 
76     tryInstallLeakCanary(true)
77   }
78 
79   override fun shouldCreateLoadingView(): Boolean {
80     // Home app shouldn't show LoadingView as it indicates state when the app's manifest is being
81     // downloaded and Splash info is not yet available and this is not the case for Home app
82     // (Splash info is known from the start).
83     return false
84   }
85 
86   override fun onResume() {
87     super.onResume()
88     SoLoader.init(this, false)
89     Analytics.logEvent(Analytics.AnalyticsEvent.HOME_APPEARED)
90   }
91   //endregion Activity Lifecycle
92   /**
93    * This method has been split out from onDestroy lifecycle method to [ReactNativeActivity.destroyReactInstanceManager]
94    * and overridden here as we want to prevent destroying react instance manager when HomeActivity gets destroyed.
95    * It needs to continue to live since it is needed for DevMenu to work as expected (it relies on ExponentKernelModule from that react context).
96    */
97   override fun destroyReactInstanceManager() {}
98 
99   private fun tryInstallLeakCanary(shouldAskForPermissions: Boolean) {
100     if (BuildConfig.DEBUG && Constants.ENABLE_LEAK_CANARY) {
101       // Leak canary needs WRITE_EXTERNAL_STORAGE permission
102       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
103         if (shouldAskForPermissions && ContextCompat.checkSelfPermission(
104             this,
105             Manifest.permission.WRITE_EXTERNAL_STORAGE
106           ) != PackageManager.PERMISSION_GRANTED
107         ) {
108           requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1248919246)
109         } else {
110           LeakCanary.install(application)
111         }
112       } else {
113         LeakCanary.install(application)
114       }
115     }
116   }
117 
118   override fun onRequestPermissionsResult(
119     requestCode: Int,
120     permissions: Array<String>,
121     grantResults: IntArray
122   ) {
123     super.onRequestPermissionsResult(requestCode, permissions, grantResults)
124     tryInstallLeakCanary(false)
125   }
126 
127   fun onEventMainThread(event: KernelStartedRunningEvent?) {
128     reactInstanceManager.assign(kernel.reactInstanceManager)
129     reactRootView.assign(kernel.reactRootView)
130     reactInstanceManager.onHostResume(this, this)
131     setReactRootView((reactRootView.get() as View))
132     finishLoading()
133 
134     if (Constants.DEBUG_COLD_START_METHOD_TRACING) {
135       Debug.stopMethodTracing()
136     }
137   }
138 
139   override fun onError(intent: Intent) {
140     intent.putExtra(ErrorActivity.IS_HOME_KEY, true)
141     kernel.setHasError()
142   }
143 
144   companion object {
145     fun homeExpoPackages(): List<Package> {
146       return listOf(
147         ConstantsPackage(),
148         PermissionsPackage(),
149         FileSystemPackage(),
150         FontLoaderPackage(),
151         BarCodeScannerPackage(),
152         KeepAwakePackage(),
153         FaceDetectorPackage(),
154         MediaLibraryPackage(),
155         NotificationsPackage(), // home doesn't use notifications, but we want the singleton modules created
156         TaskManagerPackage(), // load expo-task-manager to restore tasks once the client is opened
157         DevicePackage(),
158         SplashScreenPackage(),
159         HapticsPackage()
160       )
161     }
162   }
163 }
164