- DIY
- A
Heavy Artillery: How to Wake Up Samsung and Xiaomi on Android 14 without Firebase
My name is Vyacheslav, and I am a "process surgeon." Currently a lawyer. I spent 20 years on the other side of the barricades - working as an investigator, assistant prosecutor, and prosecutor.
Procedural Debug
My work in court is not about beautiful speeches, but about finding bugs. I take on cases where the system has failed: the investigation made a mistake, the court turned a blind eye. I conduct an audit of the materials, find a fatal violation (a bug in the procedure), and "break" the verdict. I do not work for the sake of the process — I either see a technical possibility for annulment or I honestly tell the client: "This is WontFix."
A year ago, I realized that I needed a tool that works as uncompromisingly as I do.
I needed a digital assistant that:
Does not lie (guarantees notification of court, even if the phone is "asleep").
Does not give up (encrypts data in such a way that no officer can decipher it).
The market offered me beautiful but "leaky" calendars with cloud synchronization. As a former prosecutor, I know the value of "the cloud" — it’s just someone else's computer, to which the investigators have keys.
After retiring, I decided what to do. So I made an unexpected move. I went back to school. First — to "Test Engineer at Yandex", to understand how software breaks. Then I opened the Kotlin documentation and wrote my own system — ERRATA.
Part 1. Why Programming is Similar to Investigation
When I delved into IT, I was amazed. The logic of code and the logic of criminal procedure are almost identical.
The Criminal Procedure Code of the Russian Federation is the Technical Specification. A step to the left, a step to the right — procedural error (Exception), and the whole case collapses (App Crash).
The investigation is Debugging. You search for where in the chain of events the failure occurred. Who is to blame? An incorrect variable (false testimony) or an error in logic (incorrect qualification)?
The verdict is Release. The moment of truth when others evaluate the result of your work.
The QA course at Yandex gave me the right optics. I looked at my future product not as a creator, but as a tester. I asked questions that juniors usually ignore:
"What if the user changes the time zone an hour before the court?"
"What if the memory runs out during the recording of the verdict?"
It was precisely the QA skills that allowed me, a solo developer, to structure the architecture in such a way that I wouldn't have to rewrite everything from scratch later. I chose Clean Architecture + MVVM because I love order. In code, as in a criminal case, everything should be organized in folders, and the layers of responsibility should not mix.
I wrote an organizer app for myself called ERRATA so I wouldn't miss court hearings. And I immediately encountered the issue that standard Android methods (WorkManager, regular Alarm) simply do not work reliably. The phone "sleeps," Samsung kills processes, and the court notification arrives 30 minutes late. For a lawyer, this is fatal.
I fundamentally could not use Firebase (FCM) for the following reasons:
Offline-First: My application stores data only locally (SQLite/Room), protecting attorney-client privilege. The server does not know the schedule.
Independence: In the context of sanctions, relying on Google services in critically important software is a risk.
Part 2. The Main Pain: "The Phone Fell Asleep, the Lawyer Overslept"
My MVP was ready in a month. But during the very first field tests, I encountered a problem that jeopardized the entire project.
I had to find a way to break through the vendor protections (Samsung/Xiaomi) using standard Android SDK tools. Below is a guide to implementing an "unbreakable" alarm that works even on Android 14.
It turned out that modern Android aggressively kills background processes to save battery. The Doze Mode turns the smartphone into a brick that ignores regular timers.
For an average user, it's "oh, the push notification from the game didn’t come." For a lawyer, it’s a matter of reputation.
I needed a solution at the level of "Military Alarm." So that the phone would "scream," even if it was in deep sleep. And without using Google Services (Firebase), because in the current realities, relying on them in Russia is a risk.
Part 3. 🛑The Problem: Why WorkManager Doesn’t Work. Breaking Through Doze Mode
I tried everything: WorkManager (unreliable, the system may postpone execution), regular AlarmManager.
Notifications arrive delayed by 5 to 15 minutes.
On Samsung/Xiaomi, they don’t arrive at all if the app is killed from memory (swiped away).
The Doze Mode system ignores
setExact.
In the end, I found the "holy grail" — a combination that works on 100% of devices, including Android 14.
We use a chain that gives maximum priority to the process: AlarmManager (setAlarmClock) ➔ BroadcastReceiver ➔ Foreground Service
Why exactly this way?
setAlarmClock: The only method that Android considers a "real alarm." It guaranteed wakes the device from Doze Mode (deep sleep). Even
setExactAndAllowWhileIdleperforms worse.Foreground Service: A regular
WorkManagercan be delayed by the system. A foreground service starts instantly and (thanks to the notification) guarantees that the process won't be killed while it reads a "heavy" database.
🛠 Implementation
1. Manifest and Permissions
On Android 12+ (especially 14), the permission for exact alarms (SCHEDULE_EXACT_ALARM) needs to be requested, and USE_EXACT_ALARM is not granted to everyone. But for setAlarmClock, the permissions work differently.
kotlin
// AlarmScheduler.kt
fun scheduleAlarm(context: Context, item: ScheduleItem) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, NotificationReceiver::class.java).apply {
putExtra("ITEM_ID", item.id)
}
// Important: FLAG_IMMUTABLE may interfere with updating extras, use UPDATE_CURRENT
val pendingIntent = PendingIntent.getBroadcast(
context,
item.id.hashCode(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val alarmInfo = AlarmManager.AlarmClockInfo(
item.timeInMillis,
pendingIntent // This intent will open the app when the alarm icon is clicked in the status bar
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setAlarmClock(alarmInfo, pendingIntent)
} else {
// Fallback: ask the user for permission
askPermission(context)
}
} else {
alarmManager.setAlarmClock(alarmInfo, pendingIntent)
}
}
3. Receiver with Debounce Protection
Sometimes the system (especially Samsung when unlocking the screen) can send old intents "in a bunch". We protect against spam.
kotlin
// NotificationReceiver.kt
class NotificationReceiver : BroadcastReceiver() {
// Simple debounce via SharedFlow or static variable
companion object {
private var lastTriggerTime = 0L
}
override fun onReceive(context: Context, intent: Intent) {
val currentTime = System.currentTimeMillis()
if (currentTime - lastTriggerTime < 1000) {
Log.d("ERRATA", "Debounce: Skip duplicate alarm")
return
}
lastTriggerTime = currentTime
val itemId = intent.getIntExtra("ITEM_ID", -1)
// Start Service, as Receiver lives only 10 seconds
val serviceIntent = Intent(context, ReminderService::class.java).apply {
putExtra("ITEM_ID", itemId)
}
// For Android 8+ (Oreo) it is mandatory to use startForegroundService
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
} else {
context.startService(serviceIntent)
}
}
}
4. Service and Notifications (ReminderService)
Here we read the database and show a notification. An important nuance is navigation and WakeLock.
kotlin
// ReminderService.kt
class ReminderService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 1. Immediately show a "technical" notification so the service doesn't get killed
startForeground(SERVICE_ID, createForegroundNotification())
// 2. Asynchronously go to the database (Room)
CoroutineScope(Dispatchers.IO).launch {
val itemId = intent?.getIntExtra("ITEM_ID", -1) ?: return@launch
val item = db.dao().getById(itemId)
// 3. Show the REAL notification with data
showUserNotification(item)
// 4. Stop the service
stopSelf()
}
return START_NOT_STICKY
}
// ... createForegroundNotification() ...
}
Result: Out of 50 test runs — 50 successes. Even on a phone that was lying still.
Part 4. 🐛Samsung & Xiaomi Special: Pitfalls
Even with perfect code, vendors throw hurdles in the way.
1. “Killing” by swipe
On some versions of OneUI, if the user clears the app from "Recent" (swipe up), AlarmManager is reset.
Solution: Programmatically check the manufacturer (
Build.MANUFACTURER) and show a dialog asking to set the battery mode to "Unrestricted".
2. WorkManager delays
Never use WorkManager for precise notifications on Samsung. It is optimized for battery saving, not accuracy. Only AlarmManager.
3. Deep Links in Compose
If clicking on a notification opens a blank screen, make sure you pass the ID as an argument in the DeepLink URI (for example, myapp://detail/{id}), and in MainActivity you handle intent in onCreate and onNewIntent.
Conclusion: More than MVP.
I do not plan to become a Junior developer in a bank. I remain an advocate who writes code.
For me, programming is an extension of my primary job.
In court, I defend people against systemic errors of justice.
In code, I protect colleagues from systemic errors of Android and data leaks.
This architecture is currently running in the production of my application ERRATA (organizer for lawyers).
Tests showed:
Samsung S24 (Android 14, One UI 6.1): Works perfectly, wakes from sleep.
Xiaomi (HyperOS): Works reliably (provided permission for Auto Start is granted).
Old buckets (Android 8-10): Work without complaints.
I am not a professional developer; I came to IT from the prosecutor's office to solve my tasks. But this experience showed me that sometimes "old" tools (AlarmManager) work more reliably than trendy ones (WorkManager).
ERRATA today is not a raw prototype, but a system of version v1.0 Production Ready, ready for real work "in the field".
Behind the application's interface in Kotlin lies a reliable, although invisible to the user, infrastructure:
Own server (Node.js + SQLite), which is solely responsible for license validation and does not store user data.
Telegram bot (Telegraf), through which a secure store and key activation are implemented. This allows us to be independent of store billing and maintain direct contact with users.
A sovereign "digital safe" has been built, which is not dependent on Google, foreign clouds, and the whims of phone vendors.
The application is now available in Direct Release format (direct APK installation). If you are a lawyer with a Samsung/Xiaomi phone and are tired of missing notifications — welcome.
The code (partially) is open, and my conscience is clear. Not a word without a warrant, not a byte in the cloud.
Write comment