Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3f6ddae
Disable notification auto cancelling
naveensingh Mar 18, 2025
01c6df4
Add content descriptions to buttons
naveensingh Mar 21, 2025
fe498c9
Minor code formatting
naveensingh Mar 21, 2025
7a7e53e
Use a foreground service for sounding alarms
naveensingh Mar 21, 2025
677ec58
Remove unused timer related code from reminder activity
naveensingh Mar 21, 2025
dd5762c
Remove notification channel deletion
naveensingh Mar 21, 2025
9fa209d
Format code
naveensingh Mar 21, 2025
129abc1
Remove invalid parameter
naveensingh Mar 21, 2025
f3ab660
Minor code improvement
naveensingh Mar 21, 2025
1b39dcc
Don't dismiss alarms automatically
naveensingh Mar 21, 2025
4ae0569
Use descriptive name
naveensingh Mar 21, 2025
3ffbbfb
Merge branch 'master' into fix_silent_alarms
naveensingh Mar 23, 2025
7a15de5
Merge branch 'master' into fix_silent_alarms
naveensingh Mar 26, 2025
1c2b4d9
Minor readability improvement
naveensingh Mar 26, 2025
9a434f4
Update name in manifest
naveensingh Mar 26, 2025
3327af6
Update name in Context.kt 🤦
naveensingh Mar 26, 2025
1b41438
Format some code
naveensingh Mar 28, 2025
555162c
Rework/refactor alarm management
naveensingh Mar 28, 2025
20ed501
More code improvements
naveensingh Mar 28, 2025
16a68b1
Rename reminder activity to alarm activity
naveensingh Mar 28, 2025
733a4f6
Only reschedule recurring alarms
naveensingh Mar 28, 2025
8c3ceeb
Finish alarm activity when alarm is stopped
naveensingh Mar 28, 2025
179b36c
Remove unnecessary setting of firstDayOfWeek
naveensingh Mar 28, 2025
6e5d79d
Auto dismiss alarms properly
naveensingh Mar 29, 2025
ff13f85
Move register/unregister calls to onCreate/onDestroy
naveensingh Mar 29, 2025
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
16 changes: 12 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
android:windowSoftInputMode="adjustPan" />

<activity
android:name=".activities.ReminderActivity"
android:name=".activities.AlarmActivity"
android:configChanges="orientation|screenSize|screenLayout"
android:excludeFromRecents="true"
android:exported="false"
Expand Down Expand Up @@ -141,6 +141,14 @@
</intent-filter>
</activity>

<service
android:name=".services.AlarmService"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Used to notify the user that alarms are running" />
</service>

<service android:name=".services.SnoozeService" />

<service
Expand All @@ -163,11 +171,11 @@

<receiver android:name=".receivers.HideTimerReceiver" />

<receiver android:name=".receivers.HideAlarmReceiver" />
<receiver android:name=".receivers.StopAlarmReceiver" />

<receiver android:name=".receivers.DismissAlarmReceiver" />
<receiver android:name=".receivers.SkipUpcomingAlarmReceiver" />

<receiver android:name=".receivers.EarlyAlarmDismissalReceiver" />
<receiver android:name=".receivers.UpcomingAlarmReceiver" />

<receiver
android:name=".receivers.BootCompletedReceiver"
Expand Down
24 changes: 17 additions & 7 deletions app/src/main/kotlin/org/fossify/clock/App.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.fossify.clock

import android.app.Application
import android.app.NotificationManager
import android.content.Context
import android.os.CountDownTimer
import android.os.Handler
import android.os.Looper
Expand All @@ -11,7 +9,11 @@ import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import com.facebook.stetho.Stetho
import org.fossify.clock.extensions.*
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.getOpenTimerTabIntent
import org.fossify.clock.extensions.getTimerNotification
import org.fossify.clock.extensions.hideNotification
import org.fossify.clock.extensions.timerHelper
import org.fossify.clock.helpers.Stopwatch
import org.fossify.clock.helpers.Stopwatch.State
import org.fossify.clock.models.TimerEvent
Expand All @@ -21,6 +23,7 @@ import org.fossify.clock.services.TimerStopService
import org.fossify.clock.services.startStopwatchService
import org.fossify.clock.services.startTimerService
import org.fossify.commons.extensions.checkUseEnglish
import org.fossify.commons.extensions.notificationManager
import org.fossify.commons.extensions.showErrorToast
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
Expand Down Expand Up @@ -66,7 +69,12 @@ class App : Application(), LifecycleObserver {
val runningTimers = timers.filter { it.state is TimerState.Running }
runningTimers.forEach { timer ->
if (countDownTimers[timer.id] == null) {
EventBus.getDefault().post(TimerEvent.Start(timer.id!!, (timer.state as TimerState.Running).tick))
EventBus.getDefault().post(
TimerEvent.Start(
timerId = timer.id!!,
duration = (timer.state as TimerState.Running).tick
)
)
}
}
}
Expand Down Expand Up @@ -108,8 +116,7 @@ class App : Application(), LifecycleObserver {
fun onMessageEvent(event: TimerEvent.Finish) {
timerHelper.getTimer(event.timerId) { timer ->
val pendingIntent = getOpenTimerTabIntent(event.timerId)
val notification = getTimerNotification(timer, pendingIntent, false)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = getTimerNotification(timer, pendingIntent)

try {
notificationManager.notify(event.timerId, notification)
Expand All @@ -127,7 +134,10 @@ class App : Application(), LifecycleObserver {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: TimerEvent.Pause) {
timerHelper.getTimer(event.timerId) { timer ->
updateTimerState(event.timerId, TimerState.Paused(event.duration, (timer.state as TimerState.Running).tick))
updateTimerState(
event.timerId,
TimerState.Paused(event.duration, (timer.state as TimerState.Running).tick)
)
countDownTimers[event.timerId]?.cancel()
}
}
Expand Down
236 changes: 236 additions & 0 deletions app/src/main/kotlin/org/fossify/clock/activities/AlarmActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package org.fossify.clock.activities

import android.annotation.SuppressLint
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.AlarmClock
import android.view.MotionEvent
import android.view.WindowManager
import android.view.animation.AnimationUtils
import org.fossify.clock.R
import org.fossify.clock.databinding.ActivityAlarmBinding
import org.fossify.clock.extensions.alarmController
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.dbHelper
import org.fossify.clock.extensions.getFormattedTime
import org.fossify.clock.helpers.ALARM_ID
import org.fossify.clock.helpers.getPassedSeconds
import org.fossify.clock.models.Alarm
import org.fossify.clock.models.AlarmEvent
import org.fossify.commons.extensions.applyColorFilter
import org.fossify.commons.extensions.getProperBackgroundColor
import org.fossify.commons.extensions.getProperPrimaryColor
import org.fossify.commons.extensions.getProperTextColor
import org.fossify.commons.extensions.onGlobalLayout
import org.fossify.commons.extensions.performHapticFeedback
import org.fossify.commons.extensions.showPickSecondsDialog
import org.fossify.commons.extensions.updateTextColors
import org.fossify.commons.extensions.viewBinding
import org.fossify.commons.helpers.MINUTE_SECONDS
import org.fossify.commons.helpers.isOreoMr1Plus
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import kotlin.math.max
import kotlin.math.min

class AlarmActivity : SimpleActivity() {

private val swipeGuideFadeHandler = Handler(Looper.getMainLooper())
private var alarm: Alarm? = null
private var didVibrate = false
private var dragDownX = 0f

private val binding by viewBinding(ActivityAlarmBinding::inflate)

override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
setContentView(binding.root)
showOverLockscreen()
updateTextColors(binding.root)
updateStatusbarColor(getProperBackgroundColor())

val id = intent.getIntExtra(ALARM_ID, -1)
alarm = dbHelper.getAlarmWithId(id)
if (alarm == null) {
finish()
return
}

val label = alarm!!.label.ifEmpty {
getString(org.fossify.commons.R.string.alarm)
}

binding.reminderTitle.text = label
binding.reminderText.text = getFormattedTime(
passedSeconds = getPassedSeconds(),
showSeconds = false,
makeAmPmSmaller = false
)

setupAlarmButtons()
EventBus.getDefault().register(this)
}

@SuppressLint("ClickableViewAccessibility")
private fun setupAlarmButtons() {
binding.reminderDraggableBackground.startAnimation(
AnimationUtils.loadAnimation(this, R.anim.pulsing_animation)
)
binding.reminderDraggableBackground.applyColorFilter(getProperPrimaryColor())

val textColor = getProperTextColor()
binding.reminderDismiss.applyColorFilter(textColor)
binding.reminderDraggable.applyColorFilter(textColor)
binding.reminderSnooze.applyColorFilter(textColor)

var minDragX = 0f
var maxDragX = 0f
var initialDraggableX = 0f

binding.reminderDismiss.onGlobalLayout {
minDragX = binding.reminderSnooze.left.toFloat()
maxDragX = binding.reminderDismiss.left.toFloat()
initialDraggableX = binding.reminderDraggable.left.toFloat()
}

binding.reminderDraggable.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
dragDownX = event.x
binding.reminderDraggableBackground.animate().alpha(0f)
}

MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
dragDownX = 0f
if (!didVibrate) {
binding.reminderDraggable.animate().x(initialDraggableX).withEndAction {
binding.reminderDraggableBackground.animate().alpha(0.2f)
}

binding.reminderGuide.animate().alpha(1f).start()
swipeGuideFadeHandler.removeCallbacksAndMessages(null)
swipeGuideFadeHandler.postDelayed({
binding.reminderGuide.animate().alpha(0f).start()
}, 2000L)
}
}

MotionEvent.ACTION_MOVE -> {
binding.reminderDraggable.x = min(
a = maxDragX,
b = max(minDragX, event.rawX - dragDownX)
)

if (binding.reminderDraggable.x >= maxDragX - 50f) {
if (!didVibrate) {
binding.reminderDraggable.performHapticFeedback()
didVibrate = true
dismissAlarmAndFinish()
}
} else if (binding.reminderDraggable.x <= minDragX + 50f) {
if (!didVibrate) {
binding.reminderDraggable.performHapticFeedback()
didVibrate = true
snoozeAlarm()
}
}
}
}
true
}
}

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
setupAlarmButtons()
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
when (intent?.action) {
AlarmClock.ACTION_DISMISS_ALARM -> dismissAlarmAndFinish()
AlarmClock.ACTION_SNOOZE_ALARM -> {
val durationMinutes = intent.getIntExtra(AlarmClock.EXTRA_ALARM_SNOOZE_DURATION, -1)
if (durationMinutes == -1) {
snoozeAlarm()
} else {
snoozeAlarm(durationMinutes)
}
}

else -> {
// no-op. user probably clicked the notification
}
}
}

override fun onDestroy() {
super.onDestroy()
swipeGuideFadeHandler.removeCallbacksAndMessages(null)
EventBus.getDefault().unregister(this)
}

private fun snoozeAlarm(overrideSnoozeDuration: Int? = null) {
if (overrideSnoozeDuration != null) {
dismissAlarmAndFinish(overrideSnoozeDuration)
} else if (config.useSameSnooze) {
dismissAlarmAndFinish(config.snoozeTime)
} else {
alarmController.stopAlarm(alarmId = alarm!!.id, disable = false)
showPickSecondsDialog(
curSeconds = config.snoozeTime * MINUTE_SECONDS,
isSnoozePicker = true,
cancelCallback = {
dismissAlarmAndFinish()
},
callback = {
config.snoozeTime = it / MINUTE_SECONDS
dismissAlarmAndFinish(config.snoozeTime)
}
)
}
}

private fun dismissAlarmAndFinish(snoozeMinutes: Int = -1) {
if (alarm != null) {
if (snoozeMinutes != -1) {
alarmController.snoozeAlarm(alarm!!.id, snoozeMinutes)
} else {
alarmController.stopAlarm(alarm!!.id)
}
}

finishActivity()
}

private fun showOverLockscreen() {
window.addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)

if (isOreoMr1Plus()) {
setShowWhenLocked(true)
setTurnScreenOn(true)
}
}

@Subscribe(threadMode = ThreadMode.MAIN)
fun onAlarmStoppedEvent(event: AlarmEvent.Stopped) {
if (event.alarmId == alarm?.id && !isFinishing) {
finishActivity()
}
}

private fun finishActivity() {
finish()
overridePendingTransition(0, 0)
}
}
Loading
Loading