1 // Copyright 2015-present 650 Industries. All rights reserved.
2 package host.exp.exponent.di
3 
4 import android.app.Application
5 import android.content.Context
6 import android.os.Handler
7 import android.os.Looper
8 import com.facebook.proguard.annotations.DoNotStrip
9 import expo.modules.updates.db.DatabaseHolder
10 import expo.modules.updates.db.UpdatesDatabase
11 import host.exp.exponent.ExpoHandler
12 import host.exp.exponent.ExponentManifest
13 import host.exp.exponent.analytics.EXL
14 import host.exp.exponent.kernel.Crypto
15 import host.exp.exponent.kernel.services.ExpoKernelServiceRegistry
16 import host.exp.exponent.network.ExponentNetwork
17 import host.exp.exponent.storage.ExponentSharedPreferences
18 import java.lang.reflect.Field
19 import javax.inject.Inject
20 
21 class NativeModuleDepsProvider(application: Application) {
22   @Inject
23   @DoNotStrip
24   val mContext: Context = application
25 
26   @Inject
27   @DoNotStrip
28   val mApplicationContext: Application = application
29 
30   @Inject
31   @DoNotStrip
32   val mExpoHandler: ExpoHandler = ExpoHandler(Handler(Looper.getMainLooper()))
33 
34   @Inject
35   @DoNotStrip
36   val mExponentSharedPreferences: ExponentSharedPreferences = ExponentSharedPreferences(mContext)
37 
38   @Inject
39   @DoNotStrip
40   val mExponentNetwork: ExponentNetwork = ExponentNetwork(mContext, mExponentSharedPreferences)
41 
42   @Inject
43   @DoNotStrip
44   val mCrypto: Crypto = Crypto(mExponentNetwork)
45 
46   @Inject
47   @DoNotStrip
48   var mExponentManifest: ExponentManifest = ExponentManifest(mContext, mExponentNetwork, mCrypto, mExponentSharedPreferences)
49 
50   @Inject
51   @DoNotStrip
52   var mKernelServiceRegistry: ExpoKernelServiceRegistry = ExpoKernelServiceRegistry(mContext, mExponentSharedPreferences)
53 
54   @Inject
55   @DoNotStrip
56   val mUpdatesDatabaseHolder: DatabaseHolder = DatabaseHolder(UpdatesDatabase.getInstance(mContext))
57 
58   private val classToInstanceMap = mutableMapOf<Class<*>, Any>()
59 
60   fun add(clazz: Class<*>, instance: Any) {
61     classToInstanceMap[clazz] = instance
62   }
63 
64   fun inject(clazz: Class<*>, target: Any) {
65     for (field in clazz.declaredFields) {
66       injectFieldInTarget(target, field)
67     }
68   }
69 
70   private fun injectFieldInTarget(target: Any, field: Field) {
71     if (field.isAnnotationPresent(Inject::class.java)) {
72       val fieldClazz = field.type
73       if (!classToInstanceMap.containsKey(fieldClazz)) {
74         throw RuntimeException("NativeModuleDepsProvider could not find object for class $fieldClazz")
75       }
76       val instance = classToInstanceMap[fieldClazz]
77       try {
78         field.isAccessible = true
79         field[target] = instance
80       } catch (e: IllegalAccessException) {
81         EXL.e(TAG, e.toString())
82       }
83     }
84   }
85 
86   companion object {
87     private val TAG = NativeModuleDepsProvider::class.java.simpleName
88 
89     @JvmStatic lateinit var instance: NativeModuleDepsProvider
90       private set
91 
92     private var useTestInstance = false
93 
94     fun initialize(application: Application) {
95       if (!useTestInstance) {
96         instance = NativeModuleDepsProvider(application)
97       }
98     }
99 
100     // Only for testing!
101     fun setTestInstance(instance: NativeModuleDepsProvider) {
102       Companion.instance = instance
103       useTestInstance = true
104     }
105   }
106 
107   init {
108     for (field in NativeModuleDepsProvider::class.java.declaredFields) {
109       if (field.isAnnotationPresent(Inject::class.java)) {
110         try {
111           classToInstanceMap[field.type] = field[this]
112         } catch (e: IllegalAccessException) {
113           EXL.e(TAG, e.toString())
114         }
115       }
116     }
117   }
118 }
119