Loading app/src/main/AndroidManifest.xml +5 −3 Original line number Diff line number Diff line Loading @@ -189,8 +189,10 @@ <activity android:name="com.owncloud.android.ui.activity.SsoGrantPermissionActivity" android:exported="true" /> <!-- account type "DAVx⁵" --> <service android:name=".ReLoginWithOidcService" android:exported="false"/> <service android:name=".syncadapter.AccountAuthenticatorService" android:exported="false"> Loading app/src/main/kotlin/at/bitfire/davdroid/ReLoginWithOidcService.kt 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2024 * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package at.bitfire.davdroid import android.accounts.AccountManager import android.accounts.AccountManagerFuture 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 androidx.core.content.ContextCompat import at.bitfire.davdroid.log.Logger import org.apache.commons.lang3.NotImplementedException 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) } 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) intent?.let { ContextCompat.startActivity(context, it, null) } }, null) } private fun getIntentFromFuture(future: AccountManagerFuture<Bundle>): 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? { throw NotImplementedException() } } app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt +55 −4 Original line number Diff line number Diff line Loading @@ -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.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 /** * There are circumstances when Android drops automatic sync of accounts and resets them Loading @@ -22,12 +32,53 @@ class BootCompletedReceiver: BroadcastReceiver() { 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() val eAccountType = context.getString(R.string.eelo_account_type) val accountManager = AccountManager.get(context) AccountUtils.getMainAccounts(context) .forEach { if (eAccountType == it.type && !isLoggedWithOpenId(it, accountManager)) { notifySwitchToOpenId(it, context) } else { // sync intervals are checked in App.onCreate() val accountSettings = AccountSettings(context, it) accountSettings.initSync() } } } 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 tag = "Switch to openID" 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(tag, NOTIFY_SWITCH_TO_OPENID, notification) } private fun generateReLoginIntent(context: Context): PendingIntent { val reLoginIntent = Intent(context, ReLoginWithOidcService::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } return PendingIntent.getActivity(context, 0, reLoginIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) } } app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading app/src/main/res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -53,6 +53,8 @@ <string name="notification_channel_sync_warnings_desc">Non-fatal synchronization problems like certain invalid files</string> <string name="notification_channel_sync_io_errors">Network and I/O errors</string> <string name="notification_channel_sync_io_errors_desc">Timeouts, connection problems, etc. (often temporary)</string> <string name="notification_account_title">Your account %1$s</string> <string name="notification_switch_to_openId_text">A new login service for a better experience is available. Tap the notification to start using it.</string> <!-- IntroActivity --> <string name="intro_slogan1">Your data. Your choice.</string> Loading Loading
app/src/main/AndroidManifest.xml +5 −3 Original line number Diff line number Diff line Loading @@ -189,8 +189,10 @@ <activity android:name="com.owncloud.android.ui.activity.SsoGrantPermissionActivity" android:exported="true" /> <!-- account type "DAVx⁵" --> <service android:name=".ReLoginWithOidcService" android:exported="false"/> <service android:name=".syncadapter.AccountAuthenticatorService" android:exported="false"> Loading
app/src/main/kotlin/at/bitfire/davdroid/ReLoginWithOidcService.kt 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2024 * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package at.bitfire.davdroid import android.accounts.AccountManager import android.accounts.AccountManagerFuture 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 androidx.core.content.ContextCompat import at.bitfire.davdroid.log.Logger import org.apache.commons.lang3.NotImplementedException 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) } 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) intent?.let { ContextCompat.startActivity(context, it, null) } }, null) } private fun getIntentFromFuture(future: AccountManagerFuture<Bundle>): 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? { throw NotImplementedException() } }
app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt +55 −4 Original line number Diff line number Diff line Loading @@ -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.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 /** * There are circumstances when Android drops automatic sync of accounts and resets them Loading @@ -22,12 +32,53 @@ class BootCompletedReceiver: BroadcastReceiver() { 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() val eAccountType = context.getString(R.string.eelo_account_type) val accountManager = AccountManager.get(context) AccountUtils.getMainAccounts(context) .forEach { if (eAccountType == it.type && !isLoggedWithOpenId(it, accountManager)) { notifySwitchToOpenId(it, context) } else { // sync intervals are checked in App.onCreate() val accountSettings = AccountSettings(context, it) accountSettings.initSync() } } } 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 tag = "Switch to openID" 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(tag, NOTIFY_SWITCH_TO_OPENID, notification) } private fun generateReLoginIntent(context: Context): PendingIntent { val reLoginIntent = Intent(context, ReLoginWithOidcService::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } return PendingIntent.getActivity(context, 0, reLoginIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) } }
app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading
app/src/main/res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -53,6 +53,8 @@ <string name="notification_channel_sync_warnings_desc">Non-fatal synchronization problems like certain invalid files</string> <string name="notification_channel_sync_io_errors">Network and I/O errors</string> <string name="notification_channel_sync_io_errors_desc">Timeouts, connection problems, etc. (often temporary)</string> <string name="notification_account_title">Your account %1$s</string> <string name="notification_switch_to_openId_text">A new login service for a better experience is available. Tap the notification to start using it.</string> <!-- IntroActivity --> <string name="intro_slogan1">Your data. Your choice.</string> Loading