diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 30e04639bf227a9af7c83a4dbc1829bca94a65c0..4628744db850e390cacaf5fdb775f7e5ff45bf67 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -189,7 +189,9 @@
-
+
-
+
-
+
.
+ */
+package at.bitfire.davdroid
+
+import android.accounts.AccountManager
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import at.bitfire.davdroid.log.Logger
+import at.bitfire.davdroid.ui.setup.EeloAuthenticatorFragment
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class ReLoginWithOidcActivity: AppCompatActivity() {
+
+ private lateinit var eAccountType: String
+ private lateinit var accountManager: AccountManager
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ eAccountType = applicationContext.getString(R.string.eelo_account_type)
+ accountManager = AccountManager.get(this)
+ setContentView(R.layout.activity_create_account)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ if (removeAccount()) {
+ supportFragmentManager.beginTransaction()
+ .replace(android.R.id.content, EeloAuthenticatorFragment())
+ .commit()
+ } else {
+ Logger.log.info("Vincent: Cannot remove account")
+ finishAndRemoveTask()
+ }
+ }
+
+ private fun removeAccount(): Boolean {
+ Logger.log.info("Vincent: try to remove account")
+ val account = accountManager.getAccountsByType(eAccountType).firstOrNull()
+
+ return account?.let {
+ accountManager.removeAccountExplicitly(it)
+ } ?: false
+ }
+}
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..474404682a323a880e244aa62e6c7f5399d6db90 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,33 @@
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.ReLoginWithOidcActivity
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 at.bitfire.davdroid.ui.setup.LoginActivity.Companion.ACCOUNT_TYPE
+import at.bitfire.davdroid.ui.setup.LoginActivity.Companion.EXTRA_USERNAME
+import at.bitfire.davdroid.ui.setup.LoginActivity.Companion.USERNAME_HINT
+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 +41,87 @@ 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(account, 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(account: Account, context: Context): PendingIntent {
+ val reLoginIntent = Intent(context, ReLoginWithOidcActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ putExtra(ACCOUNT_TYPE, account.type)
+ putExtra(EXTRA_USERNAME, account.name)
+ putExtra(USERNAME_HINT, account.name)
+ }
+
+ return PendingIntent.getActivity(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.