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.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.text.SpannableString
import android.text.method.LinkMovementMethod
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.model.SingleSignOnAccount
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
private const val TAG = "AccountUtils"
import org.unifiedpush.distributor.nextpush.utils.TAG
private const val PREF_NAME = "NextPush"
private const val PREF_DEVICE_ID = "deviceId"
@ -99,7 +98,7 @@ object AccountUtils {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit()
.putString(PREF_DEVICE_ID, deviceId)
.commit()
.apply()
}
fun getDeviceId(context: Context): String? {
@ -111,14 +110,14 @@ object AccountUtils {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit()
.remove(PREF_DEVICE_ID)
.commit()
.apply()
}
fun saveUrl(context: Context, url: String) {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit()
.putString(PREF_URL, url)
.commit()
.apply()
}
fun getUrl(context: Context): String? {
@ -130,6 +129,6 @@ object AccountUtils {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit()
.remove(PREF_URL)
.commit()
.apply()
}
}

View File

@ -12,33 +12,32 @@ import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.*
import android.widget.* // ktlint-disable no-wildcard-imports
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
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.core.view.isVisible
import com.nextcloud.android.sso.AccountImporter
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.account.AccountUtils.connect
import org.unifiedpush.distributor.nextpush.account.AccountUtils.isConnected
import org.unifiedpush.distributor.nextpush.account.AccountUtils.nextcloudAppNotInstalledDialog
import org.unifiedpush.distributor.nextpush.account.AccountUtils.ssoAccount
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiDeleteApp
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiDeleteDevice
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendUnregistered
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.getDb
import org.unifiedpush.distributor.nextpush.services.*
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteDevice
import org.unifiedpush.distributor.nextpush.distributor.Distributor.getDb
import org.unifiedpush.distributor.nextpush.services.RestartWorker
import org.unifiedpush.distributor.nextpush.services.StartService
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.lang.String.format
private const val TAG = "NextPush-MainActivity"
class MainActivity : AppCompatActivity() {
private lateinit var listView: ListView
@ -106,7 +105,8 @@ class MainActivity : AppCompatActivity() {
private fun requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
!= PackageManager.PERMISSION_GRANTED
) {
Log.d(TAG, "Requesting POST_NOTIFICATIONS permission")
registerForActivityResult(
ActivityResultContracts.RequestPermission()
@ -175,7 +175,8 @@ class MainActivity : AppCompatActivity() {
private fun logout() {
val alert: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder(
this)
this
)
alert.setTitle(getString(R.string.logout_alert_title))
alert.setMessage(R.string.logout_alert_content)
alert.setPositiveButton(R.string.ok) { dialog, _ ->
@ -185,7 +186,7 @@ class MainActivity : AppCompatActivity() {
.edit()
.remove("PREF_CURRENT_ACCOUNT_STRING")
.apply()
apiDeleteDevice(this)
deleteDevice(this)
showStart()
finish()
startActivity(intent)
@ -196,34 +197,38 @@ class MainActivity : AppCompatActivity() {
private fun setListView() {
listView = findViewById(R.id.applications_list)
val db = getDb(this)
val tokenList = db.listTokens().toMutableList()
val appList = emptyArray<String>().toMutableList()
tokenList.forEach {
appList.add(db.getPackageName(it))
val tokenList = emptyList<String>().toMutableList()
val appList = emptyList<String>().toMutableList()
getDb(this).let { db ->
db.listTokens().forEach {
tokenList.add(it)
appList.add(db.getPackageName(it) ?: it)
}
}
listView.adapter = ArrayAdapter(
this,
android.R.layout.simple_list_item_1,
appList
)
listView.setOnItemLongClickListener(
fun(_: AdapterView<*>, _: View, position: Int, _: Long): Boolean {
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.setPositiveButton("YES") { dialog, _ ->
val connectorToken = tokenList[position]
sendUnregistered(this, connectorToken)
val database = getDb(this)
val appToken = database.getAppToken(connectorToken)
database.unregisterApp(connectorToken)
apiDeleteApp(this, appToken) {
deleteApp(this, connectorToken) {
Log.d(TAG, "Unregistration is finished")
this@MainActivity.runOnUiThread {
setListView()
}
}
tokenList.removeAt(position)
appList.removeAt(position)
dialog.dismiss()
}
alert.setNegativeButton("NO") { dialog, _ -> dialog.dismiss() }

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 retrofit2.http.PUT
import retrofit2.http.DELETE
import org.unifiedpush.distributor.nextpush.api.response.ApiResponse
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.PUT
import retrofit2.http.Path
interface ProviderApi {
interface ApiProvider {
@PUT("/device/")
fun createDevice(
@Body subscribeMap: MutableMap<String, String>
@Body subscribeMap: Map<String, String>
): Observable<ApiResponse>?
@DELETE("/device/{deviceId}")
@ -18,7 +19,7 @@ interface ProviderApi {
@PUT("/app/")
fun createApp(
@Body authorizeMap: MutableMap<String, String>
@Body authorizeMap: Map<String, String>
): Observable<ApiResponse>?
@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(
val success: Boolean = false,
val deviceId: String = "",
val token: String = "",
val token: String = ""
)

View File

@ -1,4 +1,4 @@
package org.unifiedpush.distributor.nextpush.api
package org.unifiedpush.distributor.nextpush.api.response
data class SSEResponse(
val type: String = "",

View File

@ -18,7 +18,7 @@ private const val CREATE_TABLE_APPS = "CREATE TABLE apps (" +
"$FIELD_APP_TOKEN TEXT," +
"PRIMARY KEY ($FIELD_CONNECTOR_TOKEN));"
class MessagingDatabase(context: Context):
class ConnectionsDatabase(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
@ -63,7 +63,7 @@ class MessagingDatabase(context: Context):
}
}
fun getPackageName(connectorToken: String): String {
fun getPackageName(connectorToken: String): String? {
val db = readableDatabase
val projection = arrayOf(FIELD_PACKAGE_NAME)
val selection = "$FIELD_CONNECTOR_TOKEN = ?"
@ -78,11 +78,11 @@ class MessagingDatabase(context: Context):
null
).use { cursor ->
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 projection = arrayOf(FIELD_APP_TOKEN)
val selection = "$FIELD_CONNECTOR_TOKEN = ?"
@ -97,11 +97,11 @@ class MessagingDatabase(context: Context):
null
).use { cursor ->
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 projection = arrayOf(FIELD_CONNECTOR_TOKEN)
val selection = "$FIELD_APP_TOKEN = ?"
@ -116,7 +116,7 @@ class MessagingDatabase(context: Context):
null
).use { cursor ->
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
}
}
@ -135,7 +135,8 @@ class MessagingDatabase(context: Context):
generateSequence { if (cursor.moveToNext()) cursor else null }
.mapNotNull {
val column = cursor.getColumnIndex(FIELD_CONNECTOR_TOKEN)
if (column >= 0) it.getString(column) else null }
if (column >= 0) it.getString(column) else null
}
.toList()
}
}

View File

@ -4,35 +4,35 @@ import android.content.Context
import android.content.Intent
import android.util.Log
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
*/
private const val TAG = "DistributorUtils"
object DistributorUtils {
object Distributor {
const val TOKEN_NEW = "token_new"
const val TOKEN_REGISTERED_OK = "token_registered_ok"
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) {
db = MessagingDatabase(context)
db = ConnectionsDatabase(context)
}
return db
}
fun sendMessage(context: Context, appToken: String, message: ByteArray) {
val db = getDb(context)
val connectorToken = db.getConnectorToken(appToken)
val connectorToken = db.getConnectorToken(appToken) ?: return
val application = getApp(context, connectorToken)
if (application.isNullOrBlank()) {
return
}
val broadcastIntent = Intent()
broadcastIntent.`package` = application
broadcastIntent.action = ACTION_MESSAGE
@ -73,7 +73,7 @@ object DistributorUtils {
context.sendBroadcast(broadcastIntent)
}
fun sendUnregistered(context: Context, connectorToken: String) {
private fun sendUnregistered(context: Context, connectorToken: String) {
val application = getApp(context, connectorToken)
if (application.isNullOrBlank()) {
return
@ -88,10 +88,7 @@ object DistributorUtils {
private fun getApp(context: Context, connectorToken: String): String? {
val db = getDb(context)
val app = db.getPackageName(connectorToken)
return app.ifBlank {
Log.w(TAG, "No app found for this token")
null
}
return app
}
private fun getEndpoint(context: Context, connectorToken: String): String {
@ -110,4 +107,41 @@ object DistributorUtils {
}
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,27 +6,27 @@ import android.content.Intent
import android.os.PowerManager
import android.util.Log
import org.unifiedpush.distributor.nextpush.account.AccountUtils.isConnected
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiCreateApp
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiDeleteApp
import org.unifiedpush.distributor.nextpush.api.ApiUtils.createQueue
import org.unifiedpush.distributor.nextpush.api.ApiUtils.delQueue
import org.unifiedpush.distributor.nextpush.distributor.*
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.TOKEN_NEW
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.TOKEN_NOK
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.TOKEN_REGISTERED_OK
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.checkToken
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.getDb
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 org.unifiedpush.distributor.nextpush.distributor.* // ktlint-disable no-wildcard-imports
import org.unifiedpush.distributor.nextpush.distributor.Distributor.TOKEN_NEW
import org.unifiedpush.distributor.nextpush.distributor.Distributor.TOKEN_NOK
import org.unifiedpush.distributor.nextpush.distributor.Distributor.TOKEN_REGISTERED_OK
import org.unifiedpush.distributor.nextpush.distributor.Distributor.checkToken
import org.unifiedpush.distributor.nextpush.distributor.Distributor.createApp
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
import org.unifiedpush.distributor.nextpush.distributor.Distributor.getDb
import org.unifiedpush.distributor.nextpush.distributor.Distributor.sendEndpoint
import org.unifiedpush.distributor.nextpush.distributor.Distributor.sendRegistrationFailed
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.lang.Exception
import java.util.Timer
import kotlin.concurrent.schedule
/**
* 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() {
@ -43,23 +43,25 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
when (intent?.action) {
ACTION_REGISTER -> {
Log.i(TAG, "REGISTER")
val connectorToken = intent.getStringExtra(EXTRA_TOKEN)?: ""
val application = intent.getStringExtra(EXTRA_APPLICATION)?: ""
if (application.isBlank()) {
val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: run {
Log.w(TAG, "Trying to register an app without connector token")
return
}
val application = intent.getStringExtra(EXTRA_APPLICATION) ?: run {
Log.w(TAG, "Trying to register an app without packageName")
return
}
when (checkToken(context, connectorToken, application)) {
when (checkToken(context.applicationContext, connectorToken, application)) {
TOKEN_REGISTERED_OK -> sendEndpoint(context.applicationContext, connectorToken)
TOKEN_NOK -> sendRegistrationFailed(
context,
context.applicationContext,
application,
connectorToken
)
TOKEN_NEW -> {
if (!isConnected(context, showDialog = false)) {
if (!isConnected(context.applicationContext, showDialog = false)) {
sendRegistrationFailed(
context,
context.applicationContext,
application,
connectorToken,
message = "NextPush is not connected"
@ -68,12 +70,14 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
}
if (connectorToken !in createQueue) {
createQueue.add(connectorToken)
apiCreateApp(
delayRemove(createQueue, connectorToken)
createApp(
context.applicationContext,
application,
connectorToken
) {
sendEndpoint(context.applicationContext, connectorToken)
createQueue.remove(connectorToken)
}
} else {
Log.d(TAG, "Already registering this token")
@ -84,18 +88,15 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
ACTION_UNREGISTER -> {
Log.i(TAG, "UNREGISTER")
val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: ""
val application = getDb(context).getPackageName(connectorToken)
if (application.isBlank()) {
return
}
getDb(context.applicationContext).getPackageName(connectorToken) ?: return
if (connectorToken !in delQueue) {
delQueue.add(connectorToken)
sendUnregistered(context.applicationContext, connectorToken)
delayRemove(delQueue, connectorToken)
try {
apiDeleteApp(context.applicationContext, connectorToken) {
val db = getDb(context.applicationContext)
db.unregisterApp(connectorToken)
deleteApp(context.applicationContext, connectorToken) {
Log.d(TAG, "Unregistration is finished")
delQueue.remove(connectorToken)
}
} catch (e: Exception) {
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,14 +3,14 @@ package org.unifiedpush.distributor.nextpush.services
import android.content.Context
import android.util.Log
import okhttp3.sse.EventSource
import org.unifiedpush.distributor.nextpush.services.NotificationUtils.createWarningNotification
import org.unifiedpush.distributor.nextpush.services.NotificationUtils.deleteWarningNotification
private const val TAG = "FailureHandler"
import org.unifiedpush.distributor.nextpush.utils.NotificationUtils.createWarningNotification
import org.unifiedpush.distributor.nextpush.utils.NotificationUtils.deleteWarningNotification
import org.unifiedpush.distributor.nextpush.utils.TAG
interface FailureHandler {
var nFails: Int
// This is the last eventSource opened
var eventSource: EventSource?
@ -27,10 +27,11 @@ interface FailureHandler {
if (this.eventSource == null || this.eventSource == eventSource) {
Log.d(TAG, "EventSource is known or null")
nFails++
if (hasFailed(twice = true))
if (hasFailed(twice = true)) {
createWarningNotification(context)
}
}
}
fun setMaxFails(context: Context) {
// We set nFails to max to not restart the worker

View File

@ -2,9 +2,10 @@ package org.unifiedpush.distributor.nextpush.services
import android.content.Context
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.lastEventDate
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.util.*
import java.util.concurrent.TimeUnit
@ -13,7 +14,6 @@ private const val UNIQUE_WORK_TAG = "nextpush::RestartWorker::unique"
class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
companion object {
private const val TAG = "RestartWorker"
fun start(context: Context, delay: Long? = null) {
val work = PeriodicWorkRequestBuilder<RestartWorker>(16, TimeUnit.MINUTES)
if (delay != null) {

View File

@ -2,19 +2,17 @@ package org.unifiedpush.distributor.nextpush.services
import android.content.Context
import android.util.Base64
import okhttp3.sse.EventSourceListener
import okhttp3.sse.EventSource
import android.util.Log
import okhttp3.Response
import java.lang.Exception
import com.google.gson.Gson
import org.unifiedpush.distributor.nextpush.api.SSEResponse
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.getDb
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendMessage
import org.unifiedpush.distributor.nextpush.distributor.DistributorUtils.sendUnregistered
import java.util.*
private const val TAG = "SSEListener"
import okhttp3.Response
import okhttp3.sse.EventSource
import okhttp3.sse.EventSourceListener
import org.unifiedpush.distributor.nextpush.api.response.SSEResponse
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
class SSEListener(val context: Context) : EventSourceListener() {
@ -58,12 +56,7 @@ class SSEListener (val context: Context) : EventSourceListener() {
}
"deleteApp" -> {
val message = Gson().fromJson(data, SSEResponse::class.java)
val db = getDb(context.applicationContext)
val connectorToken = db.getConnectorToken(message.token)
if (connectorToken.isEmpty())
return
sendUnregistered(context.applicationContext, connectorToken)
db.unregisterApp(connectorToken)
deleteAppFromAppToken(context, message.token)
}
}
StartService.wakeLock?.let {
@ -74,16 +67,18 @@ class SSEListener (val context: Context) : EventSourceListener() {
}
override fun onClosed(eventSource: EventSource) {
if (!StartService.isServiceStarted)
if (!StartService.isServiceStarted) {
return
}
Log.d(TAG, "onClosed: $eventSource")
StartService.newFail(context, eventSource)
RestartWorker.start(context, delay = 0)
}
override fun onFailure(eventSource: EventSource, t: Throwable?, response: Response?) {
if (!StartService.isServiceStarted)
if (!StartService.isServiceStarted) {
return
}
Log.d(TAG, "onFailure")
t?.let {
Log.d(TAG, "An error occurred: $t")

View File

@ -3,31 +3,27 @@ package org.unifiedpush.distributor.nextpush.services
import android.app.Service
import android.content.Context
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.IBinder
import android.os.PowerManager
import android.util.Log
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException
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 org.unifiedpush.distributor.nextpush.api.ApiUtils.apiDestroy
import org.unifiedpush.distributor.nextpush.api.ApiUtils.apiSync
import org.unifiedpush.distributor.nextpush.services.NotificationUtils.createForegroundNotification
import org.unifiedpush.distributor.nextpush.account.AccountUtils.nextcloudAppNotInstalledDialog
import org.unifiedpush.distributor.nextpush.account.AccountUtils.ssoAccount
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
private const val TAG = "StartService"
class StartService : Service() {
companion object : FailureHandler {
@ -124,12 +120,13 @@ class StartService: Service(){
}
}
apiSync(this)
apiSync()
}
private var connectivityManager = null as ConnectivityManager?
private val networkCallback = object : NetworkCallback() {
val TAG = this@StartService.TAG
override fun onAvailable(network: Network) {
Log.d(TAG, "Network is CONNECTED")
if (StartService.hasFailed(twice = true, orNeverStart = false)) {
@ -163,4 +160,3 @@ class StartService: Service(){
}
}
}

View File

@ -1,4 +1,4 @@
package org.unifiedpush.distributor.nextpush.services
package org.unifiedpush.distributor.nextpush.utils
import android.Manifest
import android.app.Notification
@ -50,10 +50,14 @@ object NotificationUtils {
)
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,
notificationChannelId
) else Notification.Builder(context)
)
} else {
Notification.Builder(context)
}
return builder
.setContentTitle(context.getString(R.string.app_name))
@ -67,8 +71,9 @@ object NotificationUtils {
}
fun createWarningNotification(context: Context) {
if (warningShown)
if (warningShown) {
return
}
val notificationChannelId = "${context.getString(R.string.app_name)}.Warning"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -97,11 +102,14 @@ object NotificationUtils {
)
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,
notificationChannelId
)
else Notification.Builder(context)
} else {
Notification.Builder(context)
}
).setContentTitle(context.getString(R.string.app_name))
.setContentText(context.getString(R.string.warning_notif_description))
.setSmallIcon(R.drawable.ic_launcher_notification)

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 {
google()
mavenCentral()
maven {
url "https://plugins.gradle.org/m2/"
content {
includeModule 'org.jlleitschuh.gradle', 'ktlint-gradle'
}
}
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.0'
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
// in the individual module build.gradle files
}
@ -25,6 +31,7 @@ allprojects {
}
}
}
apply plugin: "org.jlleitschuh.gradle.ktlint"
}
task clean(type: Delete) {