This commit is contained in:
2026-10-18 22:42:00 +01:00
commit 2df34ab938
95 changed files with 12358 additions and 0 deletions
@@ -0,0 +1,93 @@
package com.dcntrlized.alarm
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
/**
* The full-screen alarm takeover.
*
* Launched by a full-screen intent from the alarm notification, so it appears
* over the lock screen and turns the screen on. It reuses the main JS bundle
* and is pointed at the `LiveAlarm` route via the intent extras, so the takeover
* is the same React screen whether the app was foreground, background or dead.
*
* Manifest requirements that make this work (see AndroidManifest.xml):
* `launchMode=singleTask`, `excludeFromRecents=true`, `showWhenLocked` and
* `turnScreenOn` on the activity, and `USE_FULL_SCREEN_INTENT`.
*/
class AlarmActivity : ReactActivity() {
override fun getMainComponentName(): String = "AlarmClient"
override fun createReactActivityDelegate(): ReactActivityDelegate =
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
override fun onCreate(savedInstanceState: Bundle?) {
// Order matters: the flags must be set before the window is shown.
showOverLockScreen()
super.onCreate(savedInstanceState)
}
private fun showOverLockScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
@Suppress("DEPRECATION")
window.addFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
// Dismiss the keyguard where the OS permits it (a secure lock screen is
// still honoured — we never try to bypass a PIN).
val km = getSystemService(Context.KEYGUARD_SERVICE) as? KeyguardManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && km?.isKeyguardLocked == true) {
km.requestDismissKeyguard(this, null)
}
}
/** Hardware back is consumed: the takeover's only exits are in-app actions. */
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
// Intentionally swallowed — see the Live alarm takeover spec.
}
override fun onDestroy() {
AlarmPlayer.stop(this)
super.onDestroy()
}
companion object {
const val EXTRA_ALERT_ID = "alarm.alertId"
const val EXTRA_MESSAGE = "alarm.message"
const val EXTRA_AUTO_STOP = "alarm.autoStopSeconds"
fun intent(context: Context, alertId: String, message: String, autoStopSeconds: Int): Intent =
Intent(context, AlarmActivity::class.java).apply {
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
)
putExtra(EXTRA_ALERT_ID, alertId)
putExtra(EXTRA_MESSAGE, message)
putExtra(EXTRA_AUTO_STOP, autoStopSeconds)
}
/** The alert id this launch is for, if any — read by the JS bridge. */
fun alertIdOf(activity: Activity?): String? =
activity?.intent?.getStringExtra(EXTRA_ALERT_ID)
}
}