Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,12 @@ class GrayScaleFilter : BaseBlocker() {
fun doGrayscaleCheck(event: AccessibilityEvent?) {
if (event == null || (event.eventType and TARGET_EVENTS_MASK) == 0) return

val currentPackageName = event.packageName?.toString() ?: return
val primaryPackage = event.packageName?.toString() ?: ""
val visiblePackages = service.getVisiblePackages(event)

// Skip check if it's the same package or system UI or keyboard
// We don't skip Curbox here because we want to disable grayscale when user is in the app
if (currentPackageName == lastPackageName ||
currentPackageName == "com.android.systemui" ||
ignoredGrayScalePackages.contains(currentPackageName)) return

lastPackageName = currentPackageName
if (primaryPackage.isNotEmpty() && primaryPackage != lastPackageName && primaryPackage != "com.android.systemui" && !ignoredGrayScalePackages.contains(primaryPackage)) {
lastPackageName = primaryPackage
}

val now = Calendar.getInstance()
val calDay = now.get(Calendar.DAY_OF_WEEK)
Expand All @@ -61,8 +58,11 @@ class GrayScaleFilter : BaseBlocker() {
val currentMinutes = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE)

var shouldGrayscale = false
var packageGrayscaled = ""

for (currentPackageName in visiblePackages) {
if (currentPackageName == "com.android.systemui" || ignoredGrayScalePackages.contains(currentPackageName)) continue

// Don't grayscale Curbox itself to ensure usability
for (group in grayscaleGroups) {
if (!group.isActive) continue

Expand All @@ -76,22 +76,26 @@ class GrayScaleFilter : BaseBlocker() {

if (intervals == null || intervals.isEmpty()) {
shouldGrayscale = true
packageGrayscaled = currentPackageName
break
} else {
val isInInterval = intervals.any { isWithinInterval(currentMinutes, it) }
if (isInInterval) {
shouldGrayscale = true
packageGrayscaled = currentPackageName
break
}
}
}
}
if (shouldGrayscale) break
}

if (shouldGrayscale) {
Log.d("GrayScaleFilter", "Enabling monochrome for $currentPackageName")
Log.d("GrayScaleFilter", "Enabling monochrome for $packageGrayscaled")
grayscaleControl.enableGrayscale(service)
} else {
Log.d("GrayScaleFilter", "Disabling monochrome for $currentPackageName")
Log.d("GrayScaleFilter", "Disabling monochrome")
grayscaleControl.disableGrayscale(service)
}
}
Expand Down
151 changes: 83 additions & 68 deletions app/src/main/java/neth/iecal/curbox/blockers/AppBlocker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,93 +91,108 @@ class AppBlocker : BaseBlocker() {
fun doAppBlockerCheck(event: AccessibilityEvent?) {
if (event == null || (event.eventType and TARGET_EVENTS_MASK) == 0) return

val packageName = event.packageName?.toString() ?: return

if (lastPackage == packageName || packageName == service.packageName || ignoredApps.contains(packageName)) {
return
val primaryPackage = event.packageName?.toString() ?: ""
val visiblePackages = service.getVisiblePackages(event)

if (primaryPackage.isNotEmpty() && primaryPackage != lastPackage && primaryPackage != service.packageName && !ignoredApps.contains(primaryPackage)) {
clearFinishedOnEachOpenSessions(primaryPackage)
lastPackage = primaryPackage
}

clearFinishedOnEachOpenSessions(packageName)

val now = System.currentTimeMillis()

blockedAppsList[packageName]?.let { entries ->
for (entry in entries) {
if (!entry.warningConfig.isOnOpenConfig ||
isGroupInCooldown(entry.groupId, now)
) {
continue
}
val activeWindow = entry.config.schedule.activeWindow(now)
if (activeWindow == null) {
entry.config.schedule.nextChangeAfter(now)?.let { nextChange ->
setUpForcedRefreshChecker(
"schedule:${entry.groupId}:$packageName",
nextChange
)
var minRemainingForAll = Long.MAX_VALUE
var anyBlocked = false
var packageBlocked = ""

for (packageName in visiblePackages) {
if (packageName == service.packageName || ignoredApps.contains(packageName)) continue

val entries = blockedAppsList[packageName]
if (entries != null) {
var isThisPackageBlocked = false
for (entry in entries) {
if (!entry.warningConfig.isOnOpenConfig || isGroupInCooldown(entry.groupId, now)) continue
val activeWindow = entry.config.schedule.activeWindow(now)
if (activeWindow == null) {
entry.config.schedule.nextChangeAfter(now)?.let { nextChange ->
setUpForcedRefreshChecker("schedule:${entry.groupId}:$packageName", nextChange)
}
continue
}
continue
notificationManager.stopTimer()
showWarningScreen(packageName, entry.groupId, entry.warningConfig)
isThisPackageBlocked = true
anyBlocked = true
packageBlocked = packageName
break
}

if (isThisPackageBlocked) break

var minRemainingForPackage = Long.MAX_VALUE
for (entry in entries) {
if (entry.warningConfig.isOnOpenConfig) continue
if (isGroupInCooldown(entry.groupId, now)) continue
val activeWindow = entry.config.schedule.activeWindow(now)
if (activeWindow == null) {
entry.config.schedule.nextChangeAfter(now)?.let { nextChange ->
setUpForcedRefreshChecker("schedule:${entry.groupId}:$packageName", nextChange)
}
continue
}

notificationManager.stopTimer()
showWarningScreen(packageName, entry.groupId, entry.warningConfig)
return
}

var minRemaining = Long.MAX_VALUE
for (entry in entries) {
if (entry.warningConfig.isOnOpenConfig) continue
if (isGroupInCooldown(entry.groupId, now)) continue
val activeWindow = entry.config.schedule.activeWindow(now)
if (activeWindow == null) {
entry.config.schedule.nextChangeAfter(now)?.let { nextChange ->
setUpForcedRefreshChecker(
"schedule:${entry.groupId}:$packageName",
nextChange
val currentUsage = runBlocking {
usageStats.getForegroundUsageBetween(
entry.groupPackages.toSet(),
activeWindow.startMs,
minOf(now, activeWindow.endMs)
)
}
continue
val usageLimitMillis = getUsageLimitForToday(entry.config.usage) * 60_000L
val remainingUsage = usageLimitMillis - currentUsage

if (remainingUsage <= 0) {
notificationManager.stopTimer()
showWarningScreen(packageName, entry.groupId, entry.warningConfig)
isThisPackageBlocked = true
anyBlocked = true
packageBlocked = packageName
break
}
if (remainingUsage < minRemainingForPackage) minRemainingForPackage = remainingUsage
setUpForcedRefreshChecker("schedule:${entry.groupId}:$packageName", activeWindow.endMs)
}

val currentUsage = runBlocking {
usageStats.getForegroundUsageBetween(
entry.groupPackages.toSet(),
activeWindow.startMs,
minOf(now, activeWindow.endMs)
)
}
val usageLimitMillis = getUsageLimitForToday(entry.config.usage) * 60_000L
val remainingUsage = usageLimitMillis - currentUsage
if (isThisPackageBlocked) break

if (remainingUsage <= 0) {
notificationManager.stopTimer()
showWarningScreen(packageName, entry.groupId, entry.warningConfig)
return
if (minRemainingForPackage != Long.MAX_VALUE) {
if (minRemainingForPackage < minRemainingForAll) minRemainingForAll = minRemainingForPackage
if (packageName == primaryPackage) {
notificationManager.startTimer(
totalMillis = minRemainingForPackage,
timerId = packageName,
title = service.getString(R.string.notification_title_remaining_usage)
)
}
setUpForcedRefreshChecker("usage:$packageName", System.currentTimeMillis() + minRemainingForPackage)
}
if (remainingUsage < minRemaining) minRemaining = remainingUsage
setUpForcedRefreshChecker(
"schedule:${entry.groupId}:$packageName",
activeWindow.endMs
)
}
if (anyBlocked) break
}

if (minRemaining != Long.MAX_VALUE) {
notificationManager.startTimer(
totalMillis = minRemaining,
timerId = packageName,
title = service.getString(R.string.notification_title_remaining_usage)
)
setUpForcedRefreshChecker("usage:$packageName", System.currentTimeMillis() + minRemaining)
} else {
showNextCooldownNotification()
if (anyBlocked) {
// Expand the PiP window if it's not the primary package
if (packageBlocked.isNotEmpty() && packageBlocked != primaryPackage) {
val launchIntent = service.packageManager.getLaunchIntentForPackage(packageBlocked)
launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
try {
service.startActivity(launchIntent)
} catch (_: Exception) {}
}
lastPackage = packageName
return
}

showNextCooldownNotification()

lastPackage = packageName
}

/**
Expand Down
46 changes: 31 additions & 15 deletions app/src/main/java/neth/iecal/curbox/blockers/FocusModeBlocker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,41 +106,57 @@ class FocusModeBlocker : BaseBlocker() {
}

fun doFocusModeCheck(event: AccessibilityEvent?) {
val packageName = event?.packageName?.toString() ?: return
if (packageName == service.packageName) return
val primaryPackage = event?.packageName?.toString() ?: ""
val visiblePackages = service.getVisiblePackages(event)

if (primaryPackage == service.packageName) return
if (!service.isDelayOver(1000)) return

if (focusModeData != null) {
if (lastPackage != packageName) {
lastPackage = packageName
var packageBlocked = ""
for (packageName in visiblePackages) {
if (packageName == service.packageName) continue
when (focusModeData!!.focusGroupData.blockMode) {
FocusBlockMode.BLOCK_SELECTED -> {
if (focusModeData!!.focusGroupData.packages.contains(packageName)) {
service.pressHome()

Log.d("focus mode","home pressed $packageName")
return
packageBlocked = packageName
break
}
}
FocusBlockMode.BLOCK_ALL_EXCEPT_SELECTED -> {
if (!focusModeData!!.focusGroupData.packages.contains(packageName)) {
service.pressHome()
Log.d("focus mode","home pressed $packageName")

return
if (!focusModeData!!.focusGroupData.packages.contains(packageName) && packageName != "com.android.systemui" && !packageName.startsWith("com.miui")) {
packageBlocked = packageName
break
}
}
}
}

if (packageBlocked.isNotEmpty()) {
if (lastPackage != packageBlocked) {
lastPackage = packageBlocked
service.pressHome()
Log.d("focus mode","home pressed $packageBlocked")

if (packageBlocked != primaryPackage) {
val launchIntent = service.packageManager.getLaunchIntentForPackage(packageBlocked)
launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
try { service.startActivity(launchIntent) } catch (_: Exception) {}
}
return
}
} else if (primaryPackage.isNotEmpty()) {
lastPackage = primaryPackage
}

if (focusModeData!!.focusGroupData.keywords.isNotEmpty() &&
URL_BAR_ID_LIST.containsKey(packageName)) {
URL_BAR_ID_LIST.containsKey(primaryPackage)) {

val now = System.currentTimeMillis()
// Throttle website checks to every 400ms within the same app to preserve performance
if (now - lastWebsiteCheckTime > 400) {
lastWebsiteCheckTime = now
if (keywordBlocker.isFocusWebsiteBlocked(packageName, focusKeywordsPatterns, focusModeData!!.focusGroupData.blockMode)) {
if (keywordBlocker.isFocusWebsiteBlocked(primaryPackage, focusKeywordsPatterns, focusModeData!!.focusGroupData.blockMode)) {
if (now - lastBlockTime > 1500) {
service.pressBack()
Log.d("focus mode","back pressed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,20 @@ open class BaseBlockingService : AccessibilityService() {
lastBackPressTimeStamp = SystemClock.uptimeMillis()

}

fun getVisiblePackages(event: AccessibilityEvent?): Set<String> {
val packages = mutableSetOf<String>()
event?.packageName?.toString()?.let { packages.add(it) }

try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
windows?.forEach { window ->
window.root?.packageName?.toString()?.let { packages.add(it) }
}
}
} catch (_: Exception) {}

return packages
}
}

2 changes: 1 addition & 1 deletion app/src/main/res/xml/app_blocker_service_config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagReportViewIds"
android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_permission_app_blocker"
android:label="Curbox App Blocker" />
Loading