Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 705ad5c8 authored by Vincent Bourgmayer's avatar Vincent Bourgmayer
Browse files

feature: implementation notification to let user relogin

parent 5b61f02b
Loading
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -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">
+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()
    }
}
+55 −4
Original line number Diff line number Diff line
@@ -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
@@ -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)
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -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)
+2 −0
Original line number Diff line number Diff line
@@ -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>