diff --git a/app/src/main/java/me/capcom/smsgateway/App.kt b/app/src/main/java/me/capcom/smsgateway/App.kt index 5d5c7bc..266821f 100644 --- a/app/src/main/java/me/capcom/smsgateway/App.kt +++ b/app/src/main/java/me/capcom/smsgateway/App.kt @@ -28,6 +28,7 @@ class App: Application() { notificationsModule, messagesModule, encryptionModule, + me.capcom.smsgateway.modules.gateway.gatewayModule, ) } @@ -36,12 +37,10 @@ class App: Application() { EventsReceiver.register(this) } - val gatewayModule by lazy { - GatewayModule( - get(), - get(), - ) + val gatewayModule: GatewayModule by lazy { + get() } + val localServerModule by lazy { LocalServerModule( get(), diff --git a/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt b/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt index c6ea2ff..91a441d 100644 --- a/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt +++ b/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayApi.kt @@ -13,13 +13,21 @@ import io.ktor.client.request.post import io.ktor.client.request.setBody import io.ktor.http.ContentType import io.ktor.http.HttpHeaders +import io.ktor.http.Url import io.ktor.http.contentType +import io.ktor.http.hostWithPort import io.ktor.serialization.gson.gson import me.capcom.smsgateway.BuildConfig import me.capcom.smsgateway.domain.MessageState import java.util.Date -class GatewayApi() { +class GatewayApi( + private val baseUrl: String, + private val privateToken: String? +) { + val hostname: String + get() = Url(baseUrl).hostWithPort + private val client = HttpClient(OkHttp) { install(UserAgent) { agent = "me.capcom.smsgateway/" + BuildConfig.VERSION_NAME @@ -30,15 +38,18 @@ class GatewayApi() { expectSuccess = true } - suspend fun deviceRegister(request: DeviceRegisterRequest): DeviceRegisterResponse { - return client.post("$BASE_URL/device") { + suspend fun deviceRegister( + request: DeviceRegisterRequest + ): DeviceRegisterResponse { + return client.post("$baseUrl/device") { + privateToken?.let { auth(it) } contentType(ContentType.Application.Json) setBody(request) }.body() } suspend fun devicePatch(token: String, request: DevicePatchRequest) { - client.patch("$BASE_URL/device") { + client.patch("$baseUrl/device") { auth(token) contentType(ContentType.Application.Json) setBody(request) @@ -46,20 +57,20 @@ class GatewayApi() { } suspend fun getMessages(token: String): List { - return client.get("$BASE_URL/message") { + return client.get("$baseUrl/message") { auth(token) }.body() } suspend fun patchMessages(token: String, request: List) { - client.patch("$BASE_URL/message") { + client.patch("$baseUrl/message") { auth(token) contentType(ContentType.Application.Json) setBody(request) } } - fun HttpRequestBuilder.auth(token: String) { + private fun HttpRequestBuilder.auth(token: String) { header(HttpHeaders.Authorization, "Bearer $token") } @@ -101,8 +112,4 @@ class GatewayApi() { val state: MessageState, val error: String?, ) - - companion object { - private const val BASE_URL = "https://sms.capcom.me/api/mobile/v1" - } } \ No newline at end of file diff --git a/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayModule.kt b/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayModule.kt index 792d919..7009db9 100644 --- a/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayModule.kt +++ b/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewayModule.kt @@ -2,6 +2,8 @@ package me.capcom.smsgateway.modules.gateway import android.content.Context import android.os.Build +import io.ktor.client.plugins.ClientRequestException +import io.ktor.http.HttpStatusCode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -23,7 +25,12 @@ class GatewayModule( private val messagesService: MessagesService, private val settings: GatewaySettings, ) { - private val api = GatewayApi() + private var _api: GatewayApi? = null + private val api + get() = _api ?: GatewayApi( + settings.privateUrl ?: GatewaySettings.PUBLIC_URL, + settings.privateToken + ).also { _api = it } val events = EventBus() var enabled: Boolean @@ -34,6 +41,11 @@ class GatewayModule( fun start(context: Context) { if (!enabled) return + this._api = GatewayApi( + settings.privateUrl ?: GatewaySettings.PUBLIC_URL, + settings.privateToken + ) + PushService.register(context) PullMessagesWorker.start(context) @@ -58,6 +70,7 @@ class GatewayModule( fun stop(context: Context) { scope.cancel() PullMessagesWorker.stop(context) + this._api = null } private suspend fun sendState( @@ -85,46 +98,53 @@ class GatewayModule( } } - suspend fun registerFcmToken(token: String) { + suspend fun registerFcmToken(pushToken: String) { if (!enabled) return val settings = settings.registrationInfo - settings?.token?.let { - withContext(Dispatchers.IO) { + val accessToken = settings?.token + + if (accessToken != null) { + // if there's an access token, try to update push token + try { api.devicePatch( - it, + accessToken, GatewayApi.DevicePatchRequest( settings.id, - token + pushToken ) ) - } - - events.emitEvent( - DeviceRegisteredEvent( - settings.login, - settings.password, - ) - ) - } - ?: kotlin.run { - val response = withContext(Dispatchers.IO) { - api.deviceRegister( - GatewayApi.DeviceRegisterRequest( - "${Build.MANUFACTURER}/${Build.PRODUCT}", - token - ) - ) - } - this.settings.registrationInfo = response - events.emitEvent( DeviceRegisteredEvent( - response.login, - response.password, + api.hostname, + settings.login, + settings.password, ) ) + return + } catch (e: ClientRequestException) { + // if token is invalid, try to register new one + if (e.response.status != HttpStatusCode.Unauthorized) { + throw e + } } + } + + val response = api.deviceRegister( + GatewayApi.DeviceRegisterRequest( + "${Build.MANUFACTURER}/${Build.PRODUCT}", + pushToken + ) + ) + this.settings.registrationInfo = response + + events.emitEvent( + DeviceRegisteredEvent( + api.hostname, + response.login, + response.password, + ) + ) } internal suspend fun getNewMessages() { diff --git a/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt b/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt index 0300b5f..ad1ee98 100644 --- a/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt +++ b/app/src/main/java/me/capcom/smsgateway/modules/gateway/GatewaySettings.kt @@ -6,7 +6,6 @@ import me.capcom.smsgateway.modules.settings.get class GatewaySettings( private val storage: KeyValueStorage, ) { - var enabled: Boolean get() = storage.get(ENABLED) ?: false set(value) = storage.set(ENABLED, value) @@ -15,8 +14,18 @@ class GatewaySettings( get() = storage.get(REGISTRATION_INFO) set(value) = storage.set(REGISTRATION_INFO, value) + val privateUrl: String? + get() = storage.get(CLOUD_URL) + val privateToken: String? + get() = storage.get(PRIVATE_TOKEN) + companion object { private const val REGISTRATION_INFO = "REGISTRATION_INFO" private const val ENABLED = "ENABLED" + + private const val CLOUD_URL = "cloud_url" + private const val PRIVATE_TOKEN = "private_token" + + const val PUBLIC_URL = "https://sms.capcom.me/api/mobile/v1" } } \ No newline at end of file diff --git a/app/src/main/java/me/capcom/smsgateway/modules/gateway/events/DeviceRegisteredEvent.kt b/app/src/main/java/me/capcom/smsgateway/modules/gateway/events/DeviceRegisteredEvent.kt index 9734aee..b81f5f5 100644 --- a/app/src/main/java/me/capcom/smsgateway/modules/gateway/events/DeviceRegisteredEvent.kt +++ b/app/src/main/java/me/capcom/smsgateway/modules/gateway/events/DeviceRegisteredEvent.kt @@ -3,6 +3,7 @@ package me.capcom.smsgateway.modules.gateway.events import me.capcom.smsgateway.modules.events.AppEvent class DeviceRegisteredEvent( + val server: String, val login: String, val password: String, ): AppEvent(NAME) { diff --git a/app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt b/app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt index 1ccd58a..bd0f9cc 100644 --- a/app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt +++ b/app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesSettings.kt @@ -9,7 +9,6 @@ class MessagesSettings( val secondsBetweenMessages: Int get() = storage.get(SECONDS_BETWEEN_MESSAGES) ?: 0 -// set(value) = storage.set(SECONDS_BETWEEN_MESSAGES, value) companion object { private const val SECONDS_BETWEEN_MESSAGES = "SECONDS_BETWEEN_MESSAGES" diff --git a/app/src/main/java/me/capcom/smsgateway/modules/settings/PreferencesStorage.kt b/app/src/main/java/me/capcom/smsgateway/modules/settings/PreferencesStorage.kt index 824e55e..fbce2f1 100644 --- a/app/src/main/java/me/capcom/smsgateway/modules/settings/PreferencesStorage.kt +++ b/app/src/main/java/me/capcom/smsgateway/modules/settings/PreferencesStorage.kt @@ -39,18 +39,22 @@ class PreferencesStorage( serializer.deserialize(it, typeOfT) } } catch (th: ClassCastException) { - when (typeOfT) { - java.lang.Long::class.java -> preferences.getLong("${prefix}.${key}", 0) as T - java.lang.Integer::class.java -> preferences.getInt("${prefix}.${key}", 0) as T - java.lang.String::class.java -> preferences.getString("${prefix}.${key}", "") as T - java.lang.Boolean::class.java -> preferences.getBoolean( - "${prefix}.${key}", - false - ) as T - - java.lang.Float::class.java -> preferences.getFloat("${prefix}.${key}", 0.0f) as T - else -> throw RuntimeException("Unknown type for key $key") - } + getFallback(typeOfT, key) + } catch (th: com.google.gson.JsonParseException) { + getFallback(typeOfT, key) } } + + private fun getFallback(typeOfT: Type, key: String) = when (typeOfT) { + java.lang.Long::class.java -> preferences.getLong("${prefix}.${key}", 0) as T + Integer::class.java -> preferences.getInt("${prefix}.${key}", 0) as T + java.lang.String::class.java -> preferences.getString("${prefix}.${key}", "") as T + java.lang.Boolean::class.java -> preferences.getBoolean( + "${prefix}.${key}", + false + ) as T + + java.lang.Float::class.java -> preferences.getFloat("${prefix}.${key}", 0.0f) as T + else -> throw RuntimeException("Unknown type for key $key") + } } \ No newline at end of file diff --git a/app/src/main/java/me/capcom/smsgateway/services/PushService.kt b/app/src/main/java/me/capcom/smsgateway/services/PushService.kt index 382e858..f441bf3 100644 --- a/app/src/main/java/me/capcom/smsgateway/services/PushService.kt +++ b/app/src/main/java/me/capcom/smsgateway/services/PushService.kt @@ -6,9 +6,6 @@ import com.google.android.gms.tasks.OnCompleteListener import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.launch import me.capcom.smsgateway.helpers.SettingsHelper import me.capcom.smsgateway.modules.gateway.PullMessagesWorker import me.capcom.smsgateway.modules.gateway.RegistrationWorker @@ -19,9 +16,7 @@ class PushService : FirebaseMessagingService() { override fun onNewToken(token: String) { settingsHelper.fcmToken = token - scope.launch { - RegistrationWorker.start(this@PushService, token) - } + RegistrationWorker.start(this@PushService, token) } override fun onMessageReceived(message: RemoteMessage) { @@ -29,15 +24,7 @@ class PushService : FirebaseMessagingService() { PullMessagesWorker.start(this) } - override fun onDestroy() { - job.cancel() - super.onDestroy() - } - companion object { - private val job = SupervisorJob() - private val scope = CoroutineScope(job) - fun register(context: Context): Unit { FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task -> if (!task.isSuccessful) { @@ -49,9 +36,7 @@ class PushService : FirebaseMessagingService() { val token = task.result // Log and toast - scope.launch { - RegistrationWorker.start(context, token) - } + RegistrationWorker.start(context, token) }) } } diff --git a/app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt b/app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt index 4cd6692..34337e4 100644 --- a/app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt +++ b/app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt @@ -92,6 +92,7 @@ class HomeFragment : Fragment() { App.instance.gatewayModule.events.events.collect { event -> val event = event as? DeviceRegisteredEvent ?: return@collect + binding.textRemoteAddress.text = getString(R.string.address_is, event.server) binding.textRemoteAuth.movementMethod = LinkMovementMethod.getInstance() binding.textRemoteAuth.text = makeCopyableLink( Html diff --git a/app/src/main/java/me/capcom/smsgateway/ui/SettingsFragment.kt b/app/src/main/java/me/capcom/smsgateway/ui/SettingsFragment.kt index c4266a9..0449eeb 100644 --- a/app/src/main/java/me/capcom/smsgateway/ui/SettingsFragment.kt +++ b/app/src/main/java/me/capcom/smsgateway/ui/SettingsFragment.kt @@ -2,24 +2,45 @@ package me.capcom.smsgateway.ui import android.os.Bundle import android.text.InputType +import androidx.core.content.edit import androidx.preference.EditTextPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import me.capcom.smsgateway.R +import me.capcom.smsgateway.modules.gateway.GatewaySettings class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) + + findPreference("gateway.cloud_url")?.setSummaryProvider { + val hostname = preferenceManager.sharedPreferences?.getString(it.key, null) + if (hostname.isNullOrEmpty()) { + preferenceManager.sharedPreferences?.edit(true) { + putString(it.key, GatewaySettings.PUBLIC_URL) + } + return@setSummaryProvider GatewaySettings.PUBLIC_URL + } + return@setSummaryProvider hostname + } } override fun onDisplayPreferenceDialog(preference: Preference) { - if (preference.key == "encryption.passphrase") { + if (preference.key == "encryption.passphrase" + || preference.key == "gateway.private_token" + ) { (preference as EditTextPreference).setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD } } + if (preference.key == "gateway.cloud_url") { + (preference as EditTextPreference).setOnBindEditTextListener { + it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI + } + } + super.onDisplayPreferenceDialog(preference) } diff --git a/app/src/main/res/drawable-notnight/ic_server.xml b/app/src/main/res/drawable-notnight/ic_server.xml new file mode 100644 index 0000000..610a839 --- /dev/null +++ b/app/src/main/res/drawable-notnight/ic_server.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable-notnight/ic_token.xml b/app/src/main/res/drawable-notnight/ic_token.xml new file mode 100644 index 0000000..8a1a01c --- /dev/null +++ b/app/src/main/res/drawable-notnight/ic_token.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_server.xml b/app/src/main/res/drawable/ic_server.xml new file mode 100644 index 0000000..6d54278 --- /dev/null +++ b/app/src/main/res/drawable/ic_server.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_token.xml b/app/src/main/res/drawable/ic_token.xml new file mode 100644 index 0000000..0aedb8e --- /dev/null +++ b/app/src/main/res/drawable/ic_token.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index d7a092a..cde0fba 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -68,13 +68,15 @@ tools:visibility="visible"> + android:text="@string/cloud_server" /> SETTINGS MESSAGES Local server - Cloud server + Cloud server Address is sms.capcom.me Start on boot Offline @@ -33,5 +33,9 @@ Encryption Use empty to disable Passphrase - Sending messages... + Sending messages… + API URL + Private Token + Ignored for public server + Address is %1$s \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index f3d707b..cb993fd 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -1,5 +1,16 @@ + + + +