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.expoview.BuildConfig
41 import org.json.JSONException
42 import javax.inject.Inject
43 
44 open class HomeActivity : BaseExperienceActivity() {
45   @Inject
46   lateinit var exponentManifest: ExponentManifest
47 
48   //region Activity Lifecycle
49   override fun onCreate(savedInstanceState: Bundle?) {
50     super.onCreate(savedInstanceState)
51     NativeModuleDepsProvider.instance.inject(HomeActivity::class.java, this)
52 
53     sdkVersion = RNObject.UNVERSIONED
54     manifest = exponentManifest.getKernelManifest()
55     experienceKey = try {
56       ExperienceKey.fromManifest(manifest!!)
57     } catch (e: JSONException) {
58       ExperienceKey("")
59     }
60 
61     // @sjchmiela, @lukmccall: We are consciously not overriding UI mode in Home, because it has no effect.
62     // `ExpoAppearanceModule` with which `ExperienceActivityUtils#overrideUiMode` is compatible
63     // is disabled in Home as of end of 2020, to fix some issues with dev menu, see:
64     // https://github.com/expo/expo/blob/eb9bd274472e646a730fd535a4bcf360039cbd49/android/expoview/src/main/java/versioned/host/exp/exponent/ExponentPackage.java#L200-L207
65     // ExperienceActivityUtils.overrideUiMode(mExponentManifest.getKernelManifest(), this);
66     ExperienceActivityUtils.configureStatusBar(exponentManifest.getKernelManifest(), this)
67 
68     EventBus.getDefault().registerSticky(this)
69     kernel.startJSKernel(this)
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         MediaLibraryPackage(),
152         NotificationsPackage(), // home doesn't use notifications, but we want the singleton modules created
153         TaskManagerPackage(), // load expo-task-manager to restore tasks once the client is opened
154         DevicePackage(),
155         SplashScreenPackage(),
156         HapticsPackage()
157       )
158     }
159   }
160 }
161