Spectrum 2.4GHz is built around a single-Activity architecture with no Fragments and no ViewModel layer. All UI state — the currently selected tab, the list of scanned networks, scan history, and permission state — is managed directly insideDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/anfegomezver/spectrum24ghz/llms.txt
Use this file to discover all available pages before exploring further.
MainActivity. Custom Canvas-based views handle all data visualisation, and two RecyclerView adapters power the scrollable lists. This flat, self-contained structure keeps the codebase easy to navigate while satisfying the app’s real-time Wi-Fi scanning requirements.
Package Structure
The source code is organized into two packages underapp/src/main/java/:
| Package | Contents |
|---|---|
com.spectrum24ghz | MainActivity, NetworkAdapter, ChannelListAdapter, TimeGraphView, StatisticsGraphView |
com.spectrum24ghz.models | ScannedNetwork, WifiChannel |
Class Breakdown
MainActivity
Orchestrates the entire app: tab navigation, permission requests, scan lifecycle, adapter wiring, and dialog presentation.
ScannedNetwork
Immutable model representing a single detected access point, including SSID, BSSID, RSSI, signal quality, frequency, channel, and capabilities.
WifiChannel
Represents a single 2.4 GHz channel (1–13). Holds frequency, region label, prime-channel flag, and a mutable list of networks found on that channel.
NetworkAdapter
RecyclerView adapter that displays the flat network list with signal icons, RSSI colour coding, and a click listener for detail dialogs.
ChannelListAdapter
RecyclerView adapter that renders each channel row with its network count and colour-coded saturation indicator.
TimeGraphView
Custom
View that draws a multi-line RSSI history graph on Canvas using Paint, Path, and CornerPathEffect.StatisticsGraphView
Custom
View that renders horizontal bar charts (signal quality, security type) and a vertical histogram (channel distribution) directly on Canvas.MainActivity
MainActivity is the single entry point and lifecycle owner. On onCreate it:
- Inflates the layout via View Binding (
ActivityMainBinding.inflate) - Obtains a
WifiManagerreference from the application context - Wires
NetworkAdaptertorvChannelsandChannelListAdaptertorvChannelsList - Registers click listeners on the four tab buttons (Networks, Channels, Time Graph, Statistics)
- Starts an auto-update
Handlerthat triggersinitiateWifiScan()every 5 seconds when the Time Graph tab is active - Immediately calls
requestPermissionsAndScan()to kick off the first scan
selectTab(int tabIndex):
| Index | Tab | Visible view |
|---|---|---|
0 | Networks | rvChannels (network list) |
1 | Channels | rvChannelsList (channel list) |
2 | Time Graph | timeGraph (TimeGraphView) |
3 | Statistics | scrollStats (StatisticsGraphView) |
ScannedNetwork
ScannedNetwork is a fully immutable data class. All seven fields are set in the constructor and exposed via getters only:
| Field | Type | Description |
|---|---|---|
ssid | String | Network name (or "<oculto>" if hidden) |
bssid | String | MAC address of the access point |
rssi | int | Raw signal level in dBm |
signalPercent | int | Normalised signal strength 0–100 % |
frequency | int | Frequency in MHz (e.g. 2437) |
channel | int | 2.4 GHz channel number (1–13, or 0 if unknown) |
capabilities | String | Raw capabilities string from ScanResult |
getSecurityLabel() method derives a human-readable security type by inspecting the capabilities string for keywords in priority order: WPA3 → WPA2 → WPA → WEP → OWE → Open (No Security). It also distinguishes Enterprise (EAP) from Personal (PSK) variants.
WifiChannel
WifiChannel represents one of the thirteen 2.4 GHz channels tracked by the app (channels 1–13, built by buildAllChannels() in MainActivity). Channels 1, 6, and 11 are flagged as prime (isPrime = true) — the three non-overlapping channels in the 2.4 GHz band.
networks list inside each WifiChannel is mutable and rebuilt on every scan cycle. The only mutable field exposed via a setter is isExpanded (used by the adapter to track expand/collapse UI state).
NetworkAdapter
NetworkAdapter extends RecyclerView.Adapter<NetworkAdapter.NetworkViewHolder>. It holds a reference to the shared scannedNetworks list in MainActivity and binds each item using ItemNetworkBinding (View Binding). The OnNetworkClickListener interface allows MainActivity to handle item taps and display a detail AlertDialog:
| Signal percent | Icon | Colour token |
|---|---|---|
| ≥ 85 % | ic_wifi_4 | sig_strong |
| 60–84 % | ic_wifi_3 | sig_good |
| 35–59 % | ic_wifi_2 | sig_medium |
| 15–34 % | ic_wifi_1 | sig_medium |
| < 15 % | ic_wifi_0 | sig_weak |
ChannelListAdapter
ChannelListAdapter extends RecyclerView.Adapter<ChannelListAdapter.ChannelViewHolder> and uses ItemChannelListBinding. The OnChannelClickListener interface delegates tap events to MainActivity for a channel-detail dialog:
min(networkCount × 25, 100)% and colour-coded green (≤ 30 %), yellow/orange (31–60 %), or red (> 60 %).
TimeGraphView
TimeGraphView extends android.view.View and overrides onDraw(Canvas). It receives the full scanHistory list from MainActivity via updateHistory() (which calls invalidate() to trigger a redraw). Each draw pass:
- Renders a dashed grid from −100 dBm to −20 dBm on the Y-axis in 10 dBm steps
- Displays a sliding window of the last 10 scan cycles on the X-axis
- Draws one
Pathper unique BSSID usingCornerPathEffect(25)for smooth curves - Adjusts line opacity and stroke width based on the most recent RSSI value (strong: full opacity at 5 px; medium: 150 alpha at 3.5 px; weak: 70 alpha at 2.5 px)
- Renders a colour-coded legend overlay (up to 6 networks) in the upper-left corner
init() and reused across draw calls to avoid object allocation during rendering.
StatisticsGraphView
StatisticsGraphView extends android.view.View and overrides onDraw(Canvas) to render three chart sections in a single vertically scrolling canvas (fixed measured height: 2200 px):
- Signal Quality — horizontal bar chart with four quality bands: Excellent (≥ 85 %), Good (60–84 %), Medium (35–59 %), Weak (< 35 %)
- Channel Usage — vertical histogram for channels 1–14; channels 1, 6, and 11 are highlighted in
sig_strong(green) to indicate non-overlapping prime channels - Security Types — horizontal bars broken down by security label returned from
ScannedNetwork.getSecurityLabel()
MainActivity.updateStatisticsView() which calls statsGraph.updateData(qCounts, cCounts, sCounts).
Scan Lifecycle
The following sequence describes one complete scan cycle from user interaction to UI update:requestPermissionsAndScan()
Checks whether
ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, and (on API 33+) NEARBY_WIFI_DEVICES are granted. If any are missing, permissionLauncher prompts the user. On grant, calls startScanFlow().startScanFlow()
Enforces a 30-second cooldown (
SCAN_COOLDOWN_MS = 30 000). If a scan was started less than 30 seconds ago, the cached WifiManager.getScanResults() are loaded immediately via loadCachedResults() without triggering a new hardware scan.initiateWifiScan()
Records
lastScanStartMs (via SystemClock.elapsedRealtime()), clears previous results, registers scanReceiver as a BroadcastReceiver for SCAN_RESULTS_AVAILABLE_ACTION, calls wifiManager.startScan(), and posts a 12-second timeout (SCAN_TIMEOUT_MS = 12 000) on scanTimeoutHandler.BroadcastReceiver.onReceive() or timeout fires
Whichever arrives first — the system broadcast or the 12-second timeout — calls
handleScanResults(boolean fresh). The receiver is immediately unregistered and the timeout is cancelled to prevent double processing.handleScanResults()
Calls
populateNetworks() to rebuild scannedNetworks and populate channel buckets, then notifies both adapters. The snapshot is appended to scanHistory (capped at 20 cycles). Both timeGraph and (if the Statistics tab is active) statsGraph are refreshed.Key Constants
| Constant | Value | Purpose |
|---|---|---|
SCAN_COOLDOWN_MS | 30 000 ms | Minimum interval between hardware scans |
SCAN_TIMEOUT_MS | 12 000 ms | Maximum wait time for scan results broadcast |
FREQ_MIN | 2410 MHz | Lower frequency bound for 2.4 GHz band filter |
FREQ_MAX | 2486 MHz | Upper frequency bound for 2.4 GHz band filter |
| Max history size | 20 cycles | Maximum entries retained in scanHistory |
| Sliding window | 10 scans | Number of cycles shown on the Time Graph at once |
View Binding
View Binding is enabled inbuild.gradle (buildFeatures { viewBinding true }). The build system generates a binding class for every layout file. The app uses two binding classes:
ActivityMainBinding— wrapsactivity_main.xml; used inMainActivityto access all root-level views without anyfindViewById()callsDialogAboutBinding— wrapsdialog_about.xml; inflated on demand when the About dialog is shown
ItemNetworkBinding and ItemChannelListBinding) rather than calling itemView.findViewById().