Add option to assign all system apps without category

This commit is contained in:
Jonas Lochmann 2020-11-23 01:00:00 +01:00
parent c40b2298d4
commit 1821e697cf
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
22 changed files with 119 additions and 30 deletions

View file

@ -31,6 +31,7 @@ abstract class PlatformIntegration(
abstract fun getLocalAppActivities(deviceId: String): Collection<AppActivity>
abstract fun getLocalAppTitle(packageName: String): String?
abstract fun getAppIcon(packageName: String): Drawable?
abstract fun isSystemImageApp(packageName: String): Boolean
abstract fun getLauncherAppPackageName(): String?
abstract fun getCurrentProtectionLevel(): ProtectionLevel
abstract fun getForegroundAppPermissionStatus(): RuntimePermissionStatus

View file

@ -34,6 +34,7 @@ import android.provider.Settings
import android.util.Log
import android.view.KeyEvent
import android.widget.Toast
import androidx.collection.LruCache
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.LiveData
@ -116,6 +117,18 @@ class AndroidIntegration(context: Context): PlatformIntegration(maximumProtectio
return AndroidIntegrationApps.getAppIcon(packageName, context)
}
private val isSystemImageAppCache = object: LruCache<String, Boolean>(8) {
override fun create(key: String): Boolean? = try {
val appInfo: ApplicationInfo = context.packageManager.getApplicationInfo(key, 0)
appInfo.flags and ApplicationInfo.FLAG_SYSTEM == ApplicationInfo.FLAG_SYSTEM
} catch (ex: PackageManager.NameNotFoundException) {
null
}
}
override fun isSystemImageApp(packageName: String): Boolean = isSystemImageAppCache.get(packageName) ?: false
override fun getLauncherAppPackageName(): String? {
return Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)

View file

@ -18,8 +18,6 @@ package io.timelimit.android.integration.platform.android
import android.annotation.TargetApi
import android.app.NotificationManager
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Build
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
@ -153,16 +151,19 @@ class NotificationListener: NotificationListenerService() {
return if (deviceAndUserRelatedData?.userRelatedData?.user?.type != UserType.Child) {
BlockingReason.None
} else {
val isSystemImageApp = appLogic.platformIntegration.isSystemImageApp(sbn.packageName)
val appHandling = AppBaseHandling.calculate(
foregroundAppPackageName = sbn.packageName,
foregroundAppActivityName = null,
pauseCounting = false,
pauseForegroundAppBackgroundLoop = false,
userRelatedData = deviceAndUserRelatedData.userRelatedData,
deviceRelatedData = deviceAndUserRelatedData.deviceRelatedData
deviceRelatedData = deviceAndUserRelatedData.deviceRelatedData,
isSystemImageApp = isSystemImageApp
)
if (appHandling is AppBaseHandling.BlockDueToNoCategory && !isSystemApp(sbn.packageName)) {
if (appHandling is AppBaseHandling.BlockDueToNoCategory && !isSystemImageApp) {
BlockingReason.NotPartOfAnCategory
} else if (appHandling is AppBaseHandling.UseCategories) {
val time = RealTime.newInstance()
@ -201,14 +202,4 @@ class NotificationListener: NotificationListenerService() {
}
}
}
private fun isSystemApp(packageName: String): Boolean {
try {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
return appInfo.flags and ApplicationInfo.FLAG_SYSTEM == ApplicationInfo.FLAG_SYSTEM
} catch (ex: PackageManager.NameNotFoundException) {
return false
}
}
}

View file

@ -55,6 +55,8 @@ class DummyIntegration(
return null
}
override fun isSystemImageApp(packageName: String): Boolean = false
override fun getLauncherAppPackageName(): String? = null
override fun getCurrentProtectionLevel(): ProtectionLevel {

View file

@ -55,7 +55,8 @@ object AppAffectedByPrimaryDeviceUtil {
deviceRelatedData = deviceAndUserRelatedData.deviceRelatedData,
userRelatedData = deviceAndUserRelatedData.userRelatedData,
pauseCounting = false,
pauseForegroundAppBackgroundLoop = false
pauseForegroundAppBackgroundLoop = false,
isSystemImageApp = logic.platformIntegration.isSystemImageApp(currentApp.packageName)
)
if (!(handling is AppBaseHandling.UseCategories)) {

View file

@ -294,7 +294,8 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
pauseForegroundAppBackgroundLoop = pauseForegroundAppBackgroundLoop,
userRelatedData = userRelatedData,
deviceRelatedData = deviceRelatedData,
pauseCounting = !isScreenOn
pauseCounting = !isScreenOn,
isSystemImageApp = appLogic.platformIntegration.isSystemImageApp(app.packageName)
)
}
@ -304,7 +305,8 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
pauseForegroundAppBackgroundLoop = false,
userRelatedData = userRelatedData,
deviceRelatedData = deviceRelatedData,
pauseCounting = false
pauseCounting = false,
isSystemImageApp = audioPlaybackPackageName?.let { appLogic.platformIntegration.isSystemImageApp(it) } ?: false
)
val allAppsBaseHandlings = foregroundAppWithBaseHandlings.map { it.second } + listOf(backgroundAppBaseHandling)

View file

@ -0,0 +1,47 @@
/*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
*
* 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 version 3 of the License.
*
* 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 io.timelimit.android.logic
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import io.timelimit.android.R
import io.timelimit.android.data.model.App
import io.timelimit.android.data.model.AppRecommendation
object DummyApps {
const val NOT_ASSIGNED_SYSTEM_IMAGE_APP = ".dummy.system_image"
fun getTitle(packageName: String, context: Context): String? = when (packageName) {
NOT_ASSIGNED_SYSTEM_IMAGE_APP -> context.getString(R.string.dummy_app_unassigned_system_image_app)
else -> null
}
fun getIcon(packageName: String, context: Context): Drawable? = when (packageName) {
NOT_ASSIGNED_SYSTEM_IMAGE_APP -> ContextCompat.getDrawable(context, R.mipmap.ic_system_app)
else -> null
}
fun getApps(deviceId: String, context: Context): List<App> = listOf(
App(
deviceId = deviceId,
packageName = NOT_ASSIGNED_SYSTEM_IMAGE_APP,
title = getTitle(NOT_ASSIGNED_SYSTEM_IMAGE_APP, context)!!,
isLaunchable = false,
recommendation = AppRecommendation.None
)
)
}

View file

@ -171,6 +171,7 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
private fun getAppsWithCategories(packageNames: List<String>, data: UserRelatedData, blockingAtActivityLevel: Boolean): Map<String, Set<String>> {
val categoryForUnassignedApps = data.categoryById[data.user.categoryForNotAssignedApps]
val categoryForOtherSystemApps = data.findCategoryApp(DummyApps.NOT_ASSIGNED_SYSTEM_IMAGE_APP)?.categoryId?.let { data.categoryById[it] }
if (blockingAtActivityLevel) {
val categoriesByPackageName = data.categoryApps.groupBy { it.packageNameWithoutActivityName }
@ -183,7 +184,9 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
val isMainAppIncluded = categoriesItems?.find { !it.specifiesActivity } != null
if (!isMainAppIncluded) {
if (categoryForUnassignedApps != null) {
if (categoryForOtherSystemApps != null && appLogic.platformIntegration.isSystemImageApp(packageName)) {
categories.add(categoryForOtherSystemApps.category.id)
} else if (categoryForUnassignedApps != null) {
categories.add(categoryForUnassignedApps.category.id)
}
}
@ -198,7 +201,10 @@ class SuspendAppsLogic(private val appLogic: AppLogic): Observer {
val result = mutableMapOf<String, Set<String>>()
packageNames.forEach { packageName ->
val category = categoryByPackageName[packageName]?.categoryId ?: categoryForUnassignedApps?.category?.id
val category = categoryByPackageName[packageName]?.categoryId ?: run {
if (categoryForOtherSystemApps != null && appLogic.platformIntegration.isSystemImageApp(packageName))
categoryForOtherSystemApps.category.id else categoryForUnassignedApps?.category?.id
}
result[packageName] = if (category != null) setOf(category) else emptySet()
}

View file

@ -22,6 +22,7 @@ import io.timelimit.android.data.model.derived.DeviceRelatedData
import io.timelimit.android.data.model.derived.UserRelatedData
import io.timelimit.android.integration.platform.android.AndroidIntegrationApps
import io.timelimit.android.logic.BlockingLevel
import io.timelimit.android.logic.DummyApps
sealed class AppBaseHandling {
object Idle: AppBaseHandling()
@ -49,7 +50,8 @@ sealed class AppBaseHandling {
pauseForegroundAppBackgroundLoop: Boolean,
pauseCounting: Boolean,
userRelatedData: UserRelatedData,
deviceRelatedData: DeviceRelatedData
deviceRelatedData: DeviceRelatedData,
isSystemImageApp: Boolean
): AppBaseHandling {
if (pauseForegroundAppBackgroundLoop) {
return PauseLogic
@ -71,7 +73,9 @@ sealed class AppBaseHandling {
} else if (foregroundAppPackageName != null) {
val appCategory = run {
val tryActivityLevelBlocking = deviceRelatedData.deviceEntry.enableActivityLevelBlocking && foregroundAppActivityName != null
val appLevelCategory = userRelatedData.findCategoryApp(foregroundAppPackageName)
val appLevelCategory = userRelatedData.findCategoryApp(foregroundAppPackageName) ?: run {
if (isSystemImageApp) userRelatedData.findCategoryApp(DummyApps.NOT_ASSIGNED_SYSTEM_IMAGE_APP) else null
}
(if (tryActivityLevelBlocking) {
userRelatedData.findCategoryApp("$foregroundAppPackageName:$foregroundAppActivityName")

View file

@ -96,7 +96,8 @@ class LockModel(application: Application): AndroidViewModel(application) {
deviceRelatedData = deviceAndUserRelatedData.deviceRelatedData,
userRelatedData = deviceAndUserRelatedData.userRelatedData,
pauseForegroundAppBackgroundLoop = false,
pauseCounting = false
pauseCounting = false,
isSystemImageApp = logic.platformIntegration.isSystemImageApp(packageName)
)
val needsNetworkId = appBaseHandling.needsNetworkId()

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
*
* 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
@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView
import io.timelimit.android.data.model.App
import io.timelimit.android.databinding.FragmentAddCategoryAppsItemBinding
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.logic.DummyApps
import kotlin.properties.Delegates
class AddAppAdapter: RecyclerView.Adapter<ViewHolder>() {
@ -61,6 +62,7 @@ class AddAppAdapter: RecyclerView.Adapter<ViewHolder>() {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val context = holder.itemView.context
holder.apply {
binding.item = item
@ -70,7 +72,8 @@ class AddAppAdapter: RecyclerView.Adapter<ViewHolder>() {
binding.executePendingBindings()
binding.icon.setImageDrawable(
DefaultAppLogic.with(holder.itemView.context)
DummyApps.getIcon(item.packageName, context) ?:
DefaultAppLogic.with(context)
.platformIntegration.getAppIcon(item.packageName)
)
}

View file

@ -44,6 +44,7 @@ import io.timelimit.android.livedata.liveDataFromValue
import io.timelimit.android.livedata.map
import io.timelimit.android.livedata.switchMap
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.logic.DummyApps
import io.timelimit.android.sync.actions.AddCategoryAppsAction
import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.getActivityViewModel
@ -147,6 +148,8 @@ class AddCategoryAppsFragment : DialogFragment() {
val installedApps = realShowAppsFromAllDevices.switchMap { appsFromAllDevices ->
if (appsFromAllDevices) appsAtAllDevices else appsAtAssignedDevices
}.map { list ->
if (list.isEmpty()) list else list + DummyApps.getApps(deviceId = list.first().deviceId, context = requireContext())
}.map { apps -> apps.distinctBy { app -> app.packageName } }
val userRelatedDataLive = database.derivedDataDao().getUserRelatedDataLive(childId)

View file

@ -25,6 +25,7 @@ import io.timelimit.android.data.model.UsedTimeItem
import io.timelimit.android.databinding.*
import io.timelimit.android.extensions.MinuteOfDay
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.logic.DummyApps
import io.timelimit.android.ui.manage.category.apps.AppAdapterHandlers
import io.timelimit.android.ui.manage.category.timelimit_rules.TimeLimitRulesHandlers
import io.timelimit.android.util.JoinUtil
@ -133,13 +134,15 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
when (item) {
is AppAndRuleItem.AppEntry -> {
val binding = holder.itemView.tag as FragmentCategoryAppsItemBinding
val context = binding.root.context
binding.item = item
binding.handlers = handlers
binding.executePendingBindings()
binding.icon.setImageDrawable(
DefaultAppLogic.with(binding.root.context)
DummyApps.getIcon(item.packageNameWithoutActivityName, context) ?:
DefaultAppLogic.with(context)
.platformIntegration.getAppIcon(item.packageNameWithoutActivityName)
)
}

View file

@ -26,6 +26,7 @@ import io.timelimit.android.extensions.takeDistributedElements
import io.timelimit.android.livedata.map
import io.timelimit.android.livedata.switchMap
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.logic.DummyApps
import java.util.*
class AppsAndRulesModel(application: Application): AndroidViewModel(application) {
@ -114,15 +115,18 @@ class AppsAndRulesModel(application: Application): AndroidViewModel(application)
private val appsOfCategoryWithNames = installedApps.switchMap { allApps ->
appsOfThisCategory.map { apps ->
apps.map { categoryApp ->
categoryApp to allApps.find { app -> app.packageName == categoryApp.packageNameWithoutActivityName }
val title = DummyApps.getTitle(categoryApp.packageNameWithoutActivityName, getApplication()) ?:
allApps.find { app -> app.packageName == categoryApp.packageNameWithoutActivityName }?.title
categoryApp to title
}
}
}
private val appEntries = appsOfCategoryWithNames.map { apps ->
apps.map { (app, appEntry) ->
if (appEntry != null) {
AppAndRuleItem.AppEntry(appEntry.title, app.packageName, app.packageNameWithoutActivityName)
apps.map { (app, title) ->
if (title != null) {
AppAndRuleItem.AppEntry(title, app.packageName, app.packageNameWithoutActivityName)
} else {
AppAndRuleItem.AppEntry("app not found", app.packageName, app.packageNameWithoutActivityName)
}

View file

@ -25,6 +25,7 @@ import io.timelimit.android.databinding.FragmentChildAppsItemBinding
import io.timelimit.android.databinding.GenericBigListHeaderBinding
import io.timelimit.android.databinding.GenericListHeaderBinding
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.logic.DummyApps
import kotlin.properties.Delegates
class ChildAppsAdapter: RecyclerView.Adapter<ChildAppsHolder>() {
@ -101,10 +102,13 @@ class ChildAppsAdapter: RecyclerView.Adapter<ChildAppsHolder>() {
is ChildAppsApp -> {
holder as AppHolder
val context = holder.binding.root.context
holder.binding.item = item.app
holder.binding.currentCategoryTitle = item.shownCategoryName
holder.binding.icon.setImageDrawable(
DefaultAppLogic.with(holder.binding.root.context).platformIntegration.getAppIcon(item.app.packageName)
DummyApps.getIcon(item.app.packageName, context) ?:
DefaultAppLogic.with(context).platformIntegration.getAppIcon(item.app.packageName)
)
holder.binding.handlers = handlers
holder.binding.executePendingBindings()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -1514,4 +1514,6 @@
<string name="task_review_text">%1$s hat angegeben, %2$s erledigt zu haben. Ist das richtig?</string>
<string name="task_review_category">Dafür wird es %1$s Extrazeit für %2$s geben</string>
<string name="task_review_last_grant">Diese Aufgabe wurde zuletzt bestätigt am %s</string>
<string name="dummy_app_unassigned_system_image_app">nicht zugeordnete Apps von der Systempartition</string>
</resources>

View file

@ -1567,4 +1567,6 @@
<string name="task_review_text">%1$s said that %2$s was finished. Is this correct?</string>
<string name="task_review_category">This will grant %1$s extra time for %2$s</string>
<string name="task_review_last_grant">This task was confirmed last time at %s</string>
<string name="dummy_app_unassigned_system_image_app">not assigned Apps from the system image</string>
</resources>