# Making TapCap ## I can haz voice clip? For decades (decades I tell you!) I've wanted a super simple voice note app for my phone. I had pretty straightforward but rigid requirements: - As soon as it launches, it starts recording - One tap to stop recording and put the audio clip somewhere I can find it - No fluff like built-in file management, transcription, cloud recording, ads, etc. Obsidian and Samsung Voice Notes, as well as many other apps, do have voice recording. However, they always miss my first requirement: I have to launch the app and tap around before I get to the point of being able to record. And by the time I get there, the thought is gone. ## From nuh-uh to yay! I've done a lot of hobby programming in the past (particularly Visual Basic back in the day and Xojo more recently) but every time I started with Android coding it just quickly became a soup of Intents and Manifests and Gradle and MVVM and the fun just drained out of it, so I never did end up writing something for myself. Enter Codex. I've been using Claude Code a lot at work lately (as a UXR!) and found that it's stupidly easy to create things. At home, I already had a ChatGPT subscription, so I decided to try Codex. And within a few hours of playing (if even that long), I had the app I've always wanted: TapCap. ![[tapcap-make.jpg|250]] ↑ That's TapCap while it's running. I launch the app. It starts recording. The microphone pulses as it records so I know it's working. I tap anywhere on the screen and it saves and exits, or I tap Cancel. ## Make yer own! If you want to make your own Voice Note app, just paste this into your Codex or Claude Code from an empty working directory: ```text Create a complete native Android app named Voice Note. Goal: Build a minimal Android 10+ app for quick audio capture. When launched, it immediately records from the microphone. Tapping the main screen ends the recording, saves it, shows a toast, and exits. Project requirements: - Create a standard Gradle Android application project. - Use Java or Kotlin; prefer a single Activity and no unnecessary dependencies. - Minimum SDK: 29. - Target/compile a current Android SDK available in the environment. - App label: Voice Note. - Package name can be com.example.voicenote unless there is an existing convention. - Use a programmatic native Android UI. Do not use Compose. Permissions: - Request android.permission.RECORD_AUDIO at runtime. - If permission is already granted, start recording immediately on launch. - If permission is denied, show a short message on the screen and do not record. Recording: - Use MediaRecorder. - Audio source: MediaRecorder.AudioSource.MIC. - Output format: MediaRecorder.OutputFormat.MPEG_4. - Audio encoder: MediaRecorder.AudioEncoder.AAC. - Audio bit rate: 128000. - Audio sample rate: 44100. - Record first to a temporary .mp4 file in the app cache directory. - Poll MediaRecorder.getMaxAmplitude() every 50ms while recording. - Handle RuntimeException around MediaRecorder.stop(), because stop can fail for invalid or very short recordings. - Stop polling amplitude before releasing the recorder. - Always release MediaRecorder when finished. Saving: - On tap-to-end, stop recording and save the temp file to DCIM/Camera. - Use MediaStore scoped storage, not direct filesystem writes to /sdcard. - Use MediaStore.Video.Media and MIME type "video/mp4", because the destination is DCIM/Camera even though the MP4 is audio-only. - Use MediaStore.MediaColumns.RELATIVE_PATH = Environment.DIRECTORY_DCIM + "/Camera". - Use IS_PENDING = 1 while writing, then update IS_PENDING = 0 after copying bytes. - Filename format must be yyyyMMdd_HHmmss_audio_only.mp4, for example 20260426_111018_audio_only.mp4. - After a successful save, delete the temp file, show a toast saying it saved to DCIM/Camera, then finish/exit the activity. Cancel: - Add a Cancel button centered horizontally near the bottom. - It should look like a native clean text/button control, not a crude hard rectangle. - Pressing Cancel stops recording if needed, releases the recorder, deletes the temp file, shows a short cancellation toast, and exits without saving. - Pressing Cancel must not trigger the main screen tap-to-save handler. Background behavior: - If the app is backgrounded while recording, schedule an automatic save after 30 seconds. - If the app returns to foreground before 30 seconds, cancel that pending save. - If Android destroys the Activity while still recording, attempt to save rather than silently discard the recording. UI: - Main screen background: black. - Show a vertically centered cluster containing: - an in-app microphone icon image, - title text "Voice Note", - caption text "Recording. Tap to end." - The main root view should handle tap-to-end. - Keep the phone status bar visible while the app is running. Use black status/navigation bars with light icons. Animation: - The in-app microphone icon itself should scale with live audio amplitude. - Quiet/minimum volume scale: 1.0x. - Maximum volume scale: 2.5x. - Use this smoothing behavior or equivalent: normalized = min(1, amplitude / 32767) nextLevel = min(1, normalized * 3.4) iconLevel += (nextLevel - iconLevel) * 0.32 scale = 1 + (2.5 - 1) * iconLevel - Apply scale to the ImageView with setScaleX/setScaleY. Icons/assets: - If the working directory contains "app icon.png", 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 - If the working directory contains "app icon-empty.png", use it as the in-app icon and copy/scale it to res/drawable-nodpi/app_icon_empty.png at 512x512. - If those image files are not present, create simple placeholder assets so the app still builds, and clearly state where the real PNGs should be placed. Implementation notes: - Prefer a single MainActivity. - Prefer programmatic views: FrameLayout root, LinearLayout main cluster, ImageView icon, TextView title, TextView caption, Button cancel. - Root should be clickable and call stop-and-save. - Cancel button should consume its own click and call cancel-without-saving. - Avoid adding XML layouts unless there is a strong reason. - Add a README with build/install instructions. 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 write directly to /sdcard/DCIM/Camera on Android 10+. - Do not forget to clear IS_PENDING after writing through MediaStore. - Do not leave amplitude polling callbacks running after recorder release. - Do not save on Cancel. - Do not discard a backgrounded recording unless Cancel was pressed or recording never started correctly. - If the installed launcher icon/name looks stale on a phone, instruct the user to uninstall and reinstall because launchers cache app metadata. ```