172 lines
5.4 KiB
Kotlin
172 lines
5.4 KiB
Kotlin
package com.massive
|
|
|
|
import android.app.AlarmManager
|
|
import android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP
|
|
import android.app.PendingIntent
|
|
import android.content.ActivityNotFoundException
|
|
import android.content.BroadcastReceiver
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.IntentFilter
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.SystemClock
|
|
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
|
|
import android.widget.Toast
|
|
import androidx.annotation.RequiresApi
|
|
|
|
@RequiresApi(Build.VERSION_CODES.O)
|
|
class Timer(private var msTimerDuration: Long) {
|
|
|
|
enum class State {
|
|
Running,
|
|
Paused,
|
|
Expired
|
|
}
|
|
|
|
fun start(context: Context) {
|
|
if (state != State.Paused) return
|
|
endTime = SystemClock.elapsedRealtime() + msTimerDuration
|
|
registerPendingIntent(context)
|
|
state = State.Running
|
|
}
|
|
|
|
fun stop(context: Context) {
|
|
if (state != State.Running) return
|
|
msTimerDuration = endTime - SystemClock.elapsedRealtime()
|
|
unregisterPendingIntent(context)
|
|
state = State.Paused
|
|
}
|
|
|
|
fun expire() {
|
|
state = State.Expired
|
|
msTimerDuration = 0
|
|
totalTimerDuration = 0
|
|
}
|
|
|
|
fun getRemainingSeconds(): Int {
|
|
return (getRemainingMillis() / 1000).toInt()
|
|
}
|
|
|
|
fun increaseDuration(context: Context, milli: Long) {
|
|
val wasRunning = isRunning()
|
|
if (wasRunning) stop(context)
|
|
msTimerDuration += milli
|
|
totalTimerDuration += milli
|
|
if (wasRunning) start(context)
|
|
}
|
|
|
|
fun isExpired(): Boolean {
|
|
return state == State.Expired
|
|
}
|
|
|
|
fun getDurationSeconds(): Int {
|
|
return (totalTimerDuration / 1000).toInt()
|
|
}
|
|
|
|
fun getRemainingMillis(): Long {
|
|
return if (state == State.Running) endTime - SystemClock.elapsedRealtime()
|
|
else
|
|
msTimerDuration
|
|
}
|
|
|
|
private fun isRunning(): Boolean {
|
|
return state == State.Running
|
|
}
|
|
|
|
private fun requestPermission(context: Context): Boolean {
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
|
|
val intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
|
|
intent.data = Uri.parse("package:" + context.packageName)
|
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
return try {
|
|
val receiver = object : BroadcastReceiver() {
|
|
override fun onReceive(context2: Context?, intent: Intent?) {
|
|
context.unregisterReceiver(this)
|
|
registerPendingIntent(context)
|
|
}
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
context.registerReceiver(
|
|
receiver,
|
|
IntentFilter(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED),
|
|
Context.RECEIVER_NOT_EXPORTED
|
|
)
|
|
} else {
|
|
context.registerReceiver(
|
|
receiver,
|
|
IntentFilter(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED)
|
|
)
|
|
}
|
|
|
|
context.startActivity(intent)
|
|
false
|
|
} catch (e: ActivityNotFoundException) {
|
|
Toast.makeText(
|
|
context,
|
|
"Request for SCHEDULE_EXACT_ALARM rejected on your device",
|
|
Toast.LENGTH_LONG
|
|
).show()
|
|
false
|
|
}
|
|
}
|
|
|
|
private fun incorrectPermissions(context: Context, alarmManager: AlarmManager): Boolean {
|
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
|
&& !alarmManager.canScheduleExactAlarms()
|
|
&& !requestPermission(context)
|
|
}
|
|
|
|
private fun getAlarmManager(context: Context): AlarmManager {
|
|
return context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
|
}
|
|
|
|
private fun unregisterPendingIntent(context: Context) {
|
|
val intent = Intent(context, TimerService::class.java)
|
|
.setAction(TimerService.TIMER_EXPIRED)
|
|
val pendingIntent = PendingIntent.getService(
|
|
context,
|
|
0,
|
|
intent,
|
|
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
|
|
)
|
|
val alarmManager = getAlarmManager(context)
|
|
if (incorrectPermissions(context, alarmManager)) return
|
|
|
|
alarmManager.cancel(pendingIntent)
|
|
pendingIntent.cancel()
|
|
}
|
|
|
|
private fun registerPendingIntent(context: Context) {
|
|
val intent = Intent(context, TimerService::class.java)
|
|
.setAction(TimerService.TIMER_EXPIRED)
|
|
val pendingIntent = PendingIntent.getService(
|
|
context,
|
|
0,
|
|
intent,
|
|
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
)
|
|
val alarmManager = getAlarmManager(context)
|
|
if (incorrectPermissions(context, alarmManager)) return
|
|
|
|
alarmManager.setExactAndAllowWhileIdle(
|
|
ELAPSED_REALTIME_WAKEUP,
|
|
endTime,
|
|
pendingIntent
|
|
)
|
|
}
|
|
|
|
private var endTime: Long = 0
|
|
private var totalTimerDuration: Long = msTimerDuration
|
|
private var state: State = State.Paused
|
|
|
|
companion object {
|
|
fun emptyTimer(): Timer {
|
|
return Timer(0)
|
|
}
|
|
|
|
const val ONE_MINUTE_MILLI: Long = 60000
|
|
}
|
|
}
|