Compare commits
2 Commits
745f9fb046
...
6d22bee440
Author | SHA1 | Date |
---|---|---|
Brandon Presley | 6d22bee440 | |
Joseph | 58b8488a27 |
|
@ -87,8 +87,8 @@ android {
|
|||
applicationId "com.massive"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 36246
|
||||
versionName "2.31"
|
||||
versionCode 36247
|
||||
versionName "2.32"
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_NETWORK_STATE"
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -28,8 +28,7 @@ class TimerService : Service() {
|
|||
|
||||
private lateinit var timerHandler: Handler
|
||||
private var timerRunnable: Runnable? = null
|
||||
private var secondsLeft: Int = 0
|
||||
private var secondsTotal: Int = 0
|
||||
private var timer: Timer = Timer.emptyTimer()
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var vibrator: Vibrator? = null
|
||||
private var currentDescription = ""
|
||||
|
@ -38,6 +37,10 @@ class TimerService : Service() {
|
|||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
Log.d("TimerService", "Received stop broadcast intent")
|
||||
timer.stop(applicationContext)
|
||||
timer.expire()
|
||||
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
@ -45,9 +48,8 @@ class TimerService : Service() {
|
|||
private val addReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
secondsLeft += 60;
|
||||
secondsTotal += 60;
|
||||
updateNotification(secondsLeft)
|
||||
timer.increaseDuration(applicationContext, Timer.ONE_MINUTE_MILLI)
|
||||
updateNotification(timer.getRemainingSeconds())
|
||||
mediaPlayer?.stop()
|
||||
vibrator?.cancel()
|
||||
}
|
||||
|
@ -69,49 +71,62 @@ class TimerService : Service() {
|
|||
} else {
|
||||
applicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
|
||||
applicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
private fun onTimerStart(intent: Intent?) {
|
||||
timerRunnable?.let { timerHandler.removeCallbacks(it) }
|
||||
secondsLeft = (intent?.getIntExtra("milliseconds", 0) ?: 0) / 1000
|
||||
currentDescription = intent?.getStringExtra("description").toString()
|
||||
secondsTotal = secondsLeft
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
timer.stop(applicationContext)
|
||||
timer = Timer((intent?.getIntExtra("milliseconds", 0) ?: 0).toLong())
|
||||
timer.start(applicationContext)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(ONGOING_ID, getProgress(secondsLeft).build(), FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
} else
|
||||
{
|
||||
startForeground(ONGOING_ID, getProgress(secondsLeft).build())
|
||||
startForeground(
|
||||
ONGOING_ID,
|
||||
getProgress(timer.getRemainingSeconds()).build(),
|
||||
FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
)
|
||||
} else {
|
||||
startForeground(ONGOING_ID, getProgress(timer.getRemainingSeconds()).build())
|
||||
}
|
||||
|
||||
battery()
|
||||
Log.d("TimerService", "onStartCommand seconds=$secondsTotal")
|
||||
Log.d("TimerService", "onTimerStart seconds=${timer.getDurationSeconds()}")
|
||||
|
||||
timerRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
val millisElapsed = System.currentTimeMillis() - startTime
|
||||
val secondsElapsed = (millisElapsed / 1000).toInt()
|
||||
if (secondsElapsed < secondsTotal) {
|
||||
secondsLeft = secondsTotal - secondsElapsed
|
||||
updateNotification(secondsLeft)
|
||||
timerHandler.postDelayed(this, 1000 - millisElapsed % 1000)
|
||||
} else {
|
||||
val settings = getSettings()
|
||||
vibrate(settings)
|
||||
playSound(settings)
|
||||
notifyFinished()
|
||||
}
|
||||
val startTime = SystemClock.elapsedRealtime()
|
||||
if (timer.isExpired()) return
|
||||
updateNotification(timer.getRemainingSeconds())
|
||||
|
||||
val delay = timer.getRemainingMillis() % 1000
|
||||
timerHandler.postDelayed(this, if (SystemClock.elapsedRealtime() - startTime + delay > 980) 20 else delay)
|
||||
}
|
||||
}
|
||||
timerHandler.postDelayed(timerRunnable!!, 1000)
|
||||
timerHandler.postDelayed(timerRunnable!!, 20)
|
||||
}
|
||||
|
||||
private fun onTimerExpired() {
|
||||
Log.d("TimerService", "onTimerExpired duration=${timer.getDurationSeconds()}")
|
||||
timer.expire()
|
||||
|
||||
val settings = getSettings()
|
||||
vibrate(settings)
|
||||
playSound(settings)
|
||||
notifyFinished()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent != null && intent.action == TIMER_EXPIRED) onTimerExpired()
|
||||
else onTimerStart(intent)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
timerHandler.removeCallbacks(timerRunnable!!)
|
||||
timerRunnable?.let { timerHandler.removeCallbacks(it) }
|
||||
applicationContext.unregisterReceiver(stopReceiver)
|
||||
applicationContext.unregisterReceiver(addReceiver)
|
||||
mediaPlayer?.stop()
|
||||
|
@ -217,7 +232,7 @@ class TimerService : Service() {
|
|||
.setContentTitle(currentDescription)
|
||||
.setContentText(notificationText)
|
||||
.setSmallIcon(R.drawable.ic_baseline_timer_24)
|
||||
.setProgress(secondsTotal, timeLeftInSeconds, false)
|
||||
.setProgress(timer.getDurationSeconds(), timeLeftInSeconds, false)
|
||||
.setContentIntent(contentPending)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.setAutoCancel(false)
|
||||
|
@ -261,7 +276,11 @@ class TimerService : Service() {
|
|||
val notificationManager = NotificationManagerCompat.from(this)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel =
|
||||
NotificationChannel(channelId, "Timer Finished Channel", NotificationManager.IMPORTANCE_HIGH)
|
||||
NotificationChannel(
|
||||
channelId,
|
||||
"Timer Finished Channel",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
channel.setSound(null, null)
|
||||
channel.setBypassDnd(true)
|
||||
channel.enableVibration(false)
|
||||
|
@ -332,6 +351,7 @@ class TimerService : Service() {
|
|||
companion object {
|
||||
const val STOP_BROADCAST = "stop-timer-event"
|
||||
const val ADD_BROADCAST = "add-timer-event"
|
||||
const val TIMER_EXPIRED = "timer-expired-event"
|
||||
const val ONGOING_ID = 1
|
||||
const val FINISHED_ID = 1
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "massive",
|
||||
"version": "2.31",
|
||||
"version": "2.32",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
|
|
Loading…
Reference in New Issue