This document describes the technical details and architecture of the Android app that displays internet connection speed in the status bar.
The Ukrainian original is preserved at
SPEC-uk.md.
- Language: Kotlin.
- UI framework: Jetpack Compose (Material 3).
- Concurrency: Kotlin Coroutines &
StateFlow. - Dependency Injection: Dagger Hilt.
- Settings storage: Jetpack DataStore (Preferences) — used to persist
configuration (e.g. the
isEnabled: Booleanflag, so the app knows whether to restart after device reboot). - SDK:
minSdk = 26(Android 8.0),targetSdk = 35,compileSdk = 35. - Localization:
uk(default),en(fallback). - Analytics & crash reporting: deliberately none — no third-party trackers, for maximum energy efficiency and privacy.
- Status bar display: persistent notification (Foreground Service). The
icon is generated dynamically as a
Bitmapwith upload and download speed text. - Refresh interval: target is 1000 ms (one tick per second), implemented
via
delay(1000)inside a Coroutine that watches state. - Speed calculation (drift-corrected formula):
- The delta uses the real elapsed time between ticks to avoid timer error (e.g. 1050 ms instead of 1000 ms):
bitsPerSecond = (currentBytes - previousBytes) * 8 * 1000 / elapsedMs
- Scaling (formatter) and thresholds:
Round to integers, except in the Gbps range where one decimal place
is allowed for precision and readability (e.g.
1.2G). Ranges:< 1 000 bps→ show in bps1 000 – 999 999 bps→ show in Kbps1 000 000 – 999 999 999 bps→ show in Mbps>= 1 000 000 000 bps→ show in Gbps (orG)
- Expanded notification (drawer):
- Title:
Network speed. - Body:
↓ 14 Mbps | ↑ 2 Mbps(updated dynamically). - Tap action: opens the Dashboard via
PendingIntent.getActivity. ThePendingIntent.FLAG_IMMUTABLEflag is mandatory (Android 12+ requirement).
- Title:
- Status-bar icon layout:
Text is stacked vertically — upload on top, download at the bottom:
↑ 2M↓ 14MShortening rule: values above 999 are truncated to at most 4 characters (including the decimal point in the Gbps range). - Home screen (Dashboard):
- Switch to enable/disable monitoring.
- Service status: textual indicator — "Active / Waiting for network / Stopped".
- Live speed: shows the same speed stream the service uses
(communicated via a shared Hilt-
@SingletonStateFlow). When the Switch is off (service stopped), render dashes (— / —). - Permissions: a permission management block covering
POST_NOTIFICATIONSand Battery Optimization.
- Accessibility: all interactive elements (Switch, buttons) must have
proper
contentDescriptionfor TalkBack support. - Dark/Light theme: dynamic system theme support (Compose Material 3 Theme).
- Unit tests: required coverage for the calculator and the formatter
(
SpeedFormatter) — verify zero-delta handling, overflow (negative values), and correct rounding of thresholds to whole integers / hundreds of Mbps. - Instrumented / UI tests: exercise the Compose Dashboard and verify
StateFlowstate rendering.
- Traffic reading: use
TrafficStats.getTotalRxBytes()/getTotalTxBytes(). The data includes all system traffic, which guarantees high performance. - Bitmap management and monochrome rendering (CRITICAL):
- The
smallIconin the status bar is monochrome. ABitmapof configBitmap.Config.ALPHA_8must be used, sized48x48 px. This cuts memory use by 4×. Paint.coloris set toColor.WHITE(the system uses only the alpha channel for drawing).- Only one
Bitmap+Canvasinstance is created at startup. On every tick the object is reused: theCanvasis cleared withdrawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR), the updated text is drawn, and the bitmap is handed toIcon.createWithBitmap().
- The
- No network (airplane mode): monitoring is paused, the icon is
completely hidden, and
ConnectivityManager.NetworkCallbackwaits for a network to resume. POST_NOTIFICATIONSdenied (Android 13+): the Foreground Service is not allowed to start. The Dashboard shows a red alert with a button to open settings. When the permission is granted via theActivityResultLauncher.registerForActivityResult(RequestPermission())callback, the service starts.- Notification constants:
const val CHANNEL_ID = "speed_monitor_channel"const val NOTIFICATION_ID = 1If the user disables the channel's notifications in OS settings, the Dashboard prompts them to re-enable the switch.
- Doze / battery optimization: an
Intent.ACTION_SCREEN_OFFbroadcast stops the tick timer;ACTION_SCREEN_ONresumes the service and immediately redraws the bitmap. - Calculation anomalies:
- First tick: delta = 0, the current bytes are stored as the baseline,
output is
0 bps. - Overflow / negative delta: on counter reset or OS counter overflow
(
currentBytes < previousBytes), this is treated as a "first tick" — delta = 0.
- First tick: delta = 0, the current bytes are stored as the baseline,
output is
- Auto-start after reboot:
A
BroadcastReceiverlistens strictly forandroid.intent.action.BOOT_COMPLETED(withoutdirectBootAware). The service starts only after the device has been unlocked for the first time. This is critical, because the DataStore / SharedPreferences settings file lives in Credential Protected Storage and is unavailable until first unlock. Reading the flag earlier would throw IOException (crash).
For stable operation, the following <uses-permission> entries must be
declared:
FOREGROUND_SERVICE: base requirement for FGS across old and new APIs.FOREGROUND_SERVICE_SPECIAL_USE: service type required on Android 14+. The Service tag must include the following property:<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Continuous network speed status bar overlay" />. The type is also passed during startup:startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
ACCESS_NETWORK_STATE: needed to observe connectivity viaNetworkCallback.POST_NOTIFICATIONS: required to show thesmallIconin the status bar.RECEIVE_BOOT_COMPLETED: read by theBroadcastReceiver.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS: requested from the Dashboard settings.
Notification channel setup:
- Priority / importance:
NotificationManager.IMPORTANCE_LOW(no sound, no vibration, but the icon is still visible in the status bar). - Visibility:
NotificationCompat.VISIBILITY_PUBLIC— speed is shown directly on the lock screen.