diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/account/AccountUtils.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/account/AccountUtils.kt index 482ba11..8d5dfbb 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/account/AccountUtils.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/account/AccountUtils.kt @@ -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() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/activities/MainActivity.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/activities/MainActivity.kt index 71c1ae6..bd88129 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/activities/MainActivity.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/activities/MainActivity.kt @@ -12,36 +12,35 @@ 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 + private lateinit var listView: ListView private var showLogout = false override fun onCreate(savedInstanceState: Bundle?) { @@ -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() @@ -132,7 +132,7 @@ class MainActivity : AppCompatActivity() { override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) - if(hasFocus) { + if (hasFocus) { setListView() } } @@ -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) @@ -194,42 +195,46 @@ class MainActivity : AppCompatActivity() { alert.show() } - private fun setListView(){ + private fun setListView() { listView = findViewById(R.id.applications_list) - val db = getDb(this) - val tokenList = db.listTokens().toMutableList() - val appList = emptyArray().toMutableList() - tokenList.forEach { - appList.add(db.getPackageName(it)) + + val tokenList = emptyList().toMutableList() + val appList = emptyList().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 + 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) - 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) { - Log.d(TAG,"Unregistration is finished") + fun(_: AdapterView<*>, _: View, position: Int, _: Long): Boolean { + val alert: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder( + this + ) + alert.setTitle("Unregistering") + alert.setMessage("Are you sure to unregister ${appList[position]} ?") + alert.setPositiveButton("YES") { dialog, _ -> + val connectorToken = tokenList[position] + 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() } - alert.show() - return true + dialog.dismiss() } + alert.setNegativeButton("NO") { dialog, _ -> dialog.dismiss() } + alert.show() + return true + } ) } } diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/Api.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/Api.kt new file mode 100644 index 0000000..ffbba99 --- /dev/null +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/Api.kt @@ -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 { + 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 { + 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 { + 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 { + 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() + } + }) + } + } +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/ApiUtils.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/ApiUtils.kt deleted file mode 100644 index ca850d2..0000000 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/ApiUtils.kt +++ /dev/null @@ -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().toMutableList() - val delQueue = emptyList().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 = HashMap() - parameters["deviceName"] = Build.MODEL - mApi?.createDevice(parameters) - ?.subscribeOn(Schedulers.newThread()) - ?.observeOn(Schedulers.newThread()) - ?.subscribe(object : Observer { - 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 { - 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 { - 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 { - 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() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/ProviderApi.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiProvider.kt similarity index 70% rename from app/src/main/java/org/unifiedpush/distributor/nextpush/api/ProviderApi.kt rename to app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiProvider.kt index 6fa8074..014e895 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/ProviderApi.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiProvider.kt @@ -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 + @Body subscribeMap: Map ): Observable? @DELETE("/device/{deviceId}") @@ -18,7 +19,7 @@ interface ProviderApi { @PUT("/app/") fun createApp( - @Body authorizeMap: MutableMap + @Body authorizeMap: Map ): Observable? @DELETE("/app/{token}") diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiProviderFactory.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiProviderFactory.kt new file mode 100644 index 0000000..7353aba --- /dev/null +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiProviderFactory.kt @@ -0,0 +1,6 @@ +package org.unifiedpush.distributor.nextpush.api.provider + +interface ApiProviderFactory { + fun getProviderAndExecute(block: (ApiProvider) -> Unit) + fun destroyProvider() +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiSSOFactory.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiSSOFactory.kt new file mode 100644 index 0000000..793038e --- /dev/null +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/provider/ApiSSOFactory.kt @@ -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() + } +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/ApiResponse.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/response/ApiResponse.kt similarity index 51% rename from app/src/main/java/org/unifiedpush/distributor/nextpush/api/ApiResponse.kt rename to app/src/main/java/org/unifiedpush/distributor/nextpush/api/response/ApiResponse.kt index d584e3f..119ae23 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/ApiResponse.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/response/ApiResponse.kt @@ -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 = "" ) diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/SSEResponse.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/response/SSEResponse.kt similarity index 57% rename from app/src/main/java/org/unifiedpush/distributor/nextpush/api/SSEResponse.kt rename to app/src/main/java/org/unifiedpush/distributor/nextpush/api/response/SSEResponse.kt index 087fcf1..7bc9972 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/api/SSEResponse.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/api/response/SSEResponse.kt @@ -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 token: String = "", val message: String = "", val keepalive: Int = 900 -) \ No newline at end of file +) diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/MessagingDatabase.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/ConnectionsDatabase.kt similarity index 85% rename from app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/MessagingDatabase.kt rename to app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/ConnectionsDatabase.kt index 2761c35..a5fa46d 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/MessagingDatabase.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/ConnectionsDatabase.kt @@ -13,12 +13,12 @@ private const val FIELD_PACKAGE_NAME = "packageName" private const val FIELD_CONNECTOR_TOKEN = "connectorToken" private const val FIELD_APP_TOKEN = "appToken" private const val CREATE_TABLE_APPS = "CREATE TABLE apps (" + - "$FIELD_PACKAGE_NAME TEXT," + - "$FIELD_CONNECTOR_TOKEN TEXT," + - "$FIELD_APP_TOKEN TEXT," + - "PRIMARY KEY ($FIELD_CONNECTOR_TOKEN));" + "$FIELD_PACKAGE_NAME TEXT," + + "$FIELD_CONNECTOR_TOKEN TEXT," + + "$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) { @@ -51,19 +51,19 @@ class MessagingDatabase(context: Context): val selection = "$FIELD_PACKAGE_NAME = ? AND $FIELD_CONNECTOR_TOKEN = ?" val selectionArgs = arrayOf(packageName, connectorToken) return db.query( - TABLE_APPS, - null, - selection, - selectionArgs, - null, - null, - null + TABLE_APPS, + null, + selection, + selectionArgs, + null, + null, + null ).use { cursor -> (cursor != null && cursor.count > 0) } } - 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 } } @@ -131,11 +131,12 @@ class MessagingDatabase(context: Context): null, null, null - ).use{ cursor -> + ).use { cursor -> generateSequence { if (cursor.moveToNext()) cursor else null } - .mapNotNull{ + .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() } } diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/DistributorUtils.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/Distributor.kt similarity index 65% rename from app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/DistributorUtils.kt rename to app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/Distributor.kt index 90db3ff..d3e7618 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/DistributorUtils.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/distributor/Distributor.kt @@ -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 } -} \ No newline at end of file + + 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) {} + } + } +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/RegisterBroadcastReceiver.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/RegisterBroadcastReceiver.kt index 15010ed..cd3ec3f 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/RegisterBroadcastReceiver.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/RegisterBroadcastReceiver.kt @@ -6,33 +6,33 @@ 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().toMutableList() +private val delQueue = emptyList().toMutableList() class RegisterBroadcastReceiver : BroadcastReceiver() { companion object { 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?) { @@ -41,25 +41,27 @@ class RegisterBroadcastReceiver : BroadcastReceiver() { } wakeLock?.acquire(30000L /*30 secs*/) when (intent?.action) { - ACTION_REGISTER ->{ - Log.i(TAG,"REGISTER") - val connectorToken = intent.getStringExtra(EXTRA_TOKEN)?: "" - val application = intent.getStringExtra(EXTRA_APPLICATION)?: "" - if (application.isBlank()) { - Log.w(TAG,"Trying to register an app without packageName") + ACTION_REGISTER -> { + Log.i(TAG, "REGISTER") + val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: run { + Log.w(TAG, "Trying to register an app without connector token") 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_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") @@ -81,21 +85,18 @@ 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 - } + ACTION_UNREGISTER -> { + Log.i(TAG, "UNREGISTER") + val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: "" + 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() { } } } -} \ No newline at end of file + + private fun delayRemove(list: MutableList, token: String) { + Timer().schedule(1_000L /* 1sec */) { + list.remove(token) + } + } +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/StartReceiver.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/StartReceiver.kt index 919471f..d07b83d 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/StartReceiver.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/receivers/StartReceiver.kt @@ -13,4 +13,3 @@ class StartReceiver : BroadcastReceiver() { } } } - diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/FailureHandler.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/services/FailureHandler.kt index b7ec9c1..05a71ee 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/FailureHandler.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/services/FailureHandler.kt @@ -3,32 +3,33 @@ 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? fun newEventSource(context: Context, eventSource: EventSource) { - Log.d(TAG,"newEvent/Eventsource: $eventSource") + Log.d(TAG, "newEvent/Eventsource: $eventSource") this.eventSource = eventSource nFails = 0 deleteWarningNotification(context) } fun newFail(context: Context, eventSource: EventSource?) { - Log.d(TAG,"newFail/Eventsource: $eventSource") + Log.d(TAG, "newFail/Eventsource: $eventSource") // no eventSource or the last opened 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) + } } } @@ -44,10 +45,10 @@ interface FailureHandler { nFails = 0 eventSource = null } - + fun hasFailed(twice: Boolean = false, orNeverStart: Boolean = true): Boolean { // nFails > 0 to be sure it is not actually restarting return if (orNeverStart) { eventSource == null } else { false } || - nFails > if (twice) { 1 } else { 0 } + nFails > if (twice) { 1 } else { 0 } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/RestartWorker.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/services/RestartWorker.kt index 7fac713..097711d 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/RestartWorker.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/services/RestartWorker.kt @@ -2,18 +2,18 @@ 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 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 { - private const val TAG = "RestartWorker" fun start(context: Context, delay: Long? = null) { val work = PeriodicWorkRequestBuilder(16, TimeUnit.MINUTES) 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.startListener(applicationContext) } - }?:run { + } ?: run { Log.d(TAG, "Restarting") StartService.startListener(applicationContext) } return Result.success() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/SSEListener.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/services/SSEListener.kt index 1d679fe..73731af 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/SSEListener.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/services/SSEListener.kt @@ -2,24 +2,22 @@ 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.* +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 -private const val TAG = "SSEListener" - -class SSEListener (val context: Context) : EventSourceListener() { +class SSEListener(val context: Context) : EventSourceListener() { companion object { - var lastEventDate : Calendar? = null + var lastEventDate: Calendar? = null var keepalive = 900 } @@ -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") @@ -93,12 +88,12 @@ class SSEListener (val context: Context) : EventSourceListener() { } StartService.newFail(context, eventSource) val delay = when (StartService.nFails) { - 1 -> 2 // 2sec - 2 -> 20 // 20sec - 3 -> 60 // 1min - 4 -> 300 // 5min - 5 -> 600 // 10min - else -> return // else keep the worker with its 16min + 1 -> 2 // 2sec + 2 -> 20 // 20sec + 3 -> 60 // 1min + 4 -> 300 // 5min + 5 -> 600 // 10min + else -> return // else keep the worker with its 16min }.toLong() Log.d(TAG, "Retrying in $delay s") RestartWorker.start(context, delay = delay) diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/StartService.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/services/StartService.kt index 4ba6ac0..599543f 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/StartService.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/services/StartService.kt @@ -3,41 +3,37 @@ 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() { -class StartService: Service(){ - - companion object: FailureHandler { + companion object : FailureHandler { private const val WAKE_LOCK_TAG = "NextPush:StartService:lock" const val SERVICE_STOPPED_ACTION = "org.unifiedpush.distributor.nextpush.services.STOPPED" var isServiceStarted = false var wakeLock: PowerManager.WakeLock? = null - fun startListener(context: Context){ + fun startListener(context: Context) { if (isServiceStarted && !hasFailed()) return Log.d(TAG, "Starting the Listener") Log.d(TAG, "Service is started: $isServiceStarted") @@ -45,7 +41,7 @@ class StartService: Service(){ val serviceIntent = Intent(context, StartService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(serviceIntent) - }else{ + } else { context.startService(serviceIntent) } } @@ -59,9 +55,9 @@ class StartService: Service(){ return null } - override fun onCreate(){ + override fun onCreate() { super.onCreate() - Log.i(TAG,"StartService created") + Log.i(TAG, "StartService created") val notification = createForegroundNotification(this) startForeground(NOTIFICATION_ID_FOREGROUND, notification) } @@ -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)) { @@ -154,13 +151,12 @@ class StartService: Service(){ Log.d(TAG, "Registering Network Callback") try { connectivityManager = ( - this.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - ).apply { - registerDefaultNetworkCallback(networkCallback) - } + this.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager + ).apply { + registerDefaultNetworkCallback(networkCallback) + } } catch (e: Exception) { e.printStackTrace() } } } - diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/NotificationUtils.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/utils/NotificationUtils.kt similarity index 89% rename from app/src/main/java/org/unifiedpush/distributor/nextpush/services/NotificationUtils.kt rename to app/src/main/java/org/unifiedpush/distributor/nextpush/utils/NotificationUtils.kt index e3de468..f8a90c7 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/services/NotificationUtils.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/utils/NotificationUtils.kt @@ -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( - context, - notificationChannelId - ) else Notification.Builder(context) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Notification.Builder( + context, + notificationChannelId + ) + } 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,12 +102,15 @@ 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) - ).setContentTitle(context.getString(R.string.app_name)) + } 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) .setTicker(context.getString(R.string.warning_notif_ticker)) @@ -128,4 +136,4 @@ object NotificationUtils { notificationManager.cancel(NOTIFICATION_ID_WARNING) warningShown = false } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/utils/Tag.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/utils/Tag.kt new file mode 100644 index 0000000..ac36fc2 --- /dev/null +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/utils/Tag.kt @@ -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) + } diff --git a/build.gradle b/build.gradle index f5e3b92..7f58c7b 100644 --- a/build.gradle +++ b/build.gradle @@ -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) {