Fix dismissing alarm and add +1 minute button
This commit is contained in:
parent
5355b0eb6a
commit
47cfaa4b67
|
@ -11,43 +11,40 @@
|
|||
android:name="android.permission.ACCESS_NETWORK_STATE"
|
||||
tools:node="remove" />
|
||||
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove"/>
|
||||
<uses-permission
|
||||
android:name="com.google.android.gms.permission.AD_ID"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
android:name=".MainApplication"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".TimerDone"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".TimerDone"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".StopAlarm"
|
||||
android:exported="true"
|
||||
android:process=":remote" />
|
||||
|
||||
<service
|
||||
android:name=".AlarmService"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".StopAlarm"
|
||||
android:exported="true"
|
||||
android:process=":remote" />
|
||||
|
||||
<service
|
||||
android:name=".TimerService"
|
||||
|
|
|
@ -1,217 +1,25 @@
|
|||
package com.massive
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.CountDownTimer
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.facebook.react.bridge.*
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule
|
||||
import kotlin.math.floor
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class AlarmModule(context: ReactApplicationContext?) :
|
||||
ReactContextBaseJavaModule(context), LifecycleEventListener {
|
||||
|
||||
private var countdownTimer: CountDownTimer? = null
|
||||
var currentMs: Long = 0
|
||||
private var running = false
|
||||
private var currentDescription = ""
|
||||
ReactContextBaseJavaModule(context) {
|
||||
|
||||
override fun getName(): String {
|
||||
return "AlarmModule"
|
||||
}
|
||||
|
||||
private val stopReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
Log.d("AlarmModule", "Received stop broadcast intent")
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
private val addReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
add()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
reactApplicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
reactApplicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
}
|
||||
else {
|
||||
reactApplicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
|
||||
reactApplicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHostDestroy() {
|
||||
reactApplicationContext.unregisterReceiver(stopReceiver)
|
||||
reactApplicationContext.unregisterReceiver(addReceiver)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun add() {
|
||||
Log.d("AlarmModule", "Add 1 min to alarm.")
|
||||
countdownTimer?.cancel()
|
||||
val newMs = if (running) currentMs.toInt().plus(60000) else 60000
|
||||
countdownTimer = getTimer(newMs)
|
||||
countdownTimer?.start()
|
||||
running = true
|
||||
val manager = getManager()
|
||||
manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
|
||||
val intent = Intent(reactApplicationContext, AlarmService::class.java)
|
||||
reactApplicationContext.stopService(intent)
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
fun getCurrent(): Int {
|
||||
Log.d("AlarmModule", "currentMs=$currentMs")
|
||||
if (running) return currentMs.toInt()
|
||||
return 0
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun stop() {
|
||||
Log.d("AlarmModule", "Stop alarm.")
|
||||
countdownTimer?.cancel()
|
||||
running = false
|
||||
val intent = Intent(reactApplicationContext, AlarmService::class.java)
|
||||
reactApplicationContext?.stopService(intent)
|
||||
val manager = getManager()
|
||||
manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
|
||||
manager.cancel(NOTIFICATION_ID_PENDING)
|
||||
val params =
|
||||
Arguments.createMap().apply {
|
||||
putString("minutes", "00")
|
||||
putString("seconds", "00")
|
||||
}
|
||||
reactApplicationContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit("tick", params)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun timer(milliseconds: Int, description: String) {
|
||||
Log.d("AlarmModule", "Queue alarm for $milliseconds delay")
|
||||
val intent = Intent(reactApplicationContext, TimerService::class.java)
|
||||
intent.putExtra("milliseconds", milliseconds)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
reactApplicationContext.startForegroundService(intent)
|
||||
}
|
||||
else {
|
||||
reactApplicationContext.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTimer(
|
||||
endMs: Int,
|
||||
): CountDownTimer {
|
||||
val builder = getBuilder()
|
||||
return object : CountDownTimer(endMs.toLong(), 1000) {
|
||||
override fun onTick(current: Long) {
|
||||
currentMs = current
|
||||
val seconds =
|
||||
floor((current / 1000).toDouble() % 60).toInt().toString().padStart(2, '0')
|
||||
val minutes =
|
||||
floor((current / 1000).toDouble() / 60).toInt().toString().padStart(2, '0')
|
||||
builder.setContentText("$minutes:$seconds")
|
||||
.setAutoCancel(false)
|
||||
.setDefaults(0)
|
||||
.setProgress(endMs, current.toInt(), false)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.priority = NotificationCompat.PRIORITY_LOW
|
||||
val manager = getManager()
|
||||
manager.notify(NOTIFICATION_ID_PENDING, builder.build())
|
||||
val params =
|
||||
Arguments.createMap().apply {
|
||||
putString("minutes", minutes)
|
||||
putString("seconds", seconds)
|
||||
}
|
||||
reactApplicationContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit("tick", params)
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
val context = reactApplicationContext
|
||||
val intent = Intent(context, AlarmService::class.java)
|
||||
context.startService(intent)
|
||||
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit(
|
||||
"tick",
|
||||
Arguments.createMap().apply {
|
||||
putString("minutes", "00")
|
||||
putString("seconds", "00")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
private fun getBuilder(): NotificationCompat.Builder {
|
||||
val context = reactApplicationContext
|
||||
val contentIntent = Intent(context, MainActivity::class.java)
|
||||
val pendingContent =
|
||||
PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val addBroadcast = Intent(ADD_BROADCAST).apply { setPackage(context.packageName) }
|
||||
val pendingAdd =
|
||||
PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE)
|
||||
val stopBroadcast = Intent(STOP_BROADCAST)
|
||||
stopBroadcast.setPackage(context.packageName)
|
||||
val pendingStop =
|
||||
PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE)
|
||||
return NotificationCompat.Builder(context, CHANNEL_ID_PENDING)
|
||||
.setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24)
|
||||
.setContentTitle(currentDescription)
|
||||
.setContentIntent(pendingContent)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd)
|
||||
.setDeleteIntent(pendingStop)
|
||||
}
|
||||
|
||||
private fun getManager(): NotificationManager {
|
||||
val notificationManager =
|
||||
reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val timersChannel =
|
||||
NotificationChannel(
|
||||
CHANNEL_ID_PENDING,
|
||||
CHANNEL_ID_PENDING,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
timersChannel.setSound(null, null)
|
||||
timersChannel.description = "Progress on rest timers."
|
||||
notificationManager.createNotificationChannel(timersChannel)
|
||||
}
|
||||
|
||||
return notificationManager
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STOP_BROADCAST = "stop-timer-event"
|
||||
const val ADD_BROADCAST = "add-timer-event"
|
||||
const val CHANNEL_ID_PENDING = "Timer"
|
||||
const val NOTIFICATION_ID_PENDING = 1
|
||||
}
|
||||
|
||||
override fun onHostResume() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onHostPause() {
|
||||
TODO("Not yet implemented")
|
||||
intent.putExtra("description", description)
|
||||
reactApplicationContext.startForegroundService(intent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
package com.massive
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaPlayer
|
||||
import android.media.MediaPlayer.OnPreparedListener
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
||||
class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean, val duration: Long)
|
||||
|
||||
class AlarmService : Service(), OnPreparedListener {
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var vibrator: Vibrator? = null
|
||||
|
||||
private fun getBuilder(): NotificationCompat.Builder {
|
||||
val context = applicationContext
|
||||
val contentIntent = Intent(context, MainActivity::class.java)
|
||||
val pendingContent =
|
||||
PendingIntent.getActivity(context, 0, contentIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val addBroadcast = Intent(AlarmModule.ADD_BROADCAST).apply {
|
||||
setPackage(context.packageName)
|
||||
}
|
||||
val pendingAdd =
|
||||
PendingIntent.getBroadcast(context, 0, addBroadcast, PendingIntent.FLAG_MUTABLE)
|
||||
val stopBroadcast = Intent(AlarmModule.STOP_BROADCAST)
|
||||
stopBroadcast.setPackage(context.packageName)
|
||||
val pendingStop =
|
||||
PendingIntent.getBroadcast(context, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE)
|
||||
return NotificationCompat.Builder(context, AlarmModule.CHANNEL_ID_PENDING)
|
||||
.setSmallIcon(R.drawable.ic_baseline_hourglass_bottom_24).setContentTitle("Resting")
|
||||
.setSound(null)
|
||||
.setContentIntent(pendingContent)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd)
|
||||
.setDeleteIntent(pendingStop)
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("Range")
|
||||
private fun getSettings(): Settings {
|
||||
val db = DatabaseHelper(applicationContext).readableDatabase
|
||||
val cursor = db.rawQuery("SELECT sound, noSound, vibrate, duration FROM settings", null)
|
||||
cursor.moveToFirst()
|
||||
val sound = cursor.getString(cursor.getColumnIndex("sound"))
|
||||
val noSound = cursor.getInt(cursor.getColumnIndex("noSound")) == 1
|
||||
val vibrate = cursor.getInt(cursor.getColumnIndex("vibrate")) == 1
|
||||
var duration = cursor.getLong(cursor.getColumnIndex("duration"))
|
||||
if (duration.toInt() == 0) duration = 300
|
||||
cursor.close()
|
||||
return Settings(sound, noSound, vibrate, duration)
|
||||
}
|
||||
|
||||
private fun playSound(settings: Settings) {
|
||||
if (settings.noSound) return
|
||||
if (settings.sound == null) {
|
||||
mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon)
|
||||
mediaPlayer?.start()
|
||||
mediaPlayer?.setOnCompletionListener { vibrator?.cancel() }
|
||||
} else {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
setDataSource(applicationContext, Uri.parse(settings.sound))
|
||||
prepare()
|
||||
start()
|
||||
setOnCompletionListener { vibrator?.cancel() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doNotify(): Notification {
|
||||
val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val alarmsChannel = NotificationChannel(
|
||||
CHANNEL_ID_DONE,
|
||||
CHANNEL_ID_DONE,
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
alarmsChannel.description = "Alarms for rest timers."
|
||||
alarmsChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
alarmsChannel.setSound(null, null)
|
||||
manager.createNotificationChannel(alarmsChannel)
|
||||
}
|
||||
|
||||
val builder = getBuilder()
|
||||
val context = applicationContext
|
||||
val finishIntent = Intent(context, StopAlarm::class.java)
|
||||
val finishPending = PendingIntent.getActivity(
|
||||
context, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val fullIntent = Intent(context, TimerDone::class.java)
|
||||
val fullPending = PendingIntent.getActivity(
|
||||
context, 0, fullIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
builder.setContentText("Timer finished.").setProgress(0, 0, false)
|
||||
.setAutoCancel(true).setOngoing(true).setFullScreenIntent(fullPending, true)
|
||||
.setContentIntent(finishPending).setChannelId(CHANNEL_ID_DONE)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM).priority =
|
||||
NotificationCompat.PRIORITY_HIGH
|
||||
val notification = builder.build()
|
||||
manager.notify(NOTIFICATION_ID_DONE, notification)
|
||||
manager.cancel(AlarmModule.NOTIFICATION_ID_PENDING)
|
||||
return notification
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@SuppressLint("Recycle")
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
doNotify()
|
||||
val settings = getSettings()
|
||||
playSound(settings)
|
||||
if (!settings.vibrate) return START_STICKY
|
||||
val pattern = longArrayOf(0, settings.duration, 1000, settings.duration, 1000, settings.duration)
|
||||
vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||
vibratorManager.defaultVibrator
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
getSystemService(VIBRATOR_SERVICE) as Vibrator
|
||||
}
|
||||
vibrator!!.vibrate(VibrationEffect.createWaveform(pattern, -1))
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
handler.postDelayed({ vibrator!!.cancel() }, 10000)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onPrepared(player: MediaPlayer) {
|
||||
player.start()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mediaPlayer?.stop()
|
||||
mediaPlayer?.release()
|
||||
vibrator?.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID_DONE = "Alarm"
|
||||
const val NOTIFICATION_ID_DONE = 2
|
||||
}
|
||||
}
|
|
@ -1,17 +1,11 @@
|
|||
package com.massive
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.massive.AlarmService
|
||||
import com.massive.MainActivity
|
||||
|
||||
class StopAlarm : Activity() {
|
||||
@RequiresApi(Build.VERSION_CODES.O_MR1)
|
||||
|
@ -19,7 +13,7 @@ class StopAlarm : Activity() {
|
|||
Log.d("AlarmActivity", "Call to AlarmActivity")
|
||||
super.onCreate(savedInstanceState)
|
||||
val context = applicationContext
|
||||
context.stopService(Intent(context, AlarmService::class.java))
|
||||
context.stopService(Intent(context, TimerService::class.java))
|
||||
savedInstanceState.apply { setShowWhenLocked(true) }
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
package com.massive
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
@ -10,6 +7,7 @@ import android.util.Log
|
|||
import android.view.View
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
||||
class TimerDone : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -21,38 +19,11 @@ class TimerDone : AppCompatActivity() {
|
|||
@Suppress("UNUSED_PARAMETER")
|
||||
fun stop(view: View) {
|
||||
Log.d("TimerDone", "Stopping...")
|
||||
applicationContext.stopService(Intent(applicationContext, AlarmService::class.java))
|
||||
val manager = getManager()
|
||||
manager.cancel(AlarmService.NOTIFICATION_ID_DONE)
|
||||
manager.cancel(AlarmModule.NOTIFICATION_ID_PENDING)
|
||||
applicationContext.stopService(Intent(applicationContext, TimerService::class.java))
|
||||
val manager = NotificationManagerCompat.from(this)
|
||||
manager.cancel(TimerService.CHANNEL_ID)
|
||||
val intent = Intent(applicationContext, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
applicationContext.startActivity(intent)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun getManager(): NotificationManager {
|
||||
val alarmsChannel = NotificationChannel(
|
||||
AlarmService.CHANNEL_ID_DONE,
|
||||
AlarmService.CHANNEL_ID_DONE,
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
description = "Alarms for rest timers."
|
||||
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
}
|
||||
val timersChannel = NotificationChannel(
|
||||
AlarmModule.CHANNEL_ID_PENDING,
|
||||
AlarmModule.CHANNEL_ID_PENDING,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setSound(null, null)
|
||||
description = "Progress on rest timers."
|
||||
}
|
||||
val notificationManager = applicationContext.getSystemService(
|
||||
NotificationManager::class.java
|
||||
)
|
||||
notificationManager.createNotificationChannel(alarmsChannel)
|
||||
notificationManager.createNotificationChannel(timersChannel)
|
||||
return notificationManager
|
||||
}
|
||||
}
|
|
@ -8,19 +8,27 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
class Settings(val sound: String?, val noSound: Boolean, val vibrate: Boolean, val duration: Long)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class TimerService : Service() {
|
||||
|
||||
private lateinit var timerHandler: Handler
|
||||
private var timerRunnable: Runnable? = null
|
||||
private var timeLeftInSeconds: Int = 0
|
||||
private var timeTotalInSeconds: Int = 0
|
||||
private var notificationId = 1
|
||||
private var secondsLeft: Int = 0
|
||||
private var secondsTotal: Int = 0
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var vibrator: Vibrator? = null
|
||||
private var currentDescription = ""
|
||||
|
||||
private val stopReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
|
@ -30,6 +38,16 @@ class TimerService : Service() {
|
|||
}
|
||||
}
|
||||
|
||||
private val addReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
secondsLeft += 60;
|
||||
secondsTotal += 60;
|
||||
mediaPlayer?.stop()
|
||||
vibrator?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -37,27 +55,33 @@ class TimerService : Service() {
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
applicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
applicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
}
|
||||
else {
|
||||
applicationContext.registerReceiver(stopReceiver, IntentFilter(STOP_BROADCAST))
|
||||
applicationContext.registerReceiver(addReceiver, IntentFilter(ADD_BROADCAST))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
timeLeftInSeconds = (intent?.getIntExtra("milliseconds", 0) ?: 0) / 1000
|
||||
startForeground(notificationId, createNotification(timeLeftInSeconds))
|
||||
Log.d("TimerService", "onStartCommand seconds=$timeLeftInSeconds")
|
||||
timeTotalInSeconds = timeLeftInSeconds
|
||||
secondsLeft = (intent?.getIntExtra("milliseconds", 0) ?: 0) / 1000
|
||||
currentDescription = intent?.getStringExtra("description").toString()
|
||||
secondsTotal = secondsLeft
|
||||
startForeground(CHANNEL_ID, getProgress(secondsLeft).build())
|
||||
Log.d("TimerService", "onStartCommand seconds=$secondsLeft")
|
||||
|
||||
timerRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (timeLeftInSeconds > 0) {
|
||||
timeLeftInSeconds--
|
||||
updateNotification(timeLeftInSeconds)
|
||||
if (secondsLeft > 0) {
|
||||
secondsLeft--
|
||||
updateNotification(secondsLeft)
|
||||
timerHandler.postDelayed(this, 1000)
|
||||
} else {
|
||||
startAlarmService()
|
||||
stopSelf()
|
||||
val settings = getSettings()
|
||||
vibrate(settings)
|
||||
playSound(settings)
|
||||
notifyFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,14 +93,53 @@ class TimerService : Service() {
|
|||
super.onDestroy()
|
||||
timerHandler.removeCallbacks(timerRunnable!!)
|
||||
applicationContext.unregisterReceiver(stopReceiver)
|
||||
applicationContext.unregisterReceiver(addReceiver)
|
||||
mediaPlayer?.stop()
|
||||
mediaPlayer?.release()
|
||||
vibrator?.cancel()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createNotification(timeLeftInSeconds: Int): Notification {
|
||||
val notificationTitle = "Timer"
|
||||
@SuppressLint("Range")
|
||||
private fun getSettings(): Settings {
|
||||
val db = DatabaseHelper(applicationContext).readableDatabase
|
||||
val cursor = db.rawQuery("SELECT sound, noSound, vibrate, duration FROM settings", null)
|
||||
cursor.moveToFirst()
|
||||
val sound = cursor.getString(cursor.getColumnIndex("sound"))
|
||||
val noSound = cursor.getInt(cursor.getColumnIndex("noSound")) == 1
|
||||
val vibrate = cursor.getInt(cursor.getColumnIndex("vibrate")) == 1
|
||||
var duration = cursor.getLong(cursor.getColumnIndex("duration"))
|
||||
if (duration.toInt() == 0) duration = 300
|
||||
cursor.close()
|
||||
return Settings(sound, noSound, vibrate, duration)
|
||||
}
|
||||
|
||||
private fun playSound(settings: Settings) {
|
||||
if (settings.noSound) return
|
||||
if (settings.sound == null) {
|
||||
mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon)
|
||||
mediaPlayer?.start()
|
||||
mediaPlayer?.setOnCompletionListener { vibrator?.cancel() }
|
||||
} else {
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build()
|
||||
)
|
||||
setDataSource(applicationContext, Uri.parse(settings.sound))
|
||||
prepare()
|
||||
start()
|
||||
setOnCompletionListener { vibrator?.cancel() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getProgress(timeLeftInSeconds: Int): NotificationCompat.Builder {
|
||||
val notificationText = formatTime(timeLeftInSeconds)
|
||||
val notificationChannelId = "timer_channel"
|
||||
val notificationIntent = Intent(this, TimerService::class.java)
|
||||
|
@ -86,16 +149,19 @@ class TimerService : Service() {
|
|||
notificationIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val stopBroadcast = Intent(AlarmModule.STOP_BROADCAST)
|
||||
val stopBroadcast = Intent(STOP_BROADCAST)
|
||||
stopBroadcast.setPackage(applicationContext.packageName)
|
||||
val pendingStop =
|
||||
PendingIntent.getBroadcast(applicationContext, 0, stopBroadcast, PendingIntent.FLAG_IMMUTABLE)
|
||||
val addBroadcast = Intent(ADD_BROADCAST).apply { setPackage(applicationContext.packageName) }
|
||||
val pendingAdd =
|
||||
PendingIntent.getBroadcast(applicationContext, 0, addBroadcast, PendingIntent.FLAG_MUTABLE)
|
||||
|
||||
val notificationBuilder = NotificationCompat.Builder(this, notificationChannelId)
|
||||
.setContentTitle(notificationTitle)
|
||||
.setContentTitle(currentDescription)
|
||||
.setContentText(notificationText)
|
||||
.setSmallIcon(R.drawable.ic_baseline_timer_24)
|
||||
.setProgress(timeTotalInSeconds, timeLeftInSeconds, false)
|
||||
.setProgress(secondsTotal, timeLeftInSeconds, false)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.setAutoCancel(false)
|
||||
|
@ -104,6 +170,7 @@ class TimerService : Service() {
|
|||
.setOngoing(true)
|
||||
.setDeleteIntent(pendingStop)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Stop", pendingStop)
|
||||
.addAction(R.drawable.ic_baseline_stop_24, "Add 1 min", pendingAdd)
|
||||
|
||||
val notificationManager = NotificationManagerCompat.from(this)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
@ -115,27 +182,64 @@ class TimerService : Service() {
|
|||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
return notificationBuilder.build()
|
||||
return notificationBuilder
|
||||
}
|
||||
|
||||
private fun updateNotification(timeLeftInSeconds: Int) {
|
||||
private fun vibrate(settings: Settings) {
|
||||
if (!settings.vibrate) return
|
||||
val pattern = longArrayOf(0, settings.duration, 1000, settings.duration, 1000, settings.duration)
|
||||
vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||
vibratorManager.defaultVibrator
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
getSystemService(VIBRATOR_SERVICE) as Vibrator
|
||||
}
|
||||
vibrator!!.vibrate(VibrationEffect.createWaveform(pattern, -1))
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
handler.postDelayed({ vibrator!!.cancel() }, 10000)
|
||||
}
|
||||
|
||||
private fun notifyFinished() {
|
||||
val builder = getProgress(0)
|
||||
val fullIntent = Intent(applicationContext, TimerDone::class.java)
|
||||
val fullPending = PendingIntent.getActivity(
|
||||
applicationContext, 0, fullIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val finishIntent = Intent(applicationContext, StopAlarm::class.java)
|
||||
val finishPending = PendingIntent.getActivity(
|
||||
applicationContext, 0, finishIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
builder.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(finishPending)
|
||||
.setFullScreenIntent(fullPending, true)
|
||||
.setOngoing(false)
|
||||
.setProgress(0, 0, false)
|
||||
val notificationManager = NotificationManagerCompat.from(this)
|
||||
val notification = createNotification(timeLeftInSeconds)
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
// TODO: Consider calling
|
||||
// ActivityCompat#requestPermissions
|
||||
// here to request the missing permissions, and then overriding
|
||||
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
||||
// int[] grantResults)
|
||||
// to handle the case where the user grants the permission. See the documentation
|
||||
// for ActivityCompat#requestPermissions for more details.
|
||||
return
|
||||
}
|
||||
notificationManager.notify(notificationId, notification)
|
||||
notificationManager.notify(CHANNEL_ID, builder.build())
|
||||
}
|
||||
|
||||
private fun updateNotification(seconds: Int) {
|
||||
val notificationManager = NotificationManagerCompat.from(this)
|
||||
val notification = getProgress(seconds)
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
notificationManager.notify(CHANNEL_ID, notification.build())
|
||||
}
|
||||
|
||||
private fun formatTime(timeInSeconds: Int): String {
|
||||
|
@ -144,12 +248,9 @@ class TimerService : Service() {
|
|||
return String.format("%02d:%02d", minutes, seconds)
|
||||
}
|
||||
|
||||
private fun startAlarmService() {
|
||||
val intent = Intent(applicationContext, AlarmService::class.java)
|
||||
applicationContext.startService(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STOP_BROADCAST = "stop-timer-event"
|
||||
const val ADD_BROADCAST = "add-timer-event"
|
||||
const val CHANNEL_ID = 1
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue