diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index 37a2823a93c57330d633642f8a5f81d64f2c2c21..f46bd085288459b1114456aac066fdf09a9337f3 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -43,8 +43,12 @@ import foundation.e.apps.purchase.AppPurchaseFragmentDirections import foundation.e.apps.setup.signin.SignInViewModel import foundation.e.apps.updates.UpdatesNotifier import foundation.e.apps.utils.enums.Status +import foundation.e.apps.utils.eventBus.AppEvent +import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.modules.CommonUtilsModule import foundation.e.apps.utils.parentFragment.TimeoutFragment +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import timber.log.Timber import java.io.File @@ -199,6 +203,19 @@ class MainActivity : AppCompatActivity() { } viewModel.updateAppWarningList() + + lifecycleScope.launchWhenResumed { + EventBus.events.filter { appEvent -> + appEvent is AppEvent.SignatureMissMatchError + }.collectLatest { + val appName = viewModel.getAppNameByPackageName(it.data.toString()) + ApplicationDialogFragment( + title = getString(R.string.update_error), + message = getString(R.string.error_signature_mismatch, appName), + positiveButtonText = getString(R.string.ok) + ).show(supportFragmentManager, TAG) + } + } } private fun handleFusedDownloadQueued( @@ -246,7 +263,7 @@ class MainActivity : AppCompatActivity() { } fun showSnackbarMessage(message: String) { - Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show() + Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show() } private fun showNoInternet() { diff --git a/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt index 0156a2d7a766d24b33b4c748f67e7e77e53d63c4..83834f12ad7ed5b500c1432a67311c2c2f3cafcb 100644 --- a/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt @@ -561,4 +561,8 @@ class MainActivityViewModel @Inject constructor( fun updateAppWarningList() { blockedAppRepository.fetchUpdateOfAppWarningList() } + + fun getAppNameByPackageName(packageName: String): String { + return pkgManagerModule.getAppNameFromPackageName(packageName) + } } diff --git a/app/src/main/java/foundation/e/apps/api/database/AppDatabase.kt b/app/src/main/java/foundation/e/apps/api/database/AppDatabase.kt index 174e825e2665a273fa75adb30e6f204fcd825acd..d52abde040bb160e0b70ed7cb440fd8ef715340a 100644 --- a/app/src/main/java/foundation/e/apps/api/database/AppDatabase.kt +++ b/app/src/main/java/foundation/e/apps/api/database/AppDatabase.kt @@ -6,17 +6,20 @@ import androidx.room.Room import androidx.room.RoomDatabase import foundation.e.apps.api.exodus.Tracker import foundation.e.apps.api.exodus.TrackerDao +import foundation.e.apps.api.faultyApps.FaultyApp +import foundation.e.apps.api.faultyApps.FaultyAppDao import foundation.e.apps.api.fdroid.FdroidDao import foundation.e.apps.api.fdroid.models.FdroidEntity @Database( - entities = [Tracker::class, FdroidEntity::class], - version = 2, + entities = [Tracker::class, FdroidEntity::class, FaultyApp::class], + version = 3, exportSchema = false ) abstract class AppDatabase : RoomDatabase() { abstract fun trackerDao(): TrackerDao abstract fun fdroidDao(): FdroidDao + abstract fun faultyAppsDao(): FaultyAppDao companion object { private lateinit var INSTANCE: AppDatabase diff --git a/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyApp.kt b/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyApp.kt new file mode 100644 index 0000000000000000000000000000000000000000..36c1af2e0386618742ee457e69179150f5d76418 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyApp.kt @@ -0,0 +1,27 @@ +/* + * + * * Copyright ECORP SAS 2022 + * * Apps Quickly and easily install Android apps onto your device! + * * + * * 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 . + * + */ + +package foundation.e.apps.api.faultyApps + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class FaultyApp(@PrimaryKey val packageName: String, val error: String) diff --git a/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppDao.kt b/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppDao.kt new file mode 100644 index 0000000000000000000000000000000000000000..0218ebf6d2b0208bf94d5b3ded541fdd7790d8e4 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppDao.kt @@ -0,0 +1,38 @@ +/* + * + * * Copyright ECORP SAS 2022 + * * Apps Quickly and easily install Android apps onto your device! + * * + * * 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 . + * + */ + +package foundation.e.apps.api.faultyApps + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy.REPLACE +import androidx.room.Query + +@Dao +interface FaultyAppDao { + @Insert(onConflict = REPLACE) + suspend fun addFaultyApp(faultyApp: FaultyApp): Long + + @Query("SELECT * FROM FAULTYAPP") + suspend fun getFaultyApps(): List + + @Query("DELETE FROM FaultyApp WHERE packageName = :packageName") + suspend fun deleteFaultyAppByPackageName(packageName: String): Int +} diff --git a/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppRepository.kt b/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..aebbb2822d0c4840912ceb309afd86d6f2b74fab --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppRepository.kt @@ -0,0 +1,41 @@ +/* + * + * * Copyright ECORP SAS 2022 + * * Apps Quickly and easily install Android apps onto your device! + * * + * * 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 . + * + */ + +package foundation.e.apps.api.faultyApps + +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class FaultyAppRepository @Inject constructor(private val faultyAppDao: FaultyAppDao) { + + suspend fun addFaultyApp(packageName: String, error: String) { + val faultyApp = FaultyApp(packageName, error) + faultyAppDao.addFaultyApp(faultyApp) + } + + suspend fun getAllFaultyApps(): List { + return faultyAppDao.getFaultyApps() + } + + suspend fun deleteFaultyAppByPackageName(packageName: String) { + faultyAppDao.deleteFaultyAppByPackageName(packageName) + } +} diff --git a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt index fc32cadebf3f08cc325876bc3a07cf39154ba9e5..a45d98ba7adb600b23f8f8709fe36097323e1651 100644 --- a/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +++ b/app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt @@ -49,11 +49,11 @@ import foundation.e.apps.api.gplay.GPlayAPIRepository import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.pkg.PkgManagerModule import foundation.e.apps.utils.enums.AppTag +import foundation.e.apps.utils.enums.FilterLevel import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.ResultStatus import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.Type -import foundation.e.apps.utils.enums.FilterLevel import foundation.e.apps.utils.enums.isUnFiltered import foundation.e.apps.utils.modules.CommonUtilsModule.timeoutDurationInMillis import foundation.e.apps.utils.modules.PWAManagerModule @@ -550,9 +550,11 @@ class FusedAPIImpl @Inject constructor( by = "package_name" ).body()?.run { if (apps.isNotEmpty() && numberOfResults == 1) { - fusedAppList.add(apps[0].apply { - updateFilterLevel(null) - }) + fusedAppList.add( + apps[0].apply { + updateFilterLevel(null) + } + ) } } }) @@ -591,9 +593,11 @@ class FusedAPIImpl @Inject constructor( */ val filter = getAppFilterLevel(app, authData) if (filter.isUnFiltered()) { - fusedAppList.add(app.transformToFusedApp().apply { - filterLevel = filter - }) + fusedAppList.add( + app.transformToFusedApp().apply { + filterLevel = filter + } + ) } } }) @@ -621,9 +625,11 @@ class FusedAPIImpl @Inject constructor( appList.forEach { val filter = getAppFilterLevel(it, authData) if (filter.isUnFiltered()) { - filteredFusedApps.add(it.transformToFusedApp().apply { - this.filterLevel = filter - }) + filteredFusedApps.add( + it.transformToFusedApp().apply { + this.filterLevel = filter + } + ) } } }) diff --git a/app/src/main/java/foundation/e/apps/di/DaoModule.kt b/app/src/main/java/foundation/e/apps/di/DaoModule.kt index 6b2f8692a4cbc30543b93381614ce5672c537afd..4c2a77e6f2b1d00b3d7e5f4743e7988956ffcede 100644 --- a/app/src/main/java/foundation/e/apps/di/DaoModule.kt +++ b/app/src/main/java/foundation/e/apps/di/DaoModule.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import foundation.e.apps.api.database.AppDatabase import foundation.e.apps.api.exodus.TrackerDao +import foundation.e.apps.api.faultyApps.FaultyAppDao import foundation.e.apps.api.fdroid.FdroidDao @InstallIn(SingletonComponent::class) @@ -22,4 +23,9 @@ object DaoModule { fun getFdroidDao(@ApplicationContext context: Context): FdroidDao { return AppDatabase.getInstance(context).fdroidDao() } + + @Provides + fun getFaultyAppsDao(@ApplicationContext context: Context): FaultyAppDao { + return AppDatabase.getInstance(context).faultyAppsDao() + } } diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/InstallerService.kt b/app/src/main/java/foundation/e/apps/manager/pkg/InstallerService.kt index 660e83df1d74ed48f89a75526ff97f5fccfbb8bc..23a5e2f741db6456c43b85f0dfc74ac6bc097692 100644 --- a/app/src/main/java/foundation/e/apps/manager/pkg/InstallerService.kt +++ b/app/src/main/java/foundation/e/apps/manager/pkg/InstallerService.kt @@ -25,9 +25,13 @@ import android.os.Build import android.os.IBinder import androidx.annotation.RequiresApi import dagger.hilt.android.AndroidEntryPoint +import foundation.e.apps.api.faultyApps.FaultyAppRepository import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.utils.enums.Status +import foundation.e.apps.utils.eventBus.AppEvent +import foundation.e.apps.utils.eventBus.EventBus import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -41,8 +45,12 @@ class InstallerService : Service() { @Inject lateinit var pkgManagerModule: PkgManagerModule + @Inject + lateinit var faultyAppRepository: FaultyAppRepository + companion object { const val TAG = "InstallerService" + private const val INSTALL_FAILED_UPDATE_INCOMPATIBLE = "INSTALL_FAILED_UPDATE_INCOMPATIBLE" } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @@ -57,8 +65,28 @@ class InstallerService : Service() { private fun postStatus(status: Int, packageName: String?, extra: String?) { Timber.d("postStatus: $status $packageName $extra") - if (status != PackageInstaller.STATUS_SUCCESS) { - updateInstallationIssue(packageName ?: "") + if (status == PackageInstaller.STATUS_SUCCESS) { + return + } + + updateInstallationIssue(packageName ?: "") + if (status == PackageInstaller.STATUS_FAILURE_CONFLICT && extra?.contains( + INSTALL_FAILED_UPDATE_INCOMPATIBLE + ) == true + ) { + handleInstallFailureDueToSignatureMismatch(packageName) + } + } + + private fun handleInstallFailureDueToSignatureMismatch(packageName: String?) { + MainScope().launch { + if (packageName.isNullOrEmpty()) { + Timber.wtf("Installation failure for an app without packagename!") + return@launch + } + EventBus.invokeEvent(AppEvent.SignatureMissMatchError(packageName)) + faultyAppRepository.addFaultyApp(packageName, INSTALL_FAILED_UPDATE_INCOMPATIBLE) + Timber.e("INSTALL_FAILED_UPDATE_INCOMPATIBLE for $packageName") } } diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt index 211e3dbcabbaede5ddb020f4ebc4cf9aefa744ce..665150bdd2dafb4474263e9d5255c6146124012a 100644 --- a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt +++ b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerBR.kt @@ -23,28 +23,34 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller import dagger.hilt.android.AndroidEntryPoint +import foundation.e.apps.api.faultyApps.FaultyAppRepository import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.utils.enums.Status +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +import javax.inject.Named @AndroidEntryPoint @DelicateCoroutinesApi open class PkgManagerBR : BroadcastReceiver() { - companion object { - private const val TAG = "PkgManagerBR" - } - @Inject lateinit var fusedManagerRepository: FusedManagerRepository @Inject lateinit var pkgManagerModule: PkgManagerModule + @Inject + lateinit var faultyAppRepository: FaultyAppRepository + + @Inject + @Named("ioCoroutineScope") + lateinit var coroutineScope: CoroutineScope + override fun onReceive(context: Context?, intent: Intent?) { val action = intent?.action if (context != null && action != null) { @@ -61,9 +67,11 @@ open class PkgManagerBR : BroadcastReceiver() { when (action) { Intent.ACTION_PACKAGE_ADDED -> { updateDownloadStatus(pkgName) + removeFaultyAppByPackageName(pkgName) } Intent.ACTION_PACKAGE_REMOVED -> { if (!isUpdating) deleteDownload(pkgName) + removeFaultyAppByPackageName(pkgName) } PkgManagerModule.ERROR_PACKAGE_INSTALL -> { Timber.e("Installation failed due to error: $extra") @@ -75,6 +83,12 @@ open class PkgManagerBR : BroadcastReceiver() { } } + private fun removeFaultyAppByPackageName(pkgName: String) { + coroutineScope.launch { + faultyAppRepository.deleteFaultyAppByPackageName(pkgName) + } + } + private fun deleteDownload(pkgName: String) { GlobalScope.launch { val fusedDownload = fusedManagerRepository.getFusedDownload(packageName = pkgName) diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt index e694d89e7fb0a7a6f7c2de220bd8b360f7725422..20ca99948eae7ff07402eed882f5edafa685b276 100644 --- a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt +++ b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt @@ -229,4 +229,11 @@ class PkgManagerModule @Inject constructor( fun getAllSystemApps(): List { return packageManager.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY) } + + fun getAppNameFromPackageName(packageName: String): String { + val packageManager = context.packageManager + return packageManager.getApplicationLabel( + packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA) + ).toString() + } } diff --git a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt index 8ed984890c82e3ed7051e2c8144eb0e2f303b050..2c2ce9d2e14cf6d6b88586242e62486381e28a87 100644 --- a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt +++ b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt @@ -19,6 +19,7 @@ package foundation.e.apps.updates.manager import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.api.faultyApps.FaultyAppRepository import foundation.e.apps.api.fused.FusedAPIRepository import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.manager.pkg.PkgManagerModule @@ -30,7 +31,8 @@ import javax.inject.Inject class UpdatesManagerImpl @Inject constructor( private val pkgManagerModule: PkgManagerModule, - private val fusedAPIRepository: FusedAPIRepository + private val fusedAPIRepository: FusedAPIRepository, + private val faultyAppRepository: FaultyAppRepository ) { private val TAG = UpdatesManagerImpl::class.java.simpleName @@ -75,7 +77,9 @@ class UpdatesManagerImpl @Inject constructor( } } } - return Pair(updateList, status) + val faultyAppsPackageNames = faultyAppRepository.getAllFaultyApps().map { it.packageName } + val nonFaultyUpdateList = updateList.filter { !faultyAppsPackageNames.contains(it.package_name) } + return Pair(nonFaultyUpdateList, status) } fun getApplicationCategoryPreference(): String { diff --git a/app/src/main/java/foundation/e/apps/utils/enums/FilterLevel.kt b/app/src/main/java/foundation/e/apps/utils/enums/FilterLevel.kt index 7eec33730cd5d12a5eb1aeb73dd393552d07fd9f..4fd93c524fd1f7d11f5c710bb4480cf595e991c6 100644 --- a/app/src/main/java/foundation/e/apps/utils/enums/FilterLevel.kt +++ b/app/src/main/java/foundation/e/apps/utils/enums/FilterLevel.kt @@ -34,11 +34,11 @@ package foundation.e.apps.utils.enums * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720 */ enum class FilterLevel { - UI, // Show the app in lists, but show "N/A" in the install button. - DATA, // Filter the app out from lists and search results, don't show the app at all. - NONE, // No restrictions - UNKNOWN, // Not initialised yet + UI, // Show the app in lists, but show "N/A" in the install button. + DATA, // Filter the app out from lists and search results, don't show the app at all. + NONE, // No restrictions + UNKNOWN, // Not initialised yet } fun FilterLevel.isUnFiltered(): Boolean = this == FilterLevel.NONE -fun FilterLevel.isInitialized(): Boolean = this != FilterLevel.UNKNOWN \ No newline at end of file +fun FilterLevel.isInitialized(): Boolean = this != FilterLevel.UNKNOWN diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..a3a7306ef05fe6fc38842b2a29300c7dd66851de --- /dev/null +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt @@ -0,0 +1,25 @@ +/* + * + * * Copyright ECORP SAS 2022 + * * Apps Quickly and easily install Android apps onto your device! + * * + * * 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 . + * + */ + +package foundation.e.apps.utils.eventBus + +sealed class AppEvent(val data: Any) { + class SignatureMissMatchError(packageName: String) : AppEvent(packageName) +} diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/EventBus.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/EventBus.kt new file mode 100644 index 0000000000000000000000000000000000000000..ffb5fac954cbe10ef69552dc3ff40b8c9a684d47 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/EventBus.kt @@ -0,0 +1,31 @@ +/* + * + * * Copyright ECORP SAS 2022 + * * Apps Quickly and easily install Android apps onto your device! + * * + * * 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 . + * + */ + +package foundation.e.apps.utils.eventBus + +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow + +object EventBus { + private val _events = MutableSharedFlow() + val events = _events.asSharedFlow() + + suspend fun invokeEvent(event: AppEvent) = _events.emit(event) +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 38407f25e9744a719792f2f8deac0f00a077234e..b6db8704e028d61dbc666d92a31c6a10dbc70f3f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,6 +118,8 @@ App updates will be installed automatically App updates will not be installed automatically All apps are up-to-date + The update cannot be applied because there is a signature mismatch between the update of %1$s and the version you\'ve installed on your phone. To remedy this you can uninstall %1$s and then install it again from App Lounge. <br /><br /> Note: This message won\'t appear again. + Update Error! Discover