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

Commit ff61740c authored by Hasib Prince's avatar Hasib Prince
Browse files

Showing notification for auto update

App shows notification based on settings during auto update
parent c2041ae0
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import dagger.hilt.android.AndroidEntryPoint
import foundation.e.apps.databinding.ActivityMainBinding
import foundation.e.apps.updates.UpdatesNotifier
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Type
import foundation.e.apps.utils.enums.User
@@ -115,6 +116,10 @@ class MainActivity : AppCompatActivity() {
            }
        }

        if (intent.hasExtra(UpdatesNotifier.UPDATES_NOTIFICATION_CLICK_EXTRA)) {
            bottomNavigationView.selectedItemId = R.id.updatesFragment
        }

        // Create notification channel on post-nougat devices
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            viewModel.createNotificationChannels()
+128 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2021  E FOUNDATION
 *
 * 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 foundation.e.apps.updates

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import foundation.e.apps.MainActivity
import foundation.e.apps.R
class UpdatesNotifier {
    companion object {
        const val UPDATES_NOTIFICATION_ID = 76
        const val UPDATES_NOTIFICATION_CHANNEL_ID = "updates_notification"
        const val UPDATES_NOTIFICATION_CHANNEL_TITLE = "App updates"
        const val UPDATES_NOTIFICATION_CLICK_EXTRA = "updates_notification_click_extra"
    }

    private fun getNotification(
        context: Context,
        numberOfApps: Int,
        installAutomatically: Boolean,
        unmeteredNetworkOnly: Boolean,
        isConnectedToUnmeteredNetwork: Boolean
    ):
        Notification {
        val notificationBuilder =
            NotificationCompat.Builder(context, UPDATES_NOTIFICATION_CHANNEL_ID)
        notificationBuilder.setSmallIcon(R.drawable.ic_app_updated_on)
        notificationBuilder.priority = NotificationCompat.PRIORITY_DEFAULT
        if (numberOfApps == 1) {
            notificationBuilder.setContentTitle(
                context.resources.getQuantityString(
                    R.plurals.updates_notification_title,
                    1,
                    numberOfApps
                )
            )
        } else {
            notificationBuilder.setContentTitle(
                context.resources.getQuantityString(
                    R.plurals.updates_notification_title,
                    numberOfApps,
                    numberOfApps
                )
            )
        }
        if (installAutomatically) {
            notificationBuilder.setContentText(context.getString(R.string.AUTOMATICALLY_INSTALL_updates_notification_text))
            if (unmeteredNetworkOnly && !isConnectedToUnmeteredNetwork) {
                notificationBuilder.setSubText(
                    context
                        .getString(R.string.updates_notification_unmetered_network_warning)
                )
            }
        } else {
            notificationBuilder.setContentText(context.getString(R.string.MANUALLY_INSTALL_updates_notification_text))
        }
        notificationBuilder.setContentIntent(getClickIntent(context))
        notificationBuilder.setAutoCancel(true)
        return notificationBuilder.build()
    }

    private fun getClickIntent(context: Context): PendingIntent {
        val intent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
            putExtra(UPDATES_NOTIFICATION_CLICK_EXTRA, true)
        }
        return PendingIntent.getActivity(context, 0, intent, 0)
    }

    private fun createNotificationChannel(context: Context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel(
                UPDATES_NOTIFICATION_CHANNEL_ID,
                UPDATES_NOTIFICATION_CHANNEL_TITLE,
                importance
            )
            val notificationManager: NotificationManager =
                context.getSystemService(Context.NOTIFICATION_SERVICE) as
                    NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }

    fun showNotification(
        context: Context,
        numberOfApps: Int,
        installAutomatically: Boolean,
        unmeteredNetworkOnly: Boolean,
        isConnectedToUnmeteredNetwork: Boolean
    ) {
        with(NotificationManagerCompat.from(context)) {
            createNotificationChannel(context)
            notify(
                UPDATES_NOTIFICATION_ID,
                getNotification(
                    context,
                    numberOfApps,
                    installAutomatically,
                    unmeteredNetworkOnly,
                    isConnectedToUnmeteredNetwork
                )
            )
        }
    }
}
+126 −19
Original line number Diff line number Diff line
package foundation.e.apps.updates

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.util.Base64
import android.util.Log
import androidx.hilt.work.HiltWorker
import androidx.preference.PreferenceManager
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.aurora.gplayapi.data.models.AuthData
import com.google.gson.Gson
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import foundation.e.apps.R
import foundation.e.apps.api.cleanapk.CleanAPKInterface
import foundation.e.apps.api.fused.FusedAPIRepository
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.fused.FusedManagerRepository
import foundation.e.apps.updates.manager.UpdatesManagerRepository
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Type
import foundation.e.apps.utils.modules.DataStoreModule
import kotlinx.coroutines.flow.collect
import java.io.ByteArrayOutputStream
import java.net.URL

@@ -35,19 +42,75 @@ class UpdatesWorker @AssistedInject constructor(
    private val gson: Gson,
) : CoroutineWorker(context, params) {
    val TAG = UpdatesWorker::class.simpleName
    private var shouldShowNotification = true
    private var automaticInstallEnabled = true
    private var onlyOnUnmeteredNetwork = false

    override suspend fun doWork(): Result {
        Log.d(TAG, "doWork: triggered")
        val authDataJson = dataStoreModule.getAuthDataSync()
        Log.d(TAG, "doWork: authdata: $authDataJson")
        val authData = gson.fromJson(authDataJson, AuthData::class.java)
        return try {
            checkForUpdates()
            Result.success()
        } catch (e: Throwable) {
            Result.failure()
        }
    }

    private suspend fun checkForUpdates() {
        loadSettings()
        val authData = getAuthData()
        val appsNeededToUpdate = updatesManagerRepository.getUpdates(authData)
        Log.d(TAG, "doWork: update needs: ${appsNeededToUpdate.size}")
        val isConnectedToUnmeteredNetwork = isConnectedToUnmeteredNetwork(applicationContext)
        handleNotification(appsNeededToUpdate, isConnectedToUnmeteredNetwork)
        triggerUpdateProcessOnSettings(
            isConnectedToUnmeteredNetwork,
            appsNeededToUpdate,
            authData
        )
    }

    private suspend fun triggerUpdateProcessOnSettings(
        isConnectedToUnmeteredNetwork: Boolean,
        appsNeededToUpdate: List<FusedApp>,
        authData: AuthData
    ) {
        if (automaticInstallEnabled &&
            applicationContext.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
        ) {
            if (onlyOnUnmeteredNetwork && isConnectedToUnmeteredNetwork) {
                startUpdateProcess(appsNeededToUpdate, authData)
            } else if (!onlyOnUnmeteredNetwork) {
                startUpdateProcess(appsNeededToUpdate, authData)
            }
        }
    }

    private fun handleNotification(
        appsNeededToUpdate: List<FusedApp>,
        isConnectedToUnmeteredNetwork: Boolean
    ) {
        if (appsNeededToUpdate.isNotEmpty()) {
            UpdatesNotifier().showNotification(
                applicationContext,
                appsNeededToUpdate.size,
                automaticInstallEnabled,
                onlyOnUnmeteredNetwork,
                isConnectedToUnmeteredNetwork
            )
        }
    }

    private fun getAuthData(): AuthData {
        val authDataJson = dataStoreModule.getAuthDataSync()
        return gson.fromJson(authDataJson, AuthData::class.java)
    }

    private suspend fun startUpdateProcess(
        appsNeededToUpdate: List<FusedApp>,
        authData: AuthData
    ) {
        appsNeededToUpdate.forEach { fusedApp ->
            Log.d(TAG, "doWork: triggering update for: ${fusedApp.name}")
            val downloadList = getAppDownloadLink(fusedApp, authData).toMutableList()
            val iconBase64 = fusedApp.getIconImageToBase64()
            val iconBase64 = getIconImageToBase64(fusedApp)

            val fusedDownload = FusedDownload(
                fusedApp._id,
@@ -63,16 +126,16 @@ class UpdatesWorker @AssistedInject constructor(
            )

            fusedManagerRepository.addDownload(fusedDownload)
            Log.d(TAG, "doWork: triggering update for: ${fusedApp.name} added download in db")
            Log.d(TAG, "doWork: triggering update for: ${fusedApp.name} downloading...")
            fusedManagerRepository.downloadApp(fusedDownload)
            Log.d(TAG, "doWork: triggering update for: ${fusedApp.name} downloaded...")
        }
        observeFusedDownload()
    }

    private suspend fun observeFusedDownload() {
        fusedManagerRepository.getDownloadListFlow().collect {
            Log.d(TAG, "doWork: updated downloadlist ${it.size}")
            it.forEach { fusedDownload ->
                Log.d(TAG, "doWork: updated downloadlistitem ${fusedDownload.name}")
                if (fusedDownload.type == Type.NATIVE && fusedDownload.status == Status.INSTALLING && fusedDownload.downloadIdMap.all { it.value }) {
                    Log.d(TAG, "doWork: triggering update for: ${fusedDownload.name} installing...")
                    fusedManagerRepository.installApp(fusedDownload)
@@ -80,7 +143,30 @@ class UpdatesWorker @AssistedInject constructor(
                }
            }
        }
        return Result.success()
    }

    private fun loadSettings() {
        val preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
        shouldShowNotification =
            preferences.getBoolean(
                applicationContext.getString(
                    R.string.updateNotify
                ),
                true
            )
        automaticInstallEnabled = preferences.getBoolean(
            applicationContext.getString(
                R.string.auto_install_enabled
            ),
            true
        )

        onlyOnUnmeteredNetwork = preferences.getBoolean(
            applicationContext.getString(
                R.string.only_unmetered_network
            ),
            false
        )
    }

    private suspend fun getAppDownloadLink(app: FusedApp, authData: AuthData): List<String> {
@@ -101,12 +187,33 @@ class UpdatesWorker @AssistedInject constructor(
        }
        return downloadList
    }
}

fun FusedApp.getIconImageToBase64(): String {
    val stream = URL(icon_image_path).openStream()
    private fun getIconImageToBase64(fusedApp: FusedApp): String {
        val url =
            if (fusedApp.origin == Origin.CLEANAPK) "${CleanAPKInterface.ASSET_URL}${fusedApp.icon_image_path}" else fusedApp.icon_image_path
        val stream = URL(url).openStream()
        val bitmap = BitmapFactory.decodeStream(stream)
        val byteArrayOS = ByteArrayOutputStream()
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOS)
        return Base64.encodeToString(byteArrayOS.toByteArray(), Base64.DEFAULT)
    }

    /*
     * Checks if the device is connected to a metered connection or not
     * @param context current Context
     * @return returns true if the connections is not metered, false otherwise
     */
    private fun isConnectedToUnmeteredNetwork(context: Context): Boolean {
        val connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val capabilities =
            connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)

        if (capabilities != null) {
            if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
                return true
            }
        }
        return false
    }
}
+26 −0
Original line number Diff line number Diff line
<!--
  ~ Copyright (C) 2019-2021  E FOUNDATION
  ~
  ~ 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/>.
  -->

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="640dp"
    android:height="512dp"
    android:viewportWidth="640"
    android:viewportHeight="512">
  <path
      android:fillColor="@color/colorAccent"
      android:pathData="M272,80c53.473,0 99.279,32.794 118.426,79.363C401.611,149.793 416.125,144 432,144c35.346,0 64,28.654 64,64 0,11.829 -3.222,22.9 -8.817,32.407A96.998,96.998 0,0 1,496 240c53.019,0 96,42.981 96,96s-42.981,96 -96,96L160,432c-61.856,0 -112,-50.144 -112,-112 0,-56.428 41.732,-103.101 96.014,-110.859 -0.003,-0.381 -0.014,-0.76 -0.014,-1.141 0,-70.692 57.308,-128 128,-128m0,-48c-84.587,0 -155.5,59.732 -172.272,139.774C39.889,196.13 0,254.416 0,320c0,88.374 71.642,160 160,160h336c79.544,0 144,-64.487 144,-144 0,-61.805 -39.188,-115.805 -96.272,-135.891C539.718,142.116 491.432,96 432,96c-7.558,0 -15.051,0.767 -22.369,2.262C377.723,58.272 328.091,32 272,32zM312,420L312,232.535l54.545,55.762c4.671,4.775 12.341,4.817 17.064,0.094l16.877,-16.877c4.686,-4.686 4.686,-12.284 0,-16.971l-104,-104c-4.686,-4.686 -12.284,-4.686 -16.971,0l-104,104c-4.686,4.686 -4.686,12.284 0,16.971l16.877,16.877c4.723,4.723 12.393,4.681 17.064,-0.094L264,232.535L264,372c0,6.627 5.373,12 12,12h24c6.627,0 12,-5.373 12,-12z"/>
</vector>
+6 −0
Original line number Diff line number Diff line
@@ -127,6 +127,9 @@
    <!-- Notification -->
    <string name="downloads">Downloads</string>
    <string name="updates">Updates</string>
    <string name="AUTOMATICALLY_INSTALL_updates_notification_text">App updates will be installed automatically</string>
    <string name="MANUALLY_INSTALL_updates_notification_text">App updates will not be installed automatically</string>
    <string name="updates_notification_unmetered_network_warning">Waiting for un-metered network</string>

<!--    trackers_dialog-->
    <string name="privacy_computed_using_text">Computed using <a href="https://exodus-privacy.eu.org/en/">Exodus Privacy Analysis</a></string>
@@ -134,5 +137,8 @@

<!-- Settings Preferences-->
    <string name="update_check_intervals">updateCheckIntervals</string>
    <string name="updateNotify">updateNotify</string>
    <string name="auto_install_enabled">updateInstallAuto</string>
    <string name="only_unmetered_network">updateUnmeteredOnly</string>

</resources>
 No newline at end of file
Loading