# Making Puntito (NB: This post was written hand-in-hand with AI, but I manually checked and tweaked it thoroughly.) ## Where was I, exactly? I've wanted to automatically track my location for a while now, as a way to supplement my GPS-stamped photos, paper notes, and various logs. I tried a bunch of apps, settled on Google Maps timeline for a bit (it just works) until I saw the density of the information they collect in the logs (shocking!), then tried [Fog of World](https://play.google.com/store/apps/details?id=com.ollix.fogofworld) at my son's suggestion (very cool, but not great at exporting), and settled on [Geo Tracker](https://play.google.com/store/apps/details?id=com.ilyabogdanovich.geotracker) for the longest time. But I yearned for something of my own. A quiet little app that records where my phone has been, locally, in files I can actually inspect. Not a full map app, not a fitness tracker, not a social check-in thing, and definitely not another place where my data disappears into somebody else's product. Avoid cloud accounts, dashboards, ads, subscriptions, and general app nonsense. The requirements were simple, but pretty specific: - Wake up at regular intervals and check my current location - Log my location each time it gets it - When I stop for a while at a location, capture that in a separate file - Save location data where I can easily find it, in a .csv file There are plenty of location history apps, but most of them are either too big, too opaque, too tied to a service, or too focused on maps and routes. I mostly wanted evidence of "I was here for a while" in a format that would still make sense ten years from now. So Puntito became the answer: a tiny app that keeps a raw location log and a separate stop log. ## From tiny dot to useful trail The first version was simple: wake up every 15 minutes, get a location, and write it to a file. Android quickly made that less simple. Modern Android does not let background apps run forever, write wherever they want, or grab location without explicit permission. So, Puntito now runs as a foreground location service. Android shows a persistent notification while tracking is active; that's the price of reliable background location. The app uses Google’s fused location provider, letting Android combine GPS, Wi-Fi, cell, and cached signals instead of relying on raw GPS alone. And the stop logic is deliberately conservative: - Poll every 5 minutes - Confirm a stop after at least 10 minutes - Require at least 2 good fixes - Treat fixes within 200 meters as the same stop - Ignore fixes worse than 100 meters accuracy for stop detection - Still write poor-accuracy fixes to the raw log This keeps the stop file from filling up with every minor wobble from location drift. It also means a stop row is written only after Puntito sees that I have left the stop, not while I am still there. The screen itself is intentionally minimal: black background, Puntito icon, app name, short status, and one Start/Stop button. A gear button lets me choose the log folder when tracking isn't running. ![[puntito-make.jpg|250]] ↑ That's Puntito while it's running. The status line pulls form a list of silly little phrases, like "the road has opinions", "small roads, big dumb sky", and "dragged around by curiosity", just for a little color. ## Make yer own! If you want to make your own location stop logger, paste this into Codex or Claude Code from an empty working directory: ```text Create a complete native Android app named Location Log. Goal: Build a minimal Android 10+ location logging app. The app should run a foreground service in the background, poll location every 5 minutes, write every successful fix to a raw CSV file, and write confirmed stops to a separate stops CSV file. Project requirements: - Create a standard Gradle Android application project. - Use Java or Kotlin; prefer Java if no project convention exists. - Use a single Activity plus a foreground Service. - Minimum SDK: 29. - Target/compile a current Android SDK available in the environment. - App label: Location Log. - Package name can be com.example.locationlog unless there is an existing convention. - Use a programmatic native Android UI. Do not use Compose. - Use AndroidX only where useful; avoid unnecessary dependencies. Location provider: - Use Google Play Services fused location provider. - Add dependency com.google.android.gms:play-services-location. - Prefer FusedLocationProviderClient over raw LocationManager GPS-only polling. - For the first fix, request high accuracy. - After that, allow balanced power accuracy when appropriate. - Use cached fused location only if it is fresh enough for the polling interval. - Use a timeout so a location attempt cannot hang forever. - If a fused update fails or times out, attempt getCurrentLocation as a fallback. Permissions: - Request location permission at runtime. - Request notification permission on Android 13+. - Declare foreground service location permissions required by modern Android. - If location permission is denied, show a clear message and do not start tracking. - If notification permission is denied on Android 13+, explain that foreground tracking needs the persistent notification. Background behavior: - Use a foreground service while tracking is active. - Show a persistent notification while the service is running. - The notification title should be "Location Log". - The notification text should reflect tracking state, such as "Checking for stops every 5 minutes". - The app must not require the Activity to stay open. - Store tracking state and stop-detection state in SharedPreferences so the service can recover across app reopenings. - When the user presses Stop, stop the foreground service and clear the tracking flag. Logging: - Let the user choose a log folder using Android's Storage Access Framework. - Persist URI permission for the chosen folder. - If no folder has been chosen, fall back to Android's Documents folder through MediaStore scoped storage. - Do not write directly to arbitrary /sdcard paths. - Write CSV files, not Markdown or JSON. - File names: - location-raw.csv - location-stops.csv - Use MIME type text/csv. - Create files with a header row if they do not already exist. - Append rows without rewriting the whole file. Raw CSV: - Append every successful non-null location fix to location-raw.csv. - Header: timestamp_utc,latitude,longitude,accuracy_m - timestamp_utc should be ISO-8601 UTC, for example 2026-04-25T18:30:00Z. - latitude and longitude should use decimal degrees. - accuracy_m should be blank if Android does not provide accuracy. Stop detection: - Poll interval: 5 minutes. - Minimum stop duration: 10 minutes. - Stop radius: 200 meters. - Minimum good fixes to confirm a stop: 2. - Maximum accuracy for stop decisions: 100 meters. - Write all successful fixes to location-raw.csv, even if accuracy is poor. - Ignore poor-accuracy fixes for stop detection. - Use candidate stop state before confirming a stop. - A candidate stop becomes an active stop only after the minimum duration and minimum fix count are met. - When a good fix appears outside the active stop radius, close the active stop and append it to location-stops.csv. - Start a new candidate stop from the new fix after closing the previous stop. - Do not write a stop row while the user is still at that stop; write it after detecting departure. Stops CSV: - Header: start_local,end_local,utc_offset,duration_minutes,latitude,longitude,accuracy_m,fix_count - start_local and end_local should use local device time, formatted yyyy-MM-dd HH:mm:ss. - utc_offset should be the UTC offset at the stop start, for example -07:00. - If the user changes time zones during a single stop, ignore that complication and keep the start offset. - duration_minutes should be an integer. - latitude and longitude should represent the stop center. - accuracy_m should represent the best accuracy used for the stop, if available. - fix_count should record how many good fixes contributed to the stop. UI: - Main screen background: black. - Keep the Android status bar/header visible. - Use black status/navigation bars with light icons. - Show a vertically centered cluster containing: - the Location Log icon with rounded corners, - title text "Location Log", - status text. - When not tracking, status text should be "Ready to track". - When tracking, status text should be: Location Log is running "random message" - Pick the random message once each time the Activity launches. - Use one Start/Stop button near the lower part of the screen. - When not tracking, the button says "Start" and is yellow. - When tracking, the button says "Stop". - Show a gear icon above the Start button only when not tracking. - The gear opens the folder picker. - The icon, title, status, and button y-positions should not jump when tracking state changes. Random running messages: - Maps, miles, minor mistakes - The road has opinions - A pocketful of coordinates - Small roads, big dumb sky - Dragged around by curiosity - Not lost, just poorly labeled - One more weird little place - Your dot is showing - Elsewhere, with snacks - The map knows what you did - A trail of questionable decisions - From here to what now - Off to bother another town - The long way, obviously - Road trip, no adult supervision - Pinned, plotted, probably wrong - The coordinates of bad ideas - Everywhere is suspiciously far - Just enough map to be dangerous - Went outside; became data - Go forth and overshoot - Big map energy - Places I have inconvenienced - The map is judging us - Wander first, explain later - Record of a minor expedition - Useful notes from nowhere special - Latitude, longitude, questionable judgment - A pocket guide to being elsewhere - Mostly accurate since departure - Coordinates observed, snacks consumed - Evidence of having gone - For persons currently in motion - The official log of getting there - A small report from away - Routes, ruts, and roadside facts - Notes from the passenger seat - Mileage may vary, obviously - A practical guide to wandering off - Stamped, folded, slightly wrong - The collected errors of movement - Field notes from a moving dot - Sensible records of bad directions - Whereabouts, more or less - An index of detours - Cartographic mischief, neatly kept - For use between places - Fine observations from dumb roads - The road was right there Icons/assets: - If the working directory contains an app icon image, use it as the launcher icon source. - Generate launcher PNGs: - mipmap-mdpi/ic_launcher.png: 48x48 - mipmap-hdpi/ic_launcher.png: 72x72 - mipmap-xhdpi/ic_launcher.png: 96x96 - mipmap-xxhdpi/ic_launcher.png: 144x144 - mipmap-xxxhdpi/ic_launcher.png: 192x192 - Also use the icon in-app near the top of the main cluster. - If no icon is present, create a simple placeholder asset so the app still builds. Implementation notes: - Prefer programmatic views: FrameLayout root, vertical LinearLayout cluster, ImageView icon, TextView title, TextView status, ImageButton gear, Button Start/Stop. - Use Material/Android standard button shapes or drawable shapes with rounded corners. - Keep layout dimensions stable so text and controls do not shift between tracking and idle states. - Use SharedPreferences for: - tracking flag, - selected log folder URI, - candidate stop state, - active stop state, - last diagnostics/error state if shown during development. - During development, make diagnostic errors visible on screen so permission, provider, and write failures are debuggable. - It is acceptable to remove diagnostics from the final minimal UI after the app is working. Build verification: - Build the debug APK. - If Gradle is available, run ./gradlew assembleDebug. - On macOS with Android Studio installed, use: JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" ./gradlew assembleDebug - Report the APK path, typically app/build/outputs/apk/debug/app-debug.apk. Common pitfalls to avoid: - Do not assume GPS-only location will get a fix indoors; use fused location. - Do not expect a background location app to run reliably without a foreground service notification. - Do not require the app screen to stay open. - Do not write directly to random external storage paths on Android 10+. - Do not use DCIM as a generic file destination through MediaStore.Files; Android may reject it for non-media files. - Do not forget to persist Storage Access Framework URI permissions. - Do not treat one noisy fix as proof that the user left a stop. - Do not let poor-accuracy fixes create false stops. - Do not write the active stop row until departure is detected. - If the installed launcher icon/name looks stale on a phone, instruct the user to uninstall and reinstall because launchers cache app metadata. ```