Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions docs/troubleshooting/android-file-picker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Troubleshooting: "why is the upload page asking for my microphone?"

A walkthrough of how I diagnosed a confusing bug report on Beckham Share. It's written so the
*method* is reusable on any problem — the specific bug matters less than the way through it.

## The report
On an Android phone (Samsung, Firefox), signing in and tapping **Upload** popped up a system
prompt: *"Allow Firefox to record audio?"* Nothing on a file-share upload page should need a
microphone. So: what's actually going on, and is it dangerous?

## Step 1 — Separate fact from fear: what is actually on the page?
Before theorizing, I checked the code. A microphone can only be reached through specific browser
APIs — `getUserMedia`, `AudioContext`, `mediaDevices`. I searched the whole app for them:

```
grep -rE "getUserMedia|AudioContext|mediaDevices" app/
```

Nothing. The page has no audio code at all. That one check reframed the problem: the page isn't
*trying* to record audio — something else is triggering the prompt.

> **Lesson:** verify what the system actually contains before you explain its behavior. Rule
> things out with evidence, not assumptions.

## Step 2 — Find the only suspect
What could a browser associate with capture on this page? The file input:

```
<input type="file">
```

That's the only element involved in "Upload." Hypothesis: the browser treats a file input as a
possible *capture* source (camera / camcorder / voice memo) and pre-emptively asks for the
permissions those would need.

## Step 3 — A fix attempt that failed (and why that's useful)
My first instinct was to constrain the input with `accept="*/*"`. I shipped it and tested — and it
got **worse**: the chooser now offered only Camera / Camcorder / Photos & Videos, with no file
browser at all. I reverted immediately.

> **Lesson:** a plausible fix is a hypothesis, not a solution — especially on a platform you can't
> test from your own desk. Confirm before you trust it, and be ready to undo.

## Step 4 — Read the primary sources
Instead of guessing again, I read the authoritative references on how `<input type="file">` and
`accept` behave on Android:

- **Mozilla bug 1337692** — `accept="image/*"` requests camera, `audio/*` requests microphone,
`video/*` requests both, and **no `accept` should request only file storage**.
- **Mozilla bug 1362919** — if camera/mic permission is *denied at the app level*, older Firefox
wouldn't open the plain file picker at all.
- A cross-browser writeup on Android file inputs and the `accept` attribute.

Now I had a model: the prompt is the browser's doing, driven by `accept`, and the permission
*state* on the device matters too.

## Step 5 — When you can't reproduce, build the smallest decisive test
I couldn't reproduce the Samsung behavior on my own machine, and theory said no-`accept` should
already be fine. The bottleneck was empirical data from the device that *does* reproduce it. So I
built a tiny page with the same file input repeated under six different `accept` values, each
showing the file you picked so you can tell whether it actually worked:

[`picker-test.html`](./picker-test.html)

Then I had the person with the phone tap each one and report what happened.

> **Lesson:** when a bug only appears in an environment you don't have, stop guessing — hand that
> environment a controlled experiment that isolates one variable at a time.

## Step 6 — Read the results, land the resolution
The results were clear. **No `accept`** opened the full file picker showing all files in both
Chrome and Firefox. `application/octet-stream` hid recent files in Firefox; extension lists
over-filtered the picker. And the prompt itself is unavoidable from HTML — the browser asks
because the file chooser *can* capture media. The real fix lives on the device:

> Set the browser's **Camera + Microphone permission to "Don't allow"** in the Android app
> settings. It persists, the prompt stops, and the file picker opens every time. (Hitting **Back**
> also dismisses a single prompt.)

So the app needed **no change** — it was already at the correct setting (no `accept`).

## The takeaways
1. **Verify before you explain.** A ten-second `grep` reframed the entire problem.
2. **Treat a fix as a hypothesis** until it's confirmed in the failing environment.
3. **Read primary sources** instead of guessing repeatedly.
4. **Isolate one variable at a time** — the test page changed only `accept`.
5. **When you can't reproduce, ship an experiment, not a guess.**
6. **The fix isn't always code.** Sometimes the answer is a setting, and "no change needed" is a
valid, well-earned resolution.

The diagnostic page from Step 5 is kept here for reference: [`picker-test.html`](./picker-test.html).
Open it from a checkout (or download it and open it in a browser) to experiment on any device.
71 changes: 71 additions & 0 deletions docs/troubleshooting/picker-test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>File picker test</title>
<style>
body { font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0; padding: 16px; color: #1e1919; background: #f7f9fc; }
h1 { font-size: 19px; } p { color: #637282; font-size: 14px; }
.case { background: #fff; border: 1px solid #e3e8ee; border-radius: 12px;
padding: 14px; margin: 12px 0; }
.case b { font-size: 15px; } code { background: #f1f4f9; padding: 1px 5px; border-radius: 4px; font-size: 13px; }
label.btn { display: inline-block; margin-top: 10px; background: #0061fe; color: #fff;
padding: 12px 18px; border-radius: 8px; font-weight: 600; font-size: 15px; }
input[type=file] { display: none; }
.out { margin-top: 10px; font-size: 14px; color: #0f6e56; word-break: break-all; }
.out.empty { color: #99a; }
</style>
</head>
<body>
<h1>File picker test</h1>
<p>A standalone diagnostic for the "upload asks for microphone / no file picker" problem on
mobile browsers (see <code>android-file-picker.md</code> in this folder). Tap each
<b>Choose file</b> below and note which one opens a real <b>Files / My Files</b> browser (vs
Camera/Camcorder/Photos&amp;Videos), and whether it still asks for microphone/camera. The
picked file name shows in green to confirm it worked. Compare the cases to see how the
<code>accept</code> attribute changes the chooser on your device.</p>

<div class="case" id="c1"><b>1.</b> No <code>accept</code>
<div><label class="btn">Choose file<input type="file" data-out="o1"></label></div>
<div class="out empty" id="o1">no file chosen</div></div>

<div class="case" id="c2"><b>2.</b> <code>accept="application/octet-stream"</code>
<div><label class="btn">Choose file<input type="file" accept="application/octet-stream" data-out="o2"></label></div>
<div class="out empty" id="o2">no file chosen</div></div>

<div class="case" id="c3"><b>3.</b> Explicit extensions
<code>.pdf,.zip,.txt,.doc,.docx,.csv,.jpg,.png,.mp4,.mp3,.apk</code>
<div><label class="btn">Choose file<input type="file"
accept=".pdf,.zip,.txt,.doc,.docx,.csv,.jpg,.png,.mp4,.mp3,.apk" data-out="o3"></label></div>
<div class="out empty" id="o3">no file chosen</div></div>

<div class="case" id="c4"><b>4.</b> <code>accept="application/*,text/*"</code>
<div><label class="btn">Choose file<input type="file" accept="application/*,text/*" data-out="o4"></label></div>
<div class="out empty" id="o4">no file chosen</div></div>

<div class="case" id="c5"><b>5.</b> <code>accept="*/*"</code>
<div><label class="btn">Choose file<input type="file" accept="*/*" data-out="o5"></label></div>
<div class="out empty" id="o5">no file chosen</div></div>

<div class="case" id="c6"><b>6.</b> No <code>accept</code>, <code>multiple</code>
<div><label class="btn">Choose file<input type="file" multiple data-out="o6"></label></div>
<div class="out empty" id="o6">no file chosen</div></div>

<script>
document.querySelectorAll('input[type=file]').forEach(function (inp) {
inp.addEventListener('change', function () {
var out = document.getElementById(inp.getAttribute('data-out'));
if (inp.files && inp.files.length) {
var f = inp.files[0];
out.className = 'out';
out.textContent = 'OK: ' + f.name + ' (' + Math.round(f.size / 1024) + ' KB)' +
(inp.files.length > 1 ? ' +' + (inp.files.length - 1) + ' more' : '');
} else { out.className = 'out empty'; out.textContent = 'no file chosen'; }
});
});
</script>
</body>
</html>
Loading