From 47cfaa4b675d7dacba70d74cfab35df1373c0066 Mon Sep 17 00:00:00 2001 From: Brandon Presley Date: Fri, 16 Feb 2024 17:47:26 +1300 Subject: [PATCH] Fix dismissing alarm and add +1 minute button --- android/app/src/main/AndroidManifest.xml | 63 +++--- .../src/main/java/com/massive/AlarmModule.kt | 202 +----------------- .../src/main/java/com/massive/AlarmService.kt | 158 -------------- .../src/main/java/com/massive/StopAlarm.kt | 8 +- .../src/main/java/com/massive/TimerDone.kt | 37 +--- .../src/main/java/com/massive/TimerService.kt | 167 ++++++++++++--- 6 files changed, 174 insertions(+), 461 deletions(-) delete mode 100644 android/app/src/main/java/com/massive/AlarmService.kt diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6f7d651..dd9a78b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -11,43 +11,40 @@ android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove" /> - + - - - - - - + android:name=".MainApplication" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:theme="@style/AppTheme"> + + + + + + - - - + + + - - - + = 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) } } diff --git a/android/app/src/main/java/com/massive/AlarmService.kt b/android/app/src/main/java/com/massive/AlarmService.kt deleted file mode 100644 index a9c483e..0000000 --- a/android/app/src/main/java/com/massive/AlarmService.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/StopAlarm.kt b/android/app/src/main/java/com/massive/StopAlarm.kt index d4696a7..9c09913 100644 --- a/android/app/src/main/java/com/massive/StopAlarm.kt +++ b/android/app/src/main/java/com/massive/StopAlarm.kt @@ -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 diff --git a/android/app/src/main/java/com/massive/TimerDone.kt b/android/app/src/main/java/com/massive/TimerDone.kt index 92027c1..e0cbe1b 100644 --- a/android/app/src/main/java/com/massive/TimerDone.kt +++ b/android/app/src/main/java/com/massive/TimerDone.kt @@ -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 - } } \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/TimerService.kt b/android/app/src/main/java/com/massive/TimerService.kt index 71207fb..5133ead 100644 --- a/android/app/src/main/java/com/massive/TimerService.kt +++ b/android/app/src/main/java/com/massive/TimerService.kt @@ -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 } }