Kynetics/uf-android-client

View on GitHub
uf-client-service/src/main/kotlin/com/kynetics/uf/android/UpdateFactoryService.kt

Summary

Maintainability
A
1 hr
Test Coverage
/*
 * Copyright © 2017-2023  Kynetics  LLC
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package com.kynetics.uf.android

import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.*
import android.util.Log
import androidx.core.app.NotificationCompat
import com.kynetics.uf.android.apicomptibility.ApiVersion
import com.kynetics.uf.android.client.RestartableClientService
import com.kynetics.uf.android.communication.CommunicationApi
import com.kynetics.uf.android.communication.CommunicationApiStrategy
import com.kynetics.uf.android.communication.messenger.MessageHandler
import com.kynetics.uf.android.communication.messenger.MessengerHandler
import com.kynetics.uf.android.configuration.AndroidDeploymentPermitProvider
import com.kynetics.uf.android.configuration.AndroidMessageListener
import com.kynetics.uf.android.configuration.ConfigurationHandler
import com.kynetics.uf.android.content.SharedPreferencesWithObject
import com.kynetics.uf.android.content.SharedPreferencesFactory
import com.kynetics.uf.android.ui.MainActivity
import com.kynetics.uf.android.update.CurrentUpdateState
import com.kynetics.uf.android.update.system.SystemUpdateType
import de.psdev.slf4j.android.logger.AndroidLoggerAdapter
import de.psdev.slf4j.android.logger.LogLevel
import org.eclipse.hara.ddiclient.api.MessageListener
import java.lang.ref.WeakReference

/*
 * @author Daniele Sergio
 */
class UpdateFactoryService : Service(), UpdateFactoryServiceCommand {

    override fun authorizationGranted() {
        softDeploymentPermitProvider?.allow(true)
    }

    override fun authorizationDenied() {
        softDeploymentPermitProvider?.allow(false)
    }

    private val mMessenger = Messenger(IncomingHandler(this))

    private var mNotificationManager: NotificationManager? = null
    private var systemUpdateType: SystemUpdateType = SystemUpdateType.SINGLE_COPY

    var softDeploymentPermitProvider: AndroidDeploymentPermitProvider? = null
    private var messageListener: MessageListener? = null

    private var sharedPreferencesFile: String? = null
    private var configurationHandler: ConfigurationHandler? = null
    private var ufService: RestartableClientService? = null

    private val api: CommunicationApi by lazy {
        CommunicationApiStrategy.newInstance(
            configurationHandler!!,
            ufService!!,
            softDeploymentPermitProvider!!
        )
    }
    override fun configureService() {
        if(ufService == null){
            ufService = RestartableClientService.newInstance(softDeploymentPermitProvider!!, listOf(messageListener!!))
        }
        when {
            configurationHandler!=null -> {
                ufService?.restartService(configurationHandler!!)
            }
            else -> {
                Log.w(TAG, "Service cant be configured because configuration handler is null")
            }
        }
    }

    private lateinit var forcePingPendingIntent: PendingIntent
    lateinit var currentUpdateState: CurrentUpdateState

    override fun onCreate() {
        super.onCreate()
        AndroidLoggerAdapter.setLogTag("Update Factory Client")
        if(BuildConfig.DEBUG){
            AndroidLoggerAdapter.setLogLevel(LogLevel.TRACE)
        }
        mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        sharedPreferencesFile = getString(R.string.shared_preferences_file)
        configurationHandler = ConfigurationHandler( this, getSharedPreferences(sharedPreferencesFile, Context.MODE_PRIVATE))
        systemUpdateType = SystemUpdateType.getSystemUpdateType()
        ufServiceCommand = this
        softDeploymentPermitProvider = AndroidDeploymentPermitProvider.build(configurationHandler!!, mNotificationManager!!, this)
        messageListener = AndroidMessageListener(this)
        currentUpdateState = CurrentUpdateState(this)

        val forcePingIntent = Intent(FORCE_PING_ACTION)

        forcePingPendingIntent = PendingIntent.getBroadcast(this, 1, forcePingIntent, 0)

        // add actions here !
        val intentFilter = IntentFilter()
        intentFilter.addAction(FORCE_PING_ACTION)

        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                if (intent.action == FORCE_PING_ACTION) {
                    ufService?.forcePing()
                    MessengerHandler.onAction(MessageHandler.Action.FORCE_PING)
                    val closeIntent = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
                    sendBroadcast(closeIntent)
                }
            }
        }

        registerReceiver(receiver, intentFilter)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i(TAG, String.format("service's starting with version %s (%s)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE))
        startForeground()
        isRunning = true
        var serviceConfiguration = configurationHandler?.getConfigurationFromFile()
        if (serviceConfiguration == null && intent != null) {
            serviceConfiguration = configurationHandler?.getServiceConfigurationFromIntent(intent)
        } else if (serviceConfiguration != null) {
            Log.i(TAG, "Loaded new configuration from file")
        }
        if (configurationHandler?.getCurrentConfiguration() != serviceConfiguration) {
            configurationHandler?.saveServiceConfigurationToSharedPreferences(serviceConfiguration)
        }
        configureService()
        return START_STICKY
    }

    // todo add api to configure targetAttibutes (separete  d from serviceConfiguration)
    private class IncomingHandler(service: UpdateFactoryService) : Handler(Looper.myLooper()!!) {
        private val updateFactoryServiceRef = WeakReference(service)

        private fun <T> WeakReference<T>.execute(action: T.() -> Unit){
            get()?.let { ref ->
                ref.action()
            }
        }
        override fun handleMessage(msg: Message) {
            updateFactoryServiceRef.execute { api.onMessage(msg) }
        }

    }

    override fun onBind(intent: Intent): IBinder? {
        startService(this)
        return mMessenger.binder
    }

    override fun getSharedPreferences(name: String?, mode: Int): SharedPreferencesWithObject {
        return SharedPreferencesFactory.get(applicationContext, name, mode)
    }

    private fun startForeground() {
        if(mNotificationManager != null) {
            ApiVersion.fromVersionCode().configureChannel(CHANNEL_ID, getString(R.string.app_name), mNotificationManager!!)
            startForeground(NOTIFICATION_ID, getNotification(""))
        }
    }

    fun getNotification(notificationContent: String, forcePingAction: Boolean = false): Notification {

        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.uf_logo)
                .setStyle(NotificationCompat.BigTextStyle().bigText(notificationContent))
                .setContentTitle(getString(R.string.update_factory_notification_title))
                .setContentText(notificationContent)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        if (forcePingAction) {
            notificationBuilder.addAction(android.R.drawable.ic_popup_sync, "Grant Authorization", forcePingPendingIntent)
        }
        return notificationBuilder.build()
    }

    companion object {
        enum class AuthorizationType(
            val toActionOnGranted: MessageHandler.Action,
            val toActionOnDenied: MessageHandler.Action
        ) {
            DOWNLOAD(
                    MessageHandler.Action.AUTH_DOWNLOAD_GRANTED,
                    MessageHandler.Action.AUTH_DOWNLOAD_DENIED
            ) {
                override val extra = MainActivity.INTENT_TYPE_EXTRA_VALUE_DOWNLOAD
                override val event = "WaitingDownloadAuthorization"
            },

            UPDATE(
                MessageHandler.Action.AUTH_UPDATE_GRANTED,
                    MessageHandler.Action.AUTH_UPDATE_DENIED) {
                override val extra: Int = MainActivity.INTENT_TYPE_EXTRA_VALUE_REBOOT
                override val event = "WaitingUpdateAuthorization"
            };

            abstract val extra: Int
            abstract val event: String
        }

        @JvmStatic
        fun startService(context: Context) {
            if (!isRunning) {
                val myIntent = Intent(context, UpdateFactoryService::class.java)
                ApiVersion.fromVersionCode().startService(context, myIntent)
            }
        }

        var isRunning = false

        @JvmStatic
        var ufServiceCommand: UpdateFactoryServiceCommand? = null
        private const val FORCE_PING_ACTION = "ForcePing"
        private const val CHANNEL_ID = "UPDATE_FACTORY_NOTIFICATION_CHANNEL_ID"
        const val NOTIFICATION_ID = 1
        private val TAG = UpdateFactoryService::class.java.simpleName
    }
}