Reorganize the code

Make it more readable
Prepare non-SSO ApiProvider
This commit is contained in:
sim 2023-02-18 10:18:00 +01:00
parent 76521cac7f
commit 9db96919bd
20 changed files with 548 additions and 496 deletions

View File

@ -2,6 +2,9 @@ package org.unifiedpush.distributor.nextpush.account
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.text.SpannableString import android.text.SpannableString
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.text.util.Linkify import android.text.util.Linkify
@ -16,12 +19,8 @@ import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException
import com.nextcloud.android.sso.helper.SingleAccountHelper import com.nextcloud.android.sso.helper.SingleAccountHelper
import com.nextcloud.android.sso.model.SingleSignOnAccount import com.nextcloud.android.sso.model.SingleSignOnAccount
import com.nextcloud.android.sso.ui.UiExceptionManager import com.nextcloud.android.sso.ui.UiExceptionManager
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import org.unifiedpush.distributor.nextpush.R import org.unifiedpush.distributor.nextpush.R
import org.unifiedpush.distributor.nextpush.utils.TAG
private const val TAG = "AccountUtils"
private const val PREF_NAME = "NextPush" private const val PREF_NAME = "NextPush"
private const val PREF_DEVICE_ID = "deviceId" private const val PREF_DEVICE_ID = "deviceId"
@ -99,7 +98,7 @@ object AccountUtils {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit() .edit()
.putString(PREF_DEVICE_ID, deviceId) .putString(PREF_DEVICE_ID, deviceId)
.commit() .apply()
} }
fun getDeviceId(context: Context): String? { fun getDeviceId(context: Context): String? {
@ -111,14 +110,14 @@ object AccountUtils {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit() .edit()
.remove(PREF_DEVICE_ID) .remove(PREF_DEVICE_ID)
.commit() .apply()
} }
fun saveUrl(context: Context, url: String) { fun saveUrl(context: Context, url: String) {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit() .edit()
.putString(PREF_URL, url) .putString(PREF_URL, url)
.commit() .apply()
} }
fun getUrl(context: Context): String? { fun getUrl(context: Context): String? {
@ -130,6 +129,6 @@ object AccountUtils {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit() .edit()
.remove(PREF_URL) .remove(PREF_URL)
.commit() .apply()
} }
} }

View File

@ -12,36 +12,35 @@ import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.* import android.widget.* // ktlint-disable no-wildcard-imports
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.nextcloud.android.sso.AccountImporter
import com.nextcloud.android.sso.ui.UiExceptionManager
import com.nextcloud.android.sso.helper.SingleAccountHelper
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.nextcloud.android.sso.AccountImporter
import com.nextcloud.android.sso.AccountImporter.clearAllAuthTokens import com.nextcloud.android.sso.AccountImporter.clearAllAuthTokens
import com.nextcloud.android.sso.exceptions.* import com.nextcloud.android.sso.exceptions.AccountImportCancelledException
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException
import com.nextcloud.android.sso.helper.SingleAccountHelper
import com.nextcloud.android.sso.ui.UiExceptionManager
import org.unifiedpush.distributor.nextpush.R import org.unifiedpush.distributor.nextpush.R
import org.unifiedpush.distributor.nextpush.account.AccountUtils.connect import org.unifiedpush.distributor.nextpush.account.AccountUtils.connect
import org.unifiedpush.distributor.nextpush.account.AccountUtils.isConnected import org.unifiedpush.distributor.nextpush.account.AccountUtils.isConnected
import org.unifiedpush.distributor.nextpush.account.AccountUtils.nextcloudAppNotInstalledDialog import org.unifiedpush.distributor.nextpush.account.AccountUtils.nextcloudAppNotInstalledDialog
import org.unifiedpush.distributor.nextpush.account.AccountUtils.ssoAccount import org.unifiedpush.distributor.nextpush.account.AccountUtils.ssoAccount
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiDeleteApp import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiDeleteDevice import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteDevice
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendUnregistered import org.unifiedpush.distributor.nextpush.distributor.Distributor.getDb
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.getDb import org.unifiedpush.distributor.nextpush.services.RestartWorker
import org.unifiedpush.distributor.nextpush.services.* import org.unifiedpush.distributor.nextpush.services.StartService
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.lang.String.format import java.lang.String.format
private const val TAG = "NextPush-MainActivity"
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var listView : ListView private lateinit var listView: ListView
private var showLogout = false private var showLogout = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -106,7 +105,8 @@ class MainActivity : AppCompatActivity() {
private fun requestPermissions() { private fun requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED
) {
Log.d(TAG, "Requesting POST_NOTIFICATIONS permission") Log.d(TAG, "Requesting POST_NOTIFICATIONS permission")
registerForActivityResult( registerForActivityResult(
ActivityResultContracts.RequestPermission() ActivityResultContracts.RequestPermission()
@ -132,7 +132,7 @@ class MainActivity : AppCompatActivity() {
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus) super.onWindowFocusChanged(hasFocus)
if(hasFocus) { if (hasFocus) {
setListView() setListView()
} }
} }
@ -175,7 +175,8 @@ class MainActivity : AppCompatActivity() {
private fun logout() { private fun logout() {
val alert: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder( val alert: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder(
this) this
)
alert.setTitle(getString(R.string.logout_alert_title)) alert.setTitle(getString(R.string.logout_alert_title))
alert.setMessage(R.string.logout_alert_content) alert.setMessage(R.string.logout_alert_content)
alert.setPositiveButton(R.string.ok) { dialog, _ -> alert.setPositiveButton(R.string.ok) { dialog, _ ->
@ -185,7 +186,7 @@ class MainActivity : AppCompatActivity() {
.edit() .edit()
.remove("PREF_CURRENT_ACCOUNT_STRING") .remove("PREF_CURRENT_ACCOUNT_STRING")
.apply() .apply()
apiDeleteDevice(this) deleteDevice(this)
showStart() showStart()
finish() finish()
startActivity(intent) startActivity(intent)
@ -194,42 +195,46 @@ class MainActivity : AppCompatActivity() {
alert.show() alert.show()
} }
private fun setListView(){ private fun setListView() {
listView = findViewById(R.id.applications_list) listView = findViewById(R.id.applications_list)
val db = getDb(this)
val tokenList = db.listTokens().toMutableList() val tokenList = emptyList<String>().toMutableList()
val appList = emptyArray<String>().toMutableList() val appList = emptyList<String>().toMutableList()
tokenList.forEach {
appList.add(db.getPackageName(it)) getDb(this).let { db ->
db.listTokens().forEach {
tokenList.add(it)
appList.add(db.getPackageName(it) ?: it)
}
} }
listView.adapter = ArrayAdapter( listView.adapter = ArrayAdapter(
this, this,
android.R.layout.simple_list_item_1, android.R.layout.simple_list_item_1,
appList appList
) )
listView.setOnItemLongClickListener( listView.setOnItemLongClickListener(
fun(_: AdapterView<*>, _: View, position: Int, _: Long): Boolean { fun(_: AdapterView<*>, _: View, position: Int, _: Long): Boolean {
val alert: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder( val alert: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder(
this) this
alert.setTitle("Unregistering") )
alert.setMessage("Are you sure to unregister ${appList[position]} ?") alert.setTitle("Unregistering")
alert.setPositiveButton("YES") { dialog, _ -> alert.setMessage("Are you sure to unregister ${appList[position]} ?")
val connectorToken = tokenList[position] alert.setPositiveButton("YES") { dialog, _ ->
sendUnregistered(this, connectorToken) val connectorToken = tokenList[position]
val database = getDb(this) deleteApp(this, connectorToken) {
val appToken = database.getAppToken(connectorToken) Log.d(TAG, "Unregistration is finished")
database.unregisterApp(connectorToken) this@MainActivity.runOnUiThread {
apiDeleteApp(this, appToken) { setListView()
Log.d(TAG,"Unregistration is finished")
} }
tokenList.removeAt(position)
appList.removeAt(position)
dialog.dismiss()
} }
alert.setNegativeButton("NO") { dialog, _ -> dialog.dismiss() } dialog.dismiss()
alert.show()
return true
} }
alert.setNegativeButton("NO") { dialog, _ -> dialog.dismiss() }
alert.show()
return true
}
) )
} }
} }

View File

@ -0,0 +1,211 @@
package org.unifiedpush.distributor.nextpush.api
import android.content.Context
import android.os.Build
import android.util.Log
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.sse.EventSource
import okhttp3.sse.EventSources
import org.unifiedpush.distributor.nextpush.account.AccountUtils.getDeviceId
import org.unifiedpush.distributor.nextpush.account.AccountUtils.removeDeviceId
import org.unifiedpush.distributor.nextpush.account.AccountUtils.removeUrl
import org.unifiedpush.distributor.nextpush.account.AccountUtils.saveDeviceId
import org.unifiedpush.distributor.nextpush.account.AccountUtils.saveUrl
import org.unifiedpush.distributor.nextpush.account.AccountUtils.ssoAccount
import org.unifiedpush.distributor.nextpush.api.provider.ApiProvider
import org.unifiedpush.distributor.nextpush.api.provider.ApiProvider.Companion.mApiEndpoint
import org.unifiedpush.distributor.nextpush.api.provider.ApiProviderFactory
import org.unifiedpush.distributor.nextpush.api.provider.ApiSSOFactory
import org.unifiedpush.distributor.nextpush.api.response.ApiResponse
import org.unifiedpush.distributor.nextpush.services.SSEListener
import java.util.concurrent.TimeUnit
object Api {
private val TAG = Api::class.java.simpleName
private var provider: ApiProviderFactory? = null
private var source: EventSource? = null
private fun Context.withApiProvider(block: (ApiProvider) -> Unit) {
(
provider ?: run {
Log.d(TAG, "Setting SSOProvider")
ApiSSOFactory(this).apply {
provider = this
}
}
).getProviderAndExecute(block)
}
fun apiDestroy() {
provider?.destroyProvider()
provider = null
source?.cancel()
source = null
}
fun Context.apiSync() {
getDeviceId(this)?.let {
syncDevice(it)
}
?: run {
Log.d(TAG, "No deviceId found.")
var deviceId: String? = null
val parameters = mapOf("deviceName" to Build.MODEL)
withApiProvider { apiProvider ->
apiProvider.createDevice(parameters)
?.subscribeOn(Schedulers.newThread())
?.observeOn(Schedulers.newThread())
?.subscribe(object : Observer<ApiResponse?> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "onSubscribe")
}
override fun onNext(response: ApiResponse) {
response.deviceId.let {
saveDeviceId(this@apiSync, it)
deviceId = it
}
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onComplete() {
saveUrl(this@apiSync, "${ssoAccount.url}$mApiEndpoint")
// Sync once it is registered
deviceId?.let {
syncDevice(it)
}
Log.d(TAG, "mApi register: onComplete")
}
})
}
}
}
private fun Context.syncDevice(deviceId: String) {
val client = OkHttpClient.Builder()
.readTimeout(0, TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.build()
val url = "${ssoAccount.url}$mApiEndpoint/device/$deviceId"
val request = Request.Builder().url(url)
.get()
.build()
source = EventSources.createFactory(client).newEventSource(request, SSEListener(this))
Log.d(TAG, "cSync done.")
}
fun Context.apiDeleteDevice() {
val deviceId = getDeviceId(this) ?: return
withApiProvider { apiProvider ->
apiProvider.deleteDevice(deviceId)
?.subscribeOn(Schedulers.newThread())
?.observeOn(Schedulers.newThread())
?.subscribe(object : Observer<ApiResponse?> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "Subscribed to deleteDevice.")
}
override fun onNext(response: ApiResponse) {
if (response.success) {
Log.d(TAG, "Device successfully deleted.")
} else {
Log.d(TAG, "An error occurred while deleting the device.")
}
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onComplete() {
removeUrl(this@apiDeleteDevice)
}
})
removeDeviceId(this)
}
}
fun Context.apiCreateApp(
appName: String,
block: (String?) -> Unit
) {
// The unity of connector token must already be checked here
val parameters = getDeviceId(this)?.let {
mutableMapOf(
"deviceId" to it,
"appName" to appName
)
} ?: return
withApiProvider { apiProvider ->
apiProvider.createApp(parameters)
?.subscribeOn(Schedulers.newThread())
?.observeOn(Schedulers.newThread())
?.subscribe(object : Observer<ApiResponse?> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "Subscribed to createApp.")
}
override fun onNext(response: ApiResponse) {
val nextpushToken = if (response.success) {
Log.d(TAG, "App successfully created.")
response.token
} else {
Log.d(TAG, "An error occurred while creating the application.")
null
}
block(nextpushToken)
}
override fun onError(e: Throwable) {
block(null)
e.printStackTrace()
}
override fun onComplete() {}
})
}
}
fun Context.apiDeleteApp(nextpushToken: String, block: () -> Unit) {
withApiProvider { apiProvider ->
apiProvider.deleteApp(nextpushToken)
?.subscribeOn(Schedulers.newThread())
?.observeOn(Schedulers.newThread())
?.subscribe(object : Observer<ApiResponse?> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "Subscribed to deleteApp.")
}
override fun onNext(response: ApiResponse) {
if (response.success) {
Log.d(TAG, "App successfully deleted.")
} else {
Log.d(TAG, "An error occurred while deleting the application.")
}
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onComplete() {
block()
}
})
}
}
}

View File

@ -1,271 +0,0 @@
package org.unifiedpush.distributor.nextpush.api
import android.content.Context
import android.os.Build
import android.util.Log
import com.google.gson.GsonBuilder
import com.nextcloud.android.sso.api.NextcloudAPI
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.sse.EventSource
import okhttp3.sse.EventSources
import org.unifiedpush.distributor.nextpush.account.AccountUtils.getDeviceId
import org.unifiedpush.distributor.nextpush.account.AccountUtils.removeDeviceId
import org.unifiedpush.distributor.nextpush.account.AccountUtils.removeUrl
import org.unifiedpush.distributor.nextpush.account.AccountUtils.saveDeviceId
import org.unifiedpush.distributor.nextpush.account.AccountUtils.saveUrl
import org.unifiedpush.distributor.nextpush.account.AccountUtils.ssoAccount
import org.unifiedpush.distributor.nextpush.api.ProviderApi.Companion.mApiEndpoint
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.getDb
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendUnregistered
import org.unifiedpush.distributor.nextpush.services.SSEListener
import retrofit2.NextcloudRetrofitApiBuilder
import java.util.concurrent.TimeUnit
private const val TAG = "ApiUtils"
object ApiUtils {
val createQueue = emptyList<String>().toMutableList()
val delQueue = emptyList<String>().toMutableList()
private var mApi: ProviderApi? = null
private var nextcloudAPI: NextcloudAPI? = null
private var source: EventSource? = null
fun apiDestroy() {
nextcloudAPI?.stop()
source?.cancel()
nextcloudAPI = null
source = null
mApi = null
}
private fun cApi(context: Context, callback: () -> Unit) {
mApi?.let {
callback()
} ?: run {
val nCallback = object : NextcloudAPI.ApiConnectedListener {
override fun onConnected() {
Log.d(TAG, "Api connected.")
callback()
}
override fun onError(ex: Exception) {
Log.d(TAG, "Cannot connect to API: ex = [$ex]")
}
}
NextcloudAPI(context, ssoAccount, GsonBuilder().create(), nCallback).let {
nextcloudAPI = it
mApi = NextcloudRetrofitApiBuilder(it, mApiEndpoint)
.create(ProviderApi::class.java)
}
}
}
fun apiSync(context: Context) {
cApi(context) { cSync(context) }
}
private fun cSync(context: Context) {
var deviceId = getDeviceId(context)
// Register the device if it is not yet registered
if (deviceId.isNullOrEmpty()) {
Log.d(TAG, "No deviceId found.")
val parameters: MutableMap<String, String> = HashMap()
parameters["deviceName"] = Build.MODEL
mApi?.createDevice(parameters)
?.subscribeOn(Schedulers.newThread())
?.observeOn(Schedulers.newThread())
?.subscribe(object : Observer<ApiResponse?> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "onSubscribe")
}
override fun onNext(response: ApiResponse) {
response.deviceId.let {
saveDeviceId(context, it)
deviceId = it
}
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onComplete() {
saveUrl(context, "${ssoAccount.url}${mApiEndpoint}")
// Sync once it is registered
deviceId?.let {
cSync(context, it)
}
Log.d(TAG, "mApi register: onComplete")
}
})
} else {
// Sync directly
Log.d(TAG, "Found saved deviceId")
deviceId?.let {
cSync(context, it)
}
}
}
private fun cSync(context: Context, deviceId: String) {
val client = OkHttpClient.Builder()
.readTimeout(0, TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.build()
val url = "${ssoAccount.url}$mApiEndpoint/device/$deviceId"
val request = Request.Builder().url(url)
.get()
.build()
source = EventSources.createFactory(client).newEventSource(request, SSEListener(context))
Log.d(TAG, "cSync done.")
}
fun apiDeleteDevice(context: Context) {
val db = getDb(context)
db.listTokens().forEach {
sendUnregistered(context, it)
db.unregisterApp(it)
}
cApi(context) { cDeleteDevice(context) }
}
private fun cDeleteDevice(context: Context) {
val deviceId = getDeviceId(context) ?: return
mApi?.deleteDevice(deviceId)
?.subscribeOn(Schedulers.newThread())
?.observeOn(Schedulers.newThread())
?.subscribe(object : Observer<ApiResponse?> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "Subscribed to deleteDevice.")
}
override fun onNext(response: ApiResponse) {
if (response.success) {
Log.d(TAG, "Device successfully deleted.")
} else {
Log.d(TAG, "An error occurred while deleting the device.")
}
}
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onComplete() {
removeUrl(context)
}
})
removeDeviceId(context)
}
fun apiCreateApp(
context: Context,
appName: String,
connectorToken: String,
callback: () -> Unit
) {
cApi(context) {
cCreateApp(context, appName, connectorToken) {
callback()
}
}
}
private fun cCreateApp(
context: Context,
appName: String,
connectorToken: String,
callback: () -> Unit
) {
// The unity of connector token must already be checked here
val parameters = getDeviceId(context)?.let {
mutableMapOf(
"deviceId" to it,
"appName" to appName
)
} ?: return
mApi?.createApp(parameters)
?.subscribeOn(Schedulers.newThread())
?.observeOn(Schedulers.newThread())
?.subscribe(object : Observer<ApiResponse?> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "Subscribed to createApp.")
}
override fun onNext(response: ApiResponse) {
if (response.success) {
Log.d(TAG, "App successfully created.")
/**
* Ignore printed error for SQLiteContstraintException.
* It is printed and not thrown by SQLiteDatabase.java
* So we can't catch it
*/
val db = getDb(context)
db.registerApp(appName, connectorToken, response.token)
} else {
Log.d(TAG, "An error occurred while creating the application.")
}
}
override fun onError(e: Throwable) {
createQueue.remove(connectorToken)
e.printStackTrace()
}
override fun onComplete() {
createQueue.remove(connectorToken)
callback()
}
})
}
fun apiDeleteApp(context: Context, connectorToken: String, callback: () -> Unit) {
cApi(context) {
cDeleteApp(context, connectorToken) {
callback()
}
}
}
private fun cDeleteApp(context: Context, connectorToken: String, callback: () -> Unit) {
val appToken = getDb(context).getAppToken(connectorToken)
mApi?.deleteApp(appToken)
?.subscribeOn(Schedulers.newThread())
?.observeOn(Schedulers.newThread())
?.subscribe(object : Observer<ApiResponse?> {
override fun onSubscribe(d: Disposable) {
Log.d(TAG, "Subscribed to deleteApp.")
}
override fun onNext(response: ApiResponse) {
if (response.success) {
Log.d(TAG, "App successfully deleted.")
} else {
Log.d(TAG, "An error occurred while deleting the application.")
}
}
override fun onError(e: Throwable) {
delQueue.remove(connectorToken)
e.printStackTrace()
}
override fun onComplete() {
delQueue.remove(connectorToken)
callback()
}
})
}
}

View File

@ -1,16 +1,17 @@
package org.unifiedpush.distributor.nextpush.api package org.unifiedpush.distributor.nextpush.api.provider
import io.reactivex.Observable import io.reactivex.Observable
import retrofit2.http.PUT import org.unifiedpush.distributor.nextpush.api.response.ApiResponse
import retrofit2.http.DELETE
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.PUT
import retrofit2.http.Path import retrofit2.http.Path
interface ProviderApi { interface ApiProvider {
@PUT("/device/") @PUT("/device/")
fun createDevice( fun createDevice(
@Body subscribeMap: MutableMap<String, String> @Body subscribeMap: Map<String, String>
): Observable<ApiResponse>? ): Observable<ApiResponse>?
@DELETE("/device/{deviceId}") @DELETE("/device/{deviceId}")
@ -18,7 +19,7 @@ interface ProviderApi {
@PUT("/app/") @PUT("/app/")
fun createApp( fun createApp(
@Body authorizeMap: MutableMap<String, String> @Body authorizeMap: Map<String, String>
): Observable<ApiResponse>? ): Observable<ApiResponse>?
@DELETE("/app/{token}") @DELETE("/app/{token}")

View File

@ -0,0 +1,6 @@
package org.unifiedpush.distributor.nextpush.api.provider
interface ApiProviderFactory {
fun getProviderAndExecute(block: (ApiProvider) -> Unit)
fun destroyProvider()
}

View File

@ -0,0 +1,46 @@
package org.unifiedpush.distributor.nextpush.api.provider
import android.content.Context
import android.util.Log
import com.google.gson.GsonBuilder
import com.nextcloud.android.sso.api.NextcloudAPI
import org.unifiedpush.distributor.nextpush.account.AccountUtils
import retrofit2.NextcloudRetrofitApiBuilder
class ApiSSOFactory(val context: Context) : ApiProviderFactory {
private val TAG = ApiSSOFactory::class.java.simpleName
private var apiProvider: ApiProvider? = null
private lateinit var nextcloudAPI: NextcloudAPI
override fun getProviderAndExecute(block: (ApiProvider) -> Unit) {
apiProvider?.let(block)
?: run {
Log.d(TAG, "Creating new provider")
val ssoApiCallback = object : NextcloudAPI.ApiConnectedListener {
override fun onConnected() {
Log.d(TAG, "Api connected.")
NextcloudRetrofitApiBuilder(nextcloudAPI, ApiProvider.mApiEndpoint)
.create(ApiProvider::class.java).let {
apiProvider = it
block(it)
}
}
override fun onError(ex: Exception) {
Log.d(TAG, "Cannot connect to API: ex = [$ex]")
}
}
nextcloudAPI = NextcloudAPI(
context,
AccountUtils.ssoAccount,
GsonBuilder().create(),
ssoApiCallback
)
}
}
override fun destroyProvider() {
nextcloudAPI.stop()
}
}

View File

@ -1,7 +1,7 @@
package org.unifiedpush.distributor.nextpush.api package org.unifiedpush.distributor.nextpush.api.response
data class ApiResponse( data class ApiResponse(
val success: Boolean = false, val success: Boolean = false,
val deviceId: String = "", val deviceId: String = "",
val token: String = "", val token: String = ""
) )

View File

@ -1,8 +1,8 @@
package org.unifiedpush.distributor.nextpush.api package org.unifiedpush.distributor.nextpush.api.response
data class SSEResponse ( data class SSEResponse(
val type: String = "", val type: String = "",
val token: String = "", val token: String = "",
val message: String = "", val message: String = "",
val keepalive: Int = 900 val keepalive: Int = 900
) )

View File

@ -13,12 +13,12 @@ private const val FIELD_PACKAGE_NAME = "packageName"
private const val FIELD_CONNECTOR_TOKEN = "connectorToken" private const val FIELD_CONNECTOR_TOKEN = "connectorToken"
private const val FIELD_APP_TOKEN = "appToken" private const val FIELD_APP_TOKEN = "appToken"
private const val CREATE_TABLE_APPS = "CREATE TABLE apps (" + private const val CREATE_TABLE_APPS = "CREATE TABLE apps (" +
"$FIELD_PACKAGE_NAME TEXT," + "$FIELD_PACKAGE_NAME TEXT," +
"$FIELD_CONNECTOR_TOKEN TEXT," + "$FIELD_CONNECTOR_TOKEN TEXT," +
"$FIELD_APP_TOKEN TEXT," + "$FIELD_APP_TOKEN TEXT," +
"PRIMARY KEY ($FIELD_CONNECTOR_TOKEN));" "PRIMARY KEY ($FIELD_CONNECTOR_TOKEN));"
class MessagingDatabase(context: Context): class ConnectionsDatabase(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) { override fun onCreate(db: SQLiteDatabase) {
@ -51,19 +51,19 @@ class MessagingDatabase(context: Context):
val selection = "$FIELD_PACKAGE_NAME = ? AND $FIELD_CONNECTOR_TOKEN = ?" val selection = "$FIELD_PACKAGE_NAME = ? AND $FIELD_CONNECTOR_TOKEN = ?"
val selectionArgs = arrayOf(packageName, connectorToken) val selectionArgs = arrayOf(packageName, connectorToken)
return db.query( return db.query(
TABLE_APPS, TABLE_APPS,
null, null,
selection, selection,
selectionArgs, selectionArgs,
null, null,
null, null,
null null
).use { cursor -> ).use { cursor ->
(cursor != null && cursor.count > 0) (cursor != null && cursor.count > 0)
} }
} }
fun getPackageName(connectorToken: String): String { fun getPackageName(connectorToken: String): String? {
val db = readableDatabase val db = readableDatabase
val projection = arrayOf(FIELD_PACKAGE_NAME) val projection = arrayOf(FIELD_PACKAGE_NAME)
val selection = "$FIELD_CONNECTOR_TOKEN = ?" val selection = "$FIELD_CONNECTOR_TOKEN = ?"
@ -78,11 +78,11 @@ class MessagingDatabase(context: Context):
null null
).use { cursor -> ).use { cursor ->
val column = cursor.getColumnIndex(FIELD_PACKAGE_NAME) val column = cursor.getColumnIndex(FIELD_PACKAGE_NAME)
if (cursor.moveToFirst() && column >= 0) cursor.getString(column) else "" if (cursor.moveToFirst() && column >= 0) cursor.getString(column) else null
} }
} }
fun getAppToken(connectorToken: String): String { fun getAppToken(connectorToken: String): String? {
val db = readableDatabase val db = readableDatabase
val projection = arrayOf(FIELD_APP_TOKEN) val projection = arrayOf(FIELD_APP_TOKEN)
val selection = "$FIELD_CONNECTOR_TOKEN = ?" val selection = "$FIELD_CONNECTOR_TOKEN = ?"
@ -97,11 +97,11 @@ class MessagingDatabase(context: Context):
null null
).use { cursor -> ).use { cursor ->
val column = cursor.getColumnIndex(FIELD_APP_TOKEN) val column = cursor.getColumnIndex(FIELD_APP_TOKEN)
if (cursor.moveToFirst() && column >= 0) cursor.getString(column) else "" if (cursor.moveToFirst() && column >= 0) cursor.getString(column) else null
} }
} }
fun getConnectorToken(appToken: String): String { fun getConnectorToken(appToken: String): String? {
val db = readableDatabase val db = readableDatabase
val projection = arrayOf(FIELD_CONNECTOR_TOKEN) val projection = arrayOf(FIELD_CONNECTOR_TOKEN)
val selection = "$FIELD_APP_TOKEN = ?" val selection = "$FIELD_APP_TOKEN = ?"
@ -116,7 +116,7 @@ class MessagingDatabase(context: Context):
null null
).use { cursor -> ).use { cursor ->
val column = cursor.getColumnIndex(FIELD_CONNECTOR_TOKEN) val column = cursor.getColumnIndex(FIELD_CONNECTOR_TOKEN)
if (cursor.moveToFirst() && column >= 0) cursor.getString(column) else "" if (cursor.moveToFirst() && column >= 0) cursor.getString(column) else null
} }
} }
@ -131,11 +131,12 @@ class MessagingDatabase(context: Context):
null, null,
null, null,
null null
).use{ cursor -> ).use { cursor ->
generateSequence { if (cursor.moveToNext()) cursor else null } generateSequence { if (cursor.moveToNext()) cursor else null }
.mapNotNull{ .mapNotNull {
val column = cursor.getColumnIndex(FIELD_CONNECTOR_TOKEN) val column = cursor.getColumnIndex(FIELD_CONNECTOR_TOKEN)
if (column >= 0) it.getString(column) else null } if (column >= 0) it.getString(column) else null
}
.toList() .toList()
} }
} }

View File

@ -4,35 +4,35 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log
import org.unifiedpush.distributor.nextpush.account.AccountUtils.getUrl import org.unifiedpush.distributor.nextpush.account.AccountUtils.getUrl
import org.unifiedpush.distributor.nextpush.api.Api.apiCreateApp
import org.unifiedpush.distributor.nextpush.api.Api.apiDeleteApp
import org.unifiedpush.distributor.nextpush.api.Api.apiDeleteDevice
import org.unifiedpush.distributor.nextpush.utils.TAG
/** /**
* These functions are used to send messages to other apps * These functions are used to send messages to other apps
*/ */
private const val TAG = "DistributorUtils" object Distributor {
object DistributorUtils {
const val TOKEN_NEW = "token_new" const val TOKEN_NEW = "token_new"
const val TOKEN_REGISTERED_OK = "token_registered_ok" const val TOKEN_REGISTERED_OK = "token_registered_ok"
const val TOKEN_NOK = "token_nok" const val TOKEN_NOK = "token_nok"
private lateinit var db: MessagingDatabase private lateinit var db: ConnectionsDatabase
fun getDb(context: Context): MessagingDatabase { fun getDb(context: Context): ConnectionsDatabase {
if (!this::db.isInitialized) { if (!this::db.isInitialized) {
db = MessagingDatabase(context) db = ConnectionsDatabase(context)
} }
return db return db
} }
fun sendMessage(context: Context, appToken: String, message: ByteArray) { fun sendMessage(context: Context, appToken: String, message: ByteArray) {
val db = getDb(context) val db = getDb(context)
val connectorToken = db.getConnectorToken(appToken) val connectorToken = db.getConnectorToken(appToken) ?: return
val application = getApp(context, connectorToken) val application = getApp(context, connectorToken)
if (application.isNullOrBlank()) {
return
}
val broadcastIntent = Intent() val broadcastIntent = Intent()
broadcastIntent.`package` = application broadcastIntent.`package` = application
broadcastIntent.action = ACTION_MESSAGE broadcastIntent.action = ACTION_MESSAGE
@ -73,7 +73,7 @@ object DistributorUtils {
context.sendBroadcast(broadcastIntent) context.sendBroadcast(broadcastIntent)
} }
fun sendUnregistered(context: Context, connectorToken: String) { private fun sendUnregistered(context: Context, connectorToken: String) {
val application = getApp(context, connectorToken) val application = getApp(context, connectorToken)
if (application.isNullOrBlank()) { if (application.isNullOrBlank()) {
return return
@ -88,10 +88,7 @@ object DistributorUtils {
private fun getApp(context: Context, connectorToken: String): String? { private fun getApp(context: Context, connectorToken: String): String? {
val db = getDb(context) val db = getDb(context)
val app = db.getPackageName(connectorToken) val app = db.getPackageName(connectorToken)
return app.ifBlank { return app
Log.w(TAG, "No app found for this token")
null
}
} }
private fun getEndpoint(context: Context, connectorToken: String): String { private fun getEndpoint(context: Context, connectorToken: String): String {
@ -110,4 +107,41 @@ object DistributorUtils {
} }
return TOKEN_NOK return TOKEN_NOK
} }
}
fun deleteDevice(context: Context) {
val db = getDb(context)
db.listTokens().forEach {
sendUnregistered(context, it)
db.unregisterApp(it)
}
context.apiDeleteDevice()
}
fun createApp(context: Context, appName: String, connectorToken: String, block: () -> Unit) {
context.apiCreateApp(appName) { nextpushToken ->
nextpushToken?.let {
getDb(context).registerApp(appName, connectorToken, it)
}
block()
}
}
fun deleteApp(context: Context, connectorToken: String, block: () -> Unit) {
sendUnregistered(context, connectorToken)
val db = getDb(context)
db.getAppToken(
connectorToken
)?.let { nextpushToken ->
context.apiDeleteApp(nextpushToken) {
db.unregisterApp(connectorToken)
block()
}
}
}
fun deleteAppFromAppToken(context: Context, appToken: String) {
getDb(context).getConnectorToken(appToken)?.let {
deleteApp(context, it) {}
}
}
}

View File

@ -6,33 +6,33 @@ import android.content.Intent
import android.os.PowerManager import android.os.PowerManager
import android.util.Log import android.util.Log
import org.unifiedpush.distributor.nextpush.account.AccountUtils.isConnected import org.unifiedpush.distributor.nextpush.account.AccountUtils.isConnected
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiCreateApp import org.unifiedpush.distributor.nextpush.distributor.* // ktlint-disable no-wildcard-imports
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiDeleteApp import org.unifiedpush.distributor.nextpush.distributor.Distributor.TOKEN_NEW
import org.unifiedpush.distributor.nextpush.api.ApiUtils.createQueue import org.unifiedpush.distributor.nextpush.distributor.Distributor.TOKEN_NOK
import org.unifiedpush.distributor.nextpush.api.ApiUtils.delQueue import org.unifiedpush.distributor.nextpush.distributor.Distributor.TOKEN_REGISTERED_OK
import org.unifiedpush.distributor.nextpush.distributor.Distributor.checkToken
import org.unifiedpush.distributor.nextpush.distributor.* import org.unifiedpush.distributor.nextpush.distributor.Distributor.createApp
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.TOKEN_NEW import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.TOKEN_NOK import org.unifiedpush.distributor.nextpush.distributor.Distributor.getDb
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.TOKEN_REGISTERED_OK import org.unifiedpush.distributor.nextpush.distributor.Distributor.sendEndpoint
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.checkToken import org.unifiedpush.distributor.nextpush.distributor.Distributor.sendRegistrationFailed
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.getDb import org.unifiedpush.distributor.nextpush.utils.TAG
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendEndpoint
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendRegistrationFailed
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendUnregistered
import java.lang.Exception import java.lang.Exception
import java.util.Timer
import kotlin.concurrent.schedule
/** /**
* THIS SERVICE IS USED BY OTHER APPS TO REGISTER * THIS SERVICE IS USED BY OTHER APPS TO REGISTER
*/ */
private const val TAG = "RegisterBroadcastReceiver" private val createQueue = emptyList<String>().toMutableList()
private val delQueue = emptyList<String>().toMutableList()
class RegisterBroadcastReceiver : BroadcastReceiver() { class RegisterBroadcastReceiver : BroadcastReceiver() {
companion object { companion object {
private const val WAKE_LOCK_TAG = "NextPush:RegisterBroadcastReceiver:lock" private const val WAKE_LOCK_TAG = "NextPush:RegisterBroadcastReceiver:lock"
private var wakeLock : PowerManager.WakeLock? = null private var wakeLock: PowerManager.WakeLock? = null
} }
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
@ -41,25 +41,27 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
} }
wakeLock?.acquire(30000L /*30 secs*/) wakeLock?.acquire(30000L /*30 secs*/)
when (intent?.action) { when (intent?.action) {
ACTION_REGISTER ->{ ACTION_REGISTER -> {
Log.i(TAG,"REGISTER") Log.i(TAG, "REGISTER")
val connectorToken = intent.getStringExtra(EXTRA_TOKEN)?: "" val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: run {
val application = intent.getStringExtra(EXTRA_APPLICATION)?: "" Log.w(TAG, "Trying to register an app without connector token")
if (application.isBlank()) {
Log.w(TAG,"Trying to register an app without packageName")
return return
} }
when (checkToken(context, connectorToken, application)) { val application = intent.getStringExtra(EXTRA_APPLICATION) ?: run {
Log.w(TAG, "Trying to register an app without packageName")
return
}
when (checkToken(context.applicationContext, connectorToken, application)) {
TOKEN_REGISTERED_OK -> sendEndpoint(context.applicationContext, connectorToken) TOKEN_REGISTERED_OK -> sendEndpoint(context.applicationContext, connectorToken)
TOKEN_NOK -> sendRegistrationFailed( TOKEN_NOK -> sendRegistrationFailed(
context, context.applicationContext,
application, application,
connectorToken connectorToken
) )
TOKEN_NEW -> { TOKEN_NEW -> {
if (!isConnected(context, showDialog = false)) { if (!isConnected(context.applicationContext, showDialog = false)) {
sendRegistrationFailed( sendRegistrationFailed(
context, context.applicationContext,
application, application,
connectorToken, connectorToken,
message = "NextPush is not connected" message = "NextPush is not connected"
@ -68,12 +70,14 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
} }
if (connectorToken !in createQueue) { if (connectorToken !in createQueue) {
createQueue.add(connectorToken) createQueue.add(connectorToken)
apiCreateApp( delayRemove(createQueue, connectorToken)
createApp(
context.applicationContext, context.applicationContext,
application, application,
connectorToken connectorToken
) { ) {
sendEndpoint(context.applicationContext, connectorToken) sendEndpoint(context.applicationContext, connectorToken)
createQueue.remove(connectorToken)
} }
} else { } else {
Log.d(TAG, "Already registering this token") Log.d(TAG, "Already registering this token")
@ -81,21 +85,18 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
} }
} }
} }
ACTION_UNREGISTER ->{ ACTION_UNREGISTER -> {
Log.i(TAG,"UNREGISTER") Log.i(TAG, "UNREGISTER")
val connectorToken = intent.getStringExtra(EXTRA_TOKEN)?: "" val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: ""
val application = getDb(context).getPackageName(connectorToken) getDb(context.applicationContext).getPackageName(connectorToken) ?: return
if (application.isBlank()) {
return
}
if (connectorToken !in delQueue) { if (connectorToken !in delQueue) {
delQueue.add(connectorToken) delQueue.add(connectorToken)
sendUnregistered(context.applicationContext, connectorToken) delayRemove(delQueue, connectorToken)
try { try {
apiDeleteApp(context.applicationContext, connectorToken) { deleteApp(context.applicationContext, connectorToken) {
val db = getDb(context.applicationContext)
db.unregisterApp(connectorToken)
Log.d(TAG, "Unregistration is finished") Log.d(TAG, "Unregistration is finished")
delQueue.remove(connectorToken)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.d(TAG, "Could not delete app") Log.d(TAG, "Could not delete app")
@ -111,4 +112,10 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
} }
} }
} }
}
private fun delayRemove(list: MutableList<String>, token: String) {
Timer().schedule(1_000L /* 1sec */) {
list.remove(token)
}
}
}

View File

@ -13,4 +13,3 @@ class StartReceiver : BroadcastReceiver() {
} }
} }
} }

View File

@ -3,32 +3,33 @@ package org.unifiedpush.distributor.nextpush.services
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import okhttp3.sse.EventSource import okhttp3.sse.EventSource
import org.unifiedpush.distributor.nextpush.services.NotificationUtils.createWarningNotification import org.unifiedpush.distributor.nextpush.utils.NotificationUtils.createWarningNotification
import org.unifiedpush.distributor.nextpush.services.NotificationUtils.deleteWarningNotification import org.unifiedpush.distributor.nextpush.utils.NotificationUtils.deleteWarningNotification
import org.unifiedpush.distributor.nextpush.utils.TAG
private const val TAG = "FailureHandler"
interface FailureHandler { interface FailureHandler {
var nFails: Int var nFails: Int
// This is the last eventSource opened // This is the last eventSource opened
var eventSource: EventSource? var eventSource: EventSource?
fun newEventSource(context: Context, eventSource: EventSource) { fun newEventSource(context: Context, eventSource: EventSource) {
Log.d(TAG,"newEvent/Eventsource: $eventSource") Log.d(TAG, "newEvent/Eventsource: $eventSource")
this.eventSource = eventSource this.eventSource = eventSource
nFails = 0 nFails = 0
deleteWarningNotification(context) deleteWarningNotification(context)
} }
fun newFail(context: Context, eventSource: EventSource?) { fun newFail(context: Context, eventSource: EventSource?) {
Log.d(TAG,"newFail/Eventsource: $eventSource") Log.d(TAG, "newFail/Eventsource: $eventSource")
// no eventSource or the last opened // no eventSource or the last opened
if (this.eventSource == null || this.eventSource == eventSource) { if (this.eventSource == null || this.eventSource == eventSource) {
Log.d(TAG, "EventSource is known or null") Log.d(TAG, "EventSource is known or null")
nFails++ nFails++
if (hasFailed(twice = true)) if (hasFailed(twice = true)) {
createWarningNotification(context) createWarningNotification(context)
}
} }
} }
@ -44,10 +45,10 @@ interface FailureHandler {
nFails = 0 nFails = 0
eventSource = null eventSource = null
} }
fun hasFailed(twice: Boolean = false, orNeverStart: Boolean = true): Boolean { fun hasFailed(twice: Boolean = false, orNeverStart: Boolean = true): Boolean {
// nFails > 0 to be sure it is not actually restarting // nFails > 0 to be sure it is not actually restarting
return if (orNeverStart) { eventSource == null } else { false } || return if (orNeverStart) { eventSource == null } else { false } ||
nFails > if (twice) { 1 } else { 0 } nFails > if (twice) { 1 } else { 0 }
} }
} }

View File

@ -2,18 +2,18 @@ package org.unifiedpush.distributor.nextpush.services
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import androidx.work.* import androidx.work.* // ktlint-disable no-wildcard-imports
import org.unifiedpush.distributor.nextpush.services.SSEListener.Companion.keepalive import org.unifiedpush.distributor.nextpush.services.SSEListener.Companion.keepalive
import org.unifiedpush.distributor.nextpush.services.SSEListener.Companion.lastEventDate import org.unifiedpush.distributor.nextpush.services.SSEListener.Companion.lastEventDate
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
private const val UNIQUE_WORK_TAG = "nextpush::RestartWorker::unique" private const val UNIQUE_WORK_TAG = "nextpush::RestartWorker::unique"
class RestartWorker (ctx: Context, params: WorkerParameters) : Worker(ctx, params) { class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
companion object { companion object {
private const val TAG = "RestartWorker"
fun start(context: Context, delay: Long? = null) { fun start(context: Context, delay: Long? = null) {
val work = PeriodicWorkRequestBuilder<RestartWorker>(16, TimeUnit.MINUTES) val work = PeriodicWorkRequestBuilder<RestartWorker>(16, TimeUnit.MINUTES)
if (delay != null) { if (delay != null) {
@ -42,10 +42,10 @@ class RestartWorker (ctx: Context, params: WorkerParameters) : Worker(ctx, param
StartService.setMaxFails(applicationContext) // Max, will keep using the current worker StartService.setMaxFails(applicationContext) // Max, will keep using the current worker
StartService.startListener(applicationContext) StartService.startListener(applicationContext)
} }
}?:run { } ?: run {
Log.d(TAG, "Restarting") Log.d(TAG, "Restarting")
StartService.startListener(applicationContext) StartService.startListener(applicationContext)
} }
return Result.success() return Result.success()
} }
} }

View File

@ -2,24 +2,22 @@ package org.unifiedpush.distributor.nextpush.services
import android.content.Context import android.content.Context
import android.util.Base64 import android.util.Base64
import okhttp3.sse.EventSourceListener
import okhttp3.sse.EventSource
import android.util.Log import android.util.Log
import okhttp3.Response
import java.lang.Exception
import com.google.gson.Gson import com.google.gson.Gson
import org.unifiedpush.distributor.nextpush.api.SSEResponse import okhttp3.Response
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.getDb import okhttp3.sse.EventSource
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendMessage import okhttp3.sse.EventSourceListener
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendUnregistered import org.unifiedpush.distributor.nextpush.api.response.SSEResponse
import java.util.* import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteAppFromAppToken
import org.unifiedpush.distributor.nextpush.distributor.Distributor.sendMessage
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.lang.Exception
import java.util.Calendar
private const val TAG = "SSEListener" class SSEListener(val context: Context) : EventSourceListener() {
class SSEListener (val context: Context) : EventSourceListener() {
companion object { companion object {
var lastEventDate : Calendar? = null var lastEventDate: Calendar? = null
var keepalive = 900 var keepalive = 900
} }
@ -58,12 +56,7 @@ class SSEListener (val context: Context) : EventSourceListener() {
} }
"deleteApp" -> { "deleteApp" -> {
val message = Gson().fromJson(data, SSEResponse::class.java) val message = Gson().fromJson(data, SSEResponse::class.java)
val db = getDb(context.applicationContext) deleteAppFromAppToken(context, message.token)
val connectorToken = db.getConnectorToken(message.token)
if (connectorToken.isEmpty())
return
sendUnregistered(context.applicationContext, connectorToken)
db.unregisterApp(connectorToken)
} }
} }
StartService.wakeLock?.let { StartService.wakeLock?.let {
@ -74,16 +67,18 @@ class SSEListener (val context: Context) : EventSourceListener() {
} }
override fun onClosed(eventSource: EventSource) { override fun onClosed(eventSource: EventSource) {
if (!StartService.isServiceStarted) if (!StartService.isServiceStarted) {
return return
}
Log.d(TAG, "onClosed: $eventSource") Log.d(TAG, "onClosed: $eventSource")
StartService.newFail(context, eventSource) StartService.newFail(context, eventSource)
RestartWorker.start(context, delay = 0) RestartWorker.start(context, delay = 0)
} }
override fun onFailure(eventSource: EventSource, t: Throwable?, response: Response?) { override fun onFailure(eventSource: EventSource, t: Throwable?, response: Response?) {
if (!StartService.isServiceStarted) if (!StartService.isServiceStarted) {
return return
}
Log.d(TAG, "onFailure") Log.d(TAG, "onFailure")
t?.let { t?.let {
Log.d(TAG, "An error occurred: $t") Log.d(TAG, "An error occurred: $t")
@ -93,12 +88,12 @@ class SSEListener (val context: Context) : EventSourceListener() {
} }
StartService.newFail(context, eventSource) StartService.newFail(context, eventSource)
val delay = when (StartService.nFails) { val delay = when (StartService.nFails) {
1 -> 2 // 2sec 1 -> 2 // 2sec
2 -> 20 // 20sec 2 -> 20 // 20sec
3 -> 60 // 1min 3 -> 60 // 1min
4 -> 300 // 5min 4 -> 300 // 5min
5 -> 600 // 10min 5 -> 600 // 10min
else -> return // else keep the worker with its 16min else -> return // else keep the worker with its 16min
}.toLong() }.toLong()
Log.d(TAG, "Retrying in $delay s") Log.d(TAG, "Retrying in $delay s")
RestartWorker.start(context, delay = delay) RestartWorker.start(context, delay = delay)

View File

@ -3,41 +3,37 @@ package org.unifiedpush.distributor.nextpush.services
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import android.util.Log import android.util.Log
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException
import com.nextcloud.android.sso.helper.SingleAccountHelper import com.nextcloud.android.sso.helper.SingleAccountHelper
import org.unifiedpush.distributor.nextpush.account.AccountUtils.ssoAccount
import org.unifiedpush.distributor.nextpush.account.AccountUtils.nextcloudAppNotInstalledDialog
import android.net.Network
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.NetworkCapabilities
import okhttp3.sse.EventSource import okhttp3.sse.EventSource
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiDestroy import org.unifiedpush.distributor.nextpush.account.AccountUtils.nextcloudAppNotInstalledDialog
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiSync import org.unifiedpush.distributor.nextpush.account.AccountUtils.ssoAccount
import org.unifiedpush.distributor.nextpush.services.NotificationUtils.createForegroundNotification import org.unifiedpush.distributor.nextpush.api.Api.apiDestroy
import org.unifiedpush.distributor.nextpush.api.Api.apiSync
import org.unifiedpush.distributor.nextpush.utils.NOTIFICATION_ID_FOREGROUND
import org.unifiedpush.distributor.nextpush.utils.NotificationUtils.createForegroundNotification
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.lang.Exception import java.lang.Exception
private const val TAG = "StartService" class StartService : Service() {
class StartService: Service(){ companion object : FailureHandler {
companion object: FailureHandler {
private const val WAKE_LOCK_TAG = "NextPush:StartService:lock" private const val WAKE_LOCK_TAG = "NextPush:StartService:lock"
const val SERVICE_STOPPED_ACTION = "org.unifiedpush.distributor.nextpush.services.STOPPED" const val SERVICE_STOPPED_ACTION = "org.unifiedpush.distributor.nextpush.services.STOPPED"
var isServiceStarted = false var isServiceStarted = false
var wakeLock: PowerManager.WakeLock? = null var wakeLock: PowerManager.WakeLock? = null
fun startListener(context: Context){ fun startListener(context: Context) {
if (isServiceStarted && !hasFailed()) return if (isServiceStarted && !hasFailed()) return
Log.d(TAG, "Starting the Listener") Log.d(TAG, "Starting the Listener")
Log.d(TAG, "Service is started: $isServiceStarted") Log.d(TAG, "Service is started: $isServiceStarted")
@ -45,7 +41,7 @@ class StartService: Service(){
val serviceIntent = Intent(context, StartService::class.java) val serviceIntent = Intent(context, StartService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent) context.startForegroundService(serviceIntent)
}else{ } else {
context.startService(serviceIntent) context.startService(serviceIntent)
} }
} }
@ -59,9 +55,9 @@ class StartService: Service(){
return null return null
} }
override fun onCreate(){ override fun onCreate() {
super.onCreate() super.onCreate()
Log.i(TAG,"StartService created") Log.i(TAG, "StartService created")
val notification = createForegroundNotification(this) val notification = createForegroundNotification(this)
startForeground(NOTIFICATION_ID_FOREGROUND, notification) startForeground(NOTIFICATION_ID_FOREGROUND, notification)
} }
@ -124,12 +120,13 @@ class StartService: Service(){
} }
} }
apiSync(this) apiSync()
} }
private var connectivityManager = null as ConnectivityManager? private var connectivityManager = null as ConnectivityManager?
private val networkCallback = object : NetworkCallback() { private val networkCallback = object : NetworkCallback() {
val TAG = this@StartService.TAG
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
Log.d(TAG, "Network is CONNECTED") Log.d(TAG, "Network is CONNECTED")
if (StartService.hasFailed(twice = true, orNeverStart = false)) { if (StartService.hasFailed(twice = true, orNeverStart = false)) {
@ -154,13 +151,12 @@ class StartService: Service(){
Log.d(TAG, "Registering Network Callback") Log.d(TAG, "Registering Network Callback")
try { try {
connectivityManager = ( connectivityManager = (
this.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager this.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
).apply { ).apply {
registerDefaultNetworkCallback(networkCallback) registerDefaultNetworkCallback(networkCallback)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
} }

View File

@ -1,4 +1,4 @@
package org.unifiedpush.distributor.nextpush.services package org.unifiedpush.distributor.nextpush.utils
import android.Manifest import android.Manifest
import android.app.Notification import android.app.Notification
@ -50,10 +50,14 @@ object NotificationUtils {
) )
val builder: Notification.Builder = val builder: Notification.Builder =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Notification.Builder( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context, Notification.Builder(
notificationChannelId context,
) else Notification.Builder(context) notificationChannelId
)
} else {
Notification.Builder(context)
}
return builder return builder
.setContentTitle(context.getString(R.string.app_name)) .setContentTitle(context.getString(R.string.app_name))
@ -67,8 +71,9 @@ object NotificationUtils {
} }
fun createWarningNotification(context: Context) { fun createWarningNotification(context: Context) {
if (warningShown) if (warningShown) {
return return
}
val notificationChannelId = "${context.getString(R.string.app_name)}.Warning" val notificationChannelId = "${context.getString(R.string.app_name)}.Warning"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -97,12 +102,15 @@ object NotificationUtils {
) )
val builder: Notification.Builder = ( val builder: Notification.Builder = (
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Notification.Builder( if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(
context, context,
notificationChannelId notificationChannelId
) )
else Notification.Builder(context) } else {
).setContentTitle(context.getString(R.string.app_name)) Notification.Builder(context)
}
).setContentTitle(context.getString(R.string.app_name))
.setContentText(context.getString(R.string.warning_notif_description)) .setContentText(context.getString(R.string.warning_notif_description))
.setSmallIcon(R.drawable.ic_launcher_notification) .setSmallIcon(R.drawable.ic_launcher_notification)
.setTicker(context.getString(R.string.warning_notif_ticker)) .setTicker(context.getString(R.string.warning_notif_ticker))
@ -128,4 +136,4 @@ object NotificationUtils {
notificationManager.cancel(NOTIFICATION_ID_WARNING) notificationManager.cancel(NOTIFICATION_ID_WARNING)
warningShown = false warningShown = false
} }
} }

View File

@ -0,0 +1,7 @@
package org.unifiedpush.distributor.nextpush.utils
val Any.TAG: String
get() {
val tag = javaClass.simpleName
return if (tag.length <= 23) tag else tag.substring(0, 23)
}

View File

@ -4,11 +4,17 @@ buildscript {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
content {
includeModule 'org.jlleitschuh.gradle', 'ktlint-gradle'
}
}
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.0' classpath 'com.android.tools.build:gradle:7.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jlleitschuh.gradle:ktlint-gradle:11.2.0"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }
@ -25,6 +31,7 @@ allprojects {
} }
} }
} }
apply plugin: "org.jlleitschuh.gradle.ktlint"
} }
task clean(type: Delete) { task clean(type: Delete) {