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
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

<application
android:label="@string/app_label"
Expand Down
66 changes: 28 additions & 38 deletions app/src/main/kotlin/cc/goll/caff/CaffeinePreferences.kt
Original file line number Diff line number Diff line change
@@ -1,58 +1,48 @@
package cc.goll.caff


import android.os.Build
import android.os.Bundle
import android.os.Build

import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge
import androidx.activity.compose.setContent

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable

import androidx.compose.material3.Text
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.indication

import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.unit.dp
import androidx.compose.ui.platform.LocalContext

import java.util.TreeSet

import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import cc.goll.caff.utils.HumanReadableTime
import java.util.TreeSet


private val DarkColors = darkColorScheme()
Expand Down Expand Up @@ -144,7 +134,7 @@ private fun Modifier.ripple(): Modifier =

@Composable
fun PreferencesDialog(durations: ObservableSet<Int>) {
var currentDuration: Int by rememberSaveable { mutableStateOf(5 * 60) }
var currentDuration: Int by rememberSaveable { mutableIntStateOf(5 * 60) }

val durationWasAdded: Boolean by remember {
derivedStateOf {
Expand Down
57 changes: 35 additions & 22 deletions app/src/main/kotlin/cc/goll/caff/CaffeineService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import android.content.Intent
import android.os.IBinder
import android.os.Binder

import android.os.PowerManager
import android.view.View
import android.view.WindowManager
import android.graphics.PixelFormat
import android.os.CountDownTimer


private lateinit var WAKE_LOCK: PowerManager.WakeLock;

// Amount of milliseconds in a second
private const val S_TO_MS: Long = 1000

Expand All @@ -35,12 +34,12 @@ class CaffeineService : Service() {
}


override fun onBind(intent: Intent): IBinder? {
override fun onBind(intent: Intent): IBinder {
return binder
}

override fun onUnbind(intent: Intent): Boolean {
if (!WAKE_LOCK.isHeld) {
if (!isScreenKeptOn) {
// If the value is null, we weren't started, which shouldn't happen.
// Either way, we just won't be stopped, so everything is fine.
stopSelfResult(lastStartId ?: 0)
Expand All @@ -51,16 +50,6 @@ class CaffeineService : Service() {
}

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
// Create a wake lock. This is done once for each execution of the application.
if (!::WAKE_LOCK.isInitialized) {
@Suppress("DEPRECATION")
WAKE_LOCK = (getSystemService(POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ON_AFTER_RELEASE,
"cc.goll.caff:caffeine"
)

WAKE_LOCK.setReferenceCounted(false)
}

// NOTE: we don't care about earlier startService calls that may not have finished their lifecycle
lastStartId = startId
Expand All @@ -85,31 +74,55 @@ class CaffeineService : Service() {

private var timer: CountDownTimer? = null

private var isScreenKeptOn = false
private var overlayView: View? = null

private fun addOverlay() {
if (isScreenKeptOn) return
val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
val params = WindowManager.LayoutParams(
1, 1,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
PixelFormat.TRANSPARENT
)
overlayView = View(this)
windowManager.addView(overlayView, params)
isScreenKeptOn = true
}

private fun removeOverlay() {
if (!isScreenKeptOn) return
val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
overlayView?.let { windowManager.removeView(it) }
overlayView = null
isScreenKeptOn = false
}

// Acquire a wake lock for @param duration seconds.
//
// If @param duration is @val Int.MAX_VALUE, acquires a wake
// lock indefinitely (until @fun releaseWakeLock is called)
fun acquireWakeLockFor(duration: Int): Boolean {
if (WAKE_LOCK.isHeld)
if (isScreenKeptOn)
return false

// Add a grace period of 1s so the user can read the duration shown initially
var grace = 0
val grace = 0

elapsed = 0
acquiredFor = duration

WAKE_LOCK.acquire()
addOverlay()

timer = when (duration) {
Int.MAX_VALUE -> null
else -> object : CountDownTimer(duration * S_TO_MS, 1 * S_TO_MS) {
override fun onTick(remaining: Long) {
if (grace > 0)
elapsed++

grace = 1
}

override fun onFinish() {
Expand All @@ -127,7 +140,7 @@ class CaffeineService : Service() {

timer?.cancel()

WAKE_LOCK.release()
removeOverlay()

if (shouldStop) {
stopSelfResult(lastStartId ?: 0)
Expand Down
44 changes: 30 additions & 14 deletions app/src/main/kotlin/cc/goll/caff/CaffeineTileService.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
package cc.goll.caff


import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.content.ComponentName

import android.graphics.drawable.Icon

import android.service.quicksettings.Tile
import android.service.quicksettings.TileService

import android.os.Build
import android.os.IBinder
import android.os.PowerManager

import java.util.concurrent.TimeUnit
import java.util.concurrent.ScheduledExecutorService
import android.provider.Settings
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.core.net.toUri
import cc.goll.caff.utils.HumanReadableTime
import java.util.TreeSet
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.TreeSet
import java.util.Timer

import cc.goll.caff.utils.HumanReadableTime
import java.util.concurrent.TimeUnit


class CaffeineTileService : TileService() {
Expand Down Expand Up @@ -111,7 +109,7 @@ class CaffeineTileService : TileService() {
}


private final val tileUpdateScheduler: ScheduledExecutorService =
private val tileUpdateScheduler: ScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor()

private var tileUpdateFuture: ScheduledFuture<*>? = null
Expand Down Expand Up @@ -185,9 +183,27 @@ class CaffeineTileService : TileService() {
}


@SuppressLint("StartActivityAndCollapseDeprecated")
override fun onClick() {
super.onClick()

if (!Settings.canDrawOverlays(this)) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
"package:$packageName".toUri()
)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

if (Build.VERSION.SDK_INT >= 34) {
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
startActivityAndCollapse(pendingIntent)
} else {
@Suppress("DEPRECATION")
startActivityAndCollapse(intent)
}
return
}

if (batteryOptimizations || !::caffeine.isInitialized) {
qsTile.state = Tile.STATE_UNAVAILABLE
qsTile.updateTile()
Expand Down