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