diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 30e04639bf227a9af7c83a4dbc1829bca94a65c0..ae77090bf1f6462eee0eaf2464c2ab173cd0df75 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -189,8 +189,10 @@
-
+
@@ -341,7 +343,7 @@
android:name="android.content.SyncAdapter"
android:resource="@xml/eelo_sync_tasks" />
-
+
-
+
.
+ */
+package at.bitfire.davdroid
+
+import android.accounts.AccountManager
+import android.accounts.AccountManagerFuture
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION
+import android.os.Build.VERSION_CODES
+import android.os.Bundle
+import android.os.IBinder
+import at.bitfire.davdroid.log.Logger
+class ReLoginWithOidcService: Service() {
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ val context = this.applicationContext
+
+ val accountManager = AccountManager.get(context)
+ val eAccountType = context.getString(R.string.eelo_account_type)
+
+ val accountRemoved = removeAccount(eAccountType, accountManager)
+
+ if (accountRemoved) {
+ openEAccountLoginForm(accountManager, eAccountType, context)
+ } else {
+ stopSelf()
+ }
+
+ return START_STICKY
+ }
+
+ private fun removeAccount(accountType: String, accountManager: AccountManager): Boolean {
+ val account = accountManager.getAccountsByType(accountType).firstOrNull()
+
+ return account?.let {
+ accountManager.removeAccountExplicitly(it)
+ } ?: false
+ }
+
+ private fun openEAccountLoginForm(accountManager: AccountManager, eAccountType: String, context: Context) {
+ accountManager.addAccount(eAccountType,
+ null,
+ null,
+ null,
+ null,
+ { future ->
+ val intent = getIntentFromFuture(future)
+
+ if (intent == null) {
+ stopSelf()
+ return@addAccount
+ }
+
+ intent.apply {
+ flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
+ }
+
+ val pendingIntent = PendingIntent.getActivity(context,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+
+ try {
+ pendingIntent.send()
+ } catch (exception: Exception){
+ Logger.log.warning("ReLogin: Can't start login activity: ${exception.message}")
+ }
+
+ stopSelf()
+ },
+ null)
+ }
+
+ private fun getIntentFromFuture(future: AccountManagerFuture): Intent? {
+ val bundle = future.result
+
+ return try {
+ if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
+ bundle?.getParcelable(AccountManager.KEY_INTENT, Intent::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ bundle?.getParcelable(AccountManager.KEY_INTENT)
+ }
+ } catch (exception: Exception) {
+ Logger.log.warning("${exception.javaClass}: can't add account: ${exception.message}")
+ null
+ }
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+}
diff --git a/app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt b/app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt
index d769750815e6293becf5334a2a931129bcf60fb6..86e6fa1eba3889af7bb8e128b370b21558bd4d93 100644
--- a/app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt
+++ b/app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt
@@ -4,12 +4,30 @@
package at.bitfire.davdroid.receiver
+import android.Manifest.permission.POST_NOTIFICATIONS
+import android.accounts.Account
+import android.accounts.AccountManager
+import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.os.Build.VERSION
+import android.os.Build.VERSION_CODES
+import androidx.core.app.NotificationManagerCompat
+import at.bitfire.davdroid.R
+import at.bitfire.davdroid.ReLoginWithOidcService
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.syncadapter.AccountUtils
+import at.bitfire.davdroid.ui.NotificationUtils
+import at.bitfire.davdroid.ui.NotificationUtils.CHANNEL_GENERAL
+import at.bitfire.davdroid.ui.NotificationUtils.NOTIFY_SWITCH_TO_OPENID
+import at.bitfire.davdroid.ui.NotificationUtils.notifyIfPossible
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
/**
* There are circumstances when Android drops automatic sync of accounts and resets them
@@ -20,14 +38,82 @@ import at.bitfire.davdroid.syncadapter.AccountUtils
*/
class BootCompletedReceiver: BroadcastReceiver() {
+ private val job = SupervisorJob()
+ private val scope = CoroutineScope(job + Dispatchers.IO)
+
override fun onReceive(context: Context, intent: Intent) {
Logger.log.info("Device has been rebooted; checking sync intervals etc.")
- // sync intervals are checked in App.onCreate()
- AccountUtils.getMainAccounts(context)
- .forEach {
- val accountSettings = AccountSettings(context, it)
- accountSettings.initSync()
+
+ val pendingResult = goAsync()
+ scope.launch {
+ try {
+ val eAccountType = context.getString(R.string.eelo_account_type)
+ val accountManager = AccountManager.get(context)
+ val canNotify = canNotify(context)
+
+ AccountUtils.getMainAccounts(context)
+ .forEach {
+ val needReLogin =
+ eAccountType == it.type && !isLoggedWithOpenId(it, accountManager)
+
+ if (canNotify && needReLogin) {
+ notifySwitchToOpenId(it, context)
+ } else { // sync intervals are checked in App.onCreate()
+ val accountSettings = AccountSettings(context, it)
+ accountSettings.initSync()
+ }
+ }
+ } finally {
+ pendingResult.finish()
+ job.cancel()
}
+ }
}
-}
\ No newline at end of file
+ private fun isLoggedWithOpenId(account: Account, accountManager: AccountManager): Boolean {
+ val hasAuthStateData = accountManager.getUserData(account, AccountSettings.KEY_AUTH_STATE) != null
+ val isPasswordNull = accountManager.getPassword(account).isNullOrEmpty()
+ return isPasswordNull && hasAuthStateData
+ }
+
+ private fun notifySwitchToOpenId(account: Account, context: Context) {
+ val title = context.getString(R.string.notification_account_title, account.name)
+ val contentText = context.getString(R.string.notification_switch_to_openId_text)
+ val intent = generateReLoginIntent(context)
+
+ val notification = NotificationUtils.newBuilder(context, CHANNEL_GENERAL)
+ .setContentTitle(title)
+ .setContentText(contentText)
+ .setSmallIcon(R.drawable.ic_info)
+ .setContentIntent(intent)
+ .setAutoCancel(true)
+ .build()
+
+ val notificationManager = NotificationManagerCompat.from(context)
+ notificationManager.notifyIfPossible(NOTIFICATION_TAG, NOTIFY_SWITCH_TO_OPENID, notification)
+ }
+
+ private fun generateReLoginIntent(context: Context): PendingIntent {
+ val reLoginIntent = Intent(context, ReLoginWithOidcService::class.java)
+
+ return PendingIntent.getService(context,
+ 0,
+ reLoginIntent,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+ }
+
+ private fun canNotify(context: Context): Boolean {
+ if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
+ val permission = POST_NOTIFICATIONS
+ if (context.checkSelfPermission(permission) != PERMISSION_GRANTED) {
+ Logger.log.warning("Missing notification permission")
+ return false
+ }
+ }
+ return true
+ }
+
+ companion object {
+ private const val NOTIFICATION_TAG = "SWITCH_TO_OPENID"
+ }
+}
diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt
index e41544d538bb2a6f08bda820781ad4d7361c0c77..143b8329d5ecb5e63c4ca1024bb08ac4d8a2f1ca 100644
--- a/app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt
+++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt
@@ -34,6 +34,7 @@ object NotificationUtils {
const val NOTIFY_SYNC_EXPEDITED = 14
const val NOTIFY_TASKS_PROVIDER_TOO_OLD = 20
const val NOTIFY_PERMISSIONS = 21
+ const val NOTIFY_SWITCH_TO_OPENID = 22
const val NOTIFY_LICENSE = 100
@@ -80,7 +81,6 @@ object NotificationUtils {
NotificationCompat.Builder(context, channel)
.setColor(ResourcesCompat.getColor(context.resources, R.color.primaryColor, null))
-
fun NotificationManagerCompat.notifyIfPossible(tag: String?, id: Int, notification: Notification) {
try {
notify(tag, id, notification)
@@ -92,4 +92,4 @@ object NotificationUtils {
fun NotificationManagerCompat.notifyIfPossible(id: Int, notification: Notification) =
notifyIfPossible(null, id, notification)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 47579443b9ea55469724ce5d64711d1afbc71c1f..60cb62446672c2dbe8857f0b8eb350adb88cec9e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -53,6 +53,8 @@
Non-fatal synchronization problems like certain invalid files
Network and I/O errors
Timeouts, connection problems, etc. (often temporary)
+ Your account %1$s
+ A new login service for a better experience is available. Tap the notification to start using it.
Your data. Your choice.