diff --git a/app/build.gradle b/app/build.gradle
index 0904d4364eacd09f6c8c0367d6eb9880d005981a..f23b9754c986316384437408cf60270e350acc1d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -30,7 +30,7 @@ android {
setProperty "archivesBaseName", "davx5-ose-" + getVersionName()
- minSdkVersion 24 // Android 7.0
+ minSdkVersion 26 // Android 9.0
targetSdkVersion 33 // Android 13
buildConfigField "String", "userAgent", "\"AccountManager\""
@@ -156,6 +156,7 @@ configurations {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
+ implementation 'androidx.activity:activity:1.9.3'
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 30e04639bf227a9af7c83a4dbc1829bca94a65c0..3a69080a3c91fd3158a6c87e65034094c9438585 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,118 +1,170 @@
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:installLocation="internalOnly">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
+ - fine location (Android 10)
+ -->
+
+
+
+
+
+
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme"
+ tools:ignore="UnusedAttribute">
+
+
-
+ tools:node="remove">
-
+
-
-
-
-
-
-
+
+
+
-
+
+
+
+
+ android:theme="@style/AppTheme.NoActionBar">
-
-
+
+
+
-
-
+ android:parentActivityName=".ui.AccountsActivity"
+ android:theme="@style/AppTheme.NoActionBar" />
+ android:parentActivityName=".ui.AccountsActivity">
-
+
+
+ android:label="@string/debug_info_title"
+ android:parentActivityName=".ui.AppSettingsActivity">
-
+
+
@@ -124,43 +176,43 @@
android:name=".ui.TasksActivity"
android:label="@string/intro_tasks_title"
android:parentActivityName=".ui.AppSettingsActivity" />
-
+ android:exported="true"
+ android:label="@string/login_title"
+ android:parentActivityName=".ui.AccountsActivity">
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+ android:theme="@style/AppTheme.NoActionBar">
-
+ android:parentActivityName=".ui.AccountsActivity" />
-
-
-
-
+
-
+
+
+ android:resource="@xml/account_authenticator" />
-
+
+
+ android:resource="@xml/sync_calendars" />
-
+
+
+ android:resource="@xml/sync_notes" />
-
+
+
+ android:resource="@xml/sync_opentasks" />
-
+
+
+ android:resource="@xml/sync_tasks_org" />
+
-
-
-
+
+ android:exported="true">
-
+
+ android:resource="@xml/account_authenticator_address_book" />
+
+ android:label="@string/address_books_authority_title" />
+
-
+
+ android:resource="@xml/sync_address_books" />
-
+
+ android:resource="@xml/sync_contacts" />
-
-
-
+ android:resource="@xml/contacts" />
+
@@ -313,22 +366,20 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/eelo_account_authenticator" />
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
+
@@ -425,7 +468,6 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/account_authenticator_eelo_address_book" />
-
-
-
+
-
-
-
+
@@ -469,8 +508,7 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/google_account_authenticator" />
-
-
-
-
+
-
-
-
+
@@ -510,7 +545,6 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/account_authenticator_google_address_book" />
-
-
-
+
-
-
-
-
-
+
@@ -568,7 +598,6 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/yahoo_account_authenticator" />
-
-
-
-
-
-
+
@@ -623,7 +648,6 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/account_authenticator_yahoo_address_book" />
-
-
-
-
-
-
+
+
+ android:exported="false"
+ android:grantUriPermissions="true">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/at/bitfire/davdroid/ReLoginWithOidcActivity.kt b/app/src/main/java/at/bitfire/davdroid/ReLoginWithOidcActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..607b1dcc9cd349c38f6c8ce80dfae5246d04f1c0
--- /dev/null
+++ b/app/src/main/java/at/bitfire/davdroid/ReLoginWithOidcActivity.kt
@@ -0,0 +1,105 @@
+/***************************************************************************************************
+ * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
+ **************************************************************************************************/
+
+package at.bitfire.davdroid
+
+import android.accounts.Account
+import android.accounts.AccountManager
+import android.accounts.AccountManager.KEY_ACCOUNT_NAME
+import android.accounts.AccountManager.KEY_BOOLEAN_RESULT
+import android.accounts.AccountManager.KEY_INTENT
+import android.accounts.AccountManagerFuture
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import androidx.appcompat.app.AppCompatActivity
+import at.bitfire.davdroid.log.Logger
+
+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)
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ val accountName = intent?.extras?.getString(KEY_ACCOUNT_NAME, null) ?: run {
+ finishAndRemoveTask()
+ return
+ }
+
+ val account = getAccount(accountName)
+
+ if (isAccountRemoved && account == null) {
+ loginAccount(eAccountType)
+ } else if (!isAccountRemoved && account != null) {
+ logoutAccount(account)
+ } else {
+ finishAndRemoveTask()
+ }
+ }
+
+ private fun getAccount(accountName: String) : Account? {
+ val eAccounts = accountManager.getAccountsByType(eAccountType)
+ return eAccounts.firstOrNull { it.name == accountName }
+ }
+
+ private fun logoutAccount(account: Account)
+ {
+ val callback: (AliasFuture) -> Unit = { future ->
+
+ val success = try {
+ future.result.getBoolean(KEY_BOOLEAN_RESULT)
+ } catch (exception: Exception) {
+ Logger.log.warning("Cannot remove account: ${account.name} : ${exception.message}")
+ false
+ }
+
+ if (success) {
+ isAccountRemoved = true
+ Logger.log.info("Account removed. Now will sign in again with OIDC")
+ loginAccount(eAccountType)
+ } else {
+ Handler(Looper.getMainLooper()).post {
+ finishAndRemoveTask()
+ }
+ }
+ }
+ accountManager.removeAccount(account, this, callback, null)
+ }
+
+ private fun loginAccount(accountType: String) {
+
+ val callback: (AliasFuture) -> Unit = { future ->
+ try {
+ val accountName = future.result?.getString(KEY_ACCOUNT_NAME)
+ if (accountName == null) {
+ Logger.log.warning("No account re-added")
+ }
+ } catch (exception: Exception) {
+ Logger.log.warning("${exception.javaClass}: can't add account: ${exception.message}")
+ }
+
+ Handler(Looper.getMainLooper()).post {
+ finishAndRemoveTask()
+ }
+ }
+
+ accountManager.addAccount(accountType,null, null, null, this, callback, null)
+ }
+
+ companion object {
+ private var isAccountRemoved = false
+ }
+}
+
+typealias AliasFuture = AccountManagerFuture
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..67b9e0ecf20574d06e1463bc28d75d0b2bc6b544 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,22 @@
package at.bitfire.davdroid.receiver
+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 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
/**
* There are circumstances when Android drops automatic sync of accounts and resets them
@@ -22,12 +32,56 @@ class BootCompletedReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Logger.log.info("Device has been rebooted; checking sync intervals etc.")
+
+ val eAccountType = context.getString(R.string.eelo_account_type)
+ val accountManager = AccountManager.get(context)
+
// sync intervals are checked in App.onCreate()
AccountUtils.getMainAccounts(context)
.forEach {
- val accountSettings = AccountSettings(context, it)
- accountSettings.initSync()
+ if (eAccountType == it.type && !isLoggedWithOpenId(it, accountManager)) {
+ notifySwitchToOpenId(it, context)
+ } else {
+ val accountSettings = AccountSettings(context, it)
+ accountSettings.initSync()
+ }
}
}
-}
\ 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 notifTag = "Switch to openID"
+ val notifTitle = context.getString(R.string.notification_account_title, account.name)
+ val notifText = context.getString(R.string.notification_switch_to_openId_text)
+ val notifIntent = generateLogoutIntent(account.name, context)
+
+ val notification = NotificationUtils.newBuilder(context, CHANNEL_GENERAL)
+ .setContentTitle(notifTitle)
+ .setContentText(notifText)
+ .setSmallIcon(R.drawable.ic_info)
+ .setContentIntent(notifIntent)
+ .setAutoCancel(true)
+ .build()
+
+ val notificationManager = NotificationManagerCompat.from(context)
+ notificationManager.notifyIfPossible(notifTag, NOTIFY_SWITCH_TO_OPENID, notification)
+ }
+
+ private fun generateLogoutIntent(accountName: String, context: Context): PendingIntent {
+ val reLoginIntent = Intent(context, ReLoginWithOidcActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName)
+ }
+
+ return PendingIntent.getActivity(context,
+ 0,
+ reLoginIntent,
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT or
+ PendingIntent.FLAG_ONE_SHOT)
+ }
+}
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..4e6f04fe5598b3aafe9c1bf16b487d5bb33f4405 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
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 47579443b9ea55469724ce5d64711d1afbc71c1f..e08b3c0048f040376e776c642aadedb5b5867229 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -43,6 +43,7 @@
Database corrupted
All accounts have been removed locally.
+
Debugging
Other important messages
Low-priority status messages
@@ -53,6 +54,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.
diff --git a/build.gradle b/build.gradle
index e969706ab4a9dd19c37b22e1bac029d0e9bb1092..22f24469ede8926b369f1d9947eb4ee50da827a9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -43,6 +43,7 @@ buildscript {
plugins {
// see https://github.com/google/ksp/releases for version numbers
id 'com.google.devtools.ksp' version '1.9.10-1.0.13' apply false
+ id 'org.jetbrains.kotlin.android' version '1.9.10' apply false
}
allprojects {