1 package host.exp.exponent.notifications
2 
3 import android.app.IntentService
4 import android.content.Intent
5 import android.util.Log
6 import com.facebook.soloader.SoLoader
7 import host.exp.exponent.analytics.EXL
8 import host.exp.exponent.di.NativeModuleDepsProvider
9 import host.exp.exponent.kernel.ExponentUrls
10 import host.exp.exponent.network.ExpoHttpCallback
11 import host.exp.exponent.network.ExpoResponse
12 import host.exp.exponent.network.ExponentNetwork
13 import host.exp.exponent.storage.ExponentSharedPreferences
14 import host.exp.exponent.utils.AsyncCondition
15 import okhttp3.MediaType.Companion.toMediaTypeOrNull
16 import okhttp3.RequestBody.Companion.toRequestBody
17 import org.json.JSONException
18 import org.json.JSONObject
19 import java.io.IOException
20 import javax.inject.Inject
21 
22 abstract class ExponentNotificationIntentService(name: String?) : IntentService(name) {
23   @Throws(IOException::class)
getTokennull24   abstract fun getToken(): String?
25 
26   abstract fun getSharedPrefsKey(): ExponentSharedPreferences.ExponentSharedPreferencesKey
27 
28   abstract fun getServerType(): String
29 
30   @Inject
31   lateinit var exponentSharedPreferences: ExponentSharedPreferences
32 
33   @Inject
34   lateinit var exponentNetwork: ExponentNetwork
35 
36   var isInitialized = false
37 
38   private fun initialize() {
39     if (isInitialized) {
40       return
41     }
42     try {
43       NativeModuleDepsProvider.instance.inject(ExponentNotificationIntentService::class.java, this)
44       isInitialized = true
45     } catch (e: Throwable) {
46     }
47   }
48 
onCreatenull49   override fun onCreate() {
50     super.onCreate()
51     initialize()
52   }
53 
54   /*
55    *  This function MUST set AsyncCondition.notify(DEVICE_PUSH_TOKEN_KEY);
56    *  eventually. Otherwise it's possible for us to get in a state where
57    *  the AsyncCondition listeners are never called (and the promise from
58    *  getExpoPushTokenAsync never resolves).
59    */
onHandleIntentnull60   override fun onHandleIntent(intent: Intent?) {
61     initialize()
62     try {
63       val token = getToken()
64       if (token == null) {
65         setTokenError("Device push token is null")
66         return
67       }
68 
69       val sharedPreferencesToken = exponentSharedPreferences.getString(getSharedPrefsKey())
70       if (sharedPreferencesToken == token) {
71         // Server already has this token, don't need to send it again.
72         AsyncCondition.notify(DEVICE_PUSH_TOKEN_KEY)
73         return
74       }
75 
76       // Needed for Arguments.createMap
77       SoLoader.init(this, false)
78 
79       val uuid = exponentSharedPreferences.getOrCreateUUID()
80       try {
81         val params = JSONObject().apply {
82           put("deviceToken", token)
83           put("deviceId", uuid)
84           put("appId", applicationContext.packageName)
85           put("type", getServerType())
86         }
87 
88         val body = params.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
89         val request = ExponentUrls.addExponentHeadersToUrl("https://exp.host/--/api/v2/push/updateDeviceToken")
90           .header("Content-Type", "application/json")
91           .post(body)
92           .build()
93 
94         exponentNetwork.client.call(
95           request,
96           object : ExpoHttpCallback {
97             override fun onFailure(e: IOException) {
98               setTokenError(e)
99             }
100 
101             @Throws(IOException::class)
102             override fun onResponse(response: ExpoResponse) {
103               if (!response.isSuccessful) {
104                 setTokenError("Failed to update the native device token with the Expo push notification service")
105                 return
106               }
107               exponentSharedPreferences.setString(getSharedPrefsKey(), token)
108               hasTokenError = false
109               AsyncCondition.notify(DEVICE_PUSH_TOKEN_KEY)
110             }
111           }
112         )
113         Log.i(TAG, "${getServerType()} Registration Token: $token")
114       } catch (e: JSONException) {
115         setTokenError(e)
116       }
117     } catch (e: SecurityException) {
118       setTokenError("Are you running in Genymotion? Follow this guide https://inthecheesefactory.com/blog/how-to-install-google-services-on-genymotion/en to install Google Play Services")
119     } catch (e: IOException) {
120       setTokenError(e)
121     }
122   }
123 
setTokenErrornull124   private fun setTokenError(e: Exception) {
125     hasTokenError = true
126     AsyncCondition.notify(DEVICE_PUSH_TOKEN_KEY)
127     EXL.e(TAG, e)
128   }
129 
setTokenErrornull130   private fun setTokenError(message: String) {
131     hasTokenError = true
132     AsyncCondition.notify(DEVICE_PUSH_TOKEN_KEY)
133     EXL.e(TAG, message)
134   }
135 
136   companion object {
137     private val TAG = ExponentNotificationIntentService::class.java.simpleName
138 
139     const val DEVICE_PUSH_TOKEN_KEY = "devicePushToken"
140 
141     var hasTokenError = false
142       private set
143   }
144 }
145