Facebook on Android: What Each Permission Grants, and What the App Actually Does

This is the Android half of a four-part look at what these apps actually do with a phone. Muse is the assistant that can act as you. Facebook on iOS is the same app under Apple’s rules, read off a jailbroken phone. iOS itself is what the phone records before any app asks. This post is Facebook, the feed app, on Android.

Facebook for Android, com.facebook.katana 583.0.0.39.55, target SDK 36. I pulled the split APKs off the phone with ADB and took them apart: JADX, then baksmali of all 22 DEX files, 218,875 smali files. The phone was not rooted. A normal adb shell cannot read /data/data/com.facebook.katana, so the live databases stayed sealed. What I have is the code, plus the permission state ADB could see: which grants were allowed and which were denied. MobileConfig can still change timers from the server without an app update. The 90 second number is compiled into this build. The scan lengths are not.

The 90 second timer, without the code

While Facebook is open on the screen, a clock inside the app fires about every minute and a half. Each time, it does three things and sends the bundle to Meta.

  1. Where the phone is. A GPS fix: latitude, longitude, how accurate it was, speed, direction, the time, and a flag for whether the location looks faked. They tile that into a quadkey, which is a map square they can store and compare.
  2. Every Wi-Fi network it can hear. Not just the one you joined. The name, the router’s hardware ID, and the signal strength. That is a fingerprint of the building. It is also how a company builds a map of “this router is at this address.”
  3. Every Bluetooth device nearby that is advertising. Phones, watches, headphones, store beacons. It keeps the raw advertisement and the device address, and the code recognizes iBeacon and Eddystone beacons. A few seconds of scanning, no filter, so it is whoever is in the room.

Then it schedules itself again.

If precise location is off, or GPS is not really available, the clock slows down on purpose. Ninety seconds becomes 3 minutes, then 6, then 12, and it will not go slower than every 24 minutes. The moment precise location is back, it returns to 90 seconds. Leave the app, and this particular clock stops. It is a foreground loop. Backgrounding it is enough to stop these quiet scans.

What it is not: a hidden microphone. The mic is a separate permission. I did not find a recorder on this timer. On the test phone the microphone permission was denied.

Why it matters if you never open a map. A single GPS pin is “you were near here.” A sample every 90 seconds, plus the Wi-Fi names around you, plus the Bluetooth devices around you, is a trail of where you were and who was nearby. Meta can change how long each scan lasts from their servers, per account, without you updating the app. Denying fine location kills the Wi-Fi census and slows the clock. Denying Bluetooth kills the device census. The device ID that ties the samples to the account does not ask permission at all.

The rest of this post is the evidence: the class that runs the clock, then each permission, what people think it is for, and what this build actually does with it.

The thing the location grant actually starts

ForegroundLocationFrameworkController runs a self-rescheduling job. Each pass does one Wi-Fi scan, one Bluetooth LE sweep, and a GPS fix, uploads the bundle, then schedules the next pass.

  • Base interval is 90 seconds. The constant in the controller is 0x15f90, which is 90,000 ms.
  • If fine location plus GPS is not fully available, the next delay is 90s << min(attempts, 4). That is 3 minutes, then 6, then 12, then a 24 minute cap. It snaps back to 90 seconds when full location returns.
  • Scan length and the high-frequency Wi-Fi flag come from MobileConfig. The keys in the binary are hash-obfuscated. Uploads carry experiment_name and friends, so the cycle is A/B tested per account.
  • Foreground only. Backgrounding stops the quiet scans.

Each cycle uploads latitude, longitude, accuracy_meters, bearing_degrees, speed_meters_per_second, is_spoofed (they check for mock GPS), timestamp_ms, quadkey, and whether the app is foreground or background.

What each permission grants, and what this build does with it

Permission What people think it is What this build actually does
ACCESS_FINE_LOCATION and coarse Check-ins and nearby places The 90 second cycle. Fine location is also the gate for the Wi-Fi AP scan. Coarse alone does not get that scan.
ACCESS_WIFI_STATE Is Wi-Fi on One WifiManager.startScan() per cycle, then getScanResults(). Fresh results go up as BSSID, SSID, RSSI, and a captive-portal flag. That is your home, work, and the neighbors, and it feeds an AP-to-location database. Wi-Fi Aware is only listened for. The app does not even declare ACCESS_WIFI_AWARE, so it is not starting a peer scan.
BLUETOOTH_SCAN / advertise, plus the old BLUETOOTH permissions Connect to a device, cast a video One unfiltered BluetoothLeScanner sweep per cycle, a few seconds, foreground only. Upload is raw advertisement bytes (base64), MAC, and RSSI. No scan filter, so it is every phone, watch, and beacon in range. The code matches iBeacon (ff4c000215) and Eddystone (16aafe). On a phone where Bluetooth connect was denied, the scan threw and was skipped.
READ_PHONE_STATE Phone number / call state Cell identity on the same cycle: base station id, tower lat/lon, network id, system id, CDMA Ec/Io. On a phone where this was denied, the cycle still ran and the tower block just degraded.
RECORD_AUDIO Someone is always listening Mic for things you start: WebRTC, Reels/Stories AudioRecord, Live (“Facecast”). I did not find a continuous background mic listener. On the test phone this permission was denied.
CAMERA, CAPTURE_VIDEO_OUTPUT Stories and calls Camera and screen-share / watch-party media projection when you use those features. Camera is not required. Denied on the test phone.
NFC Tap to pay, vaguely Reader mode you start. Tap-to-read and the MFT card scanner (enableReaderMode). Not a passive loop.
READ_CONTACTS / WRITE_CONTACTS Find friends Friend finder and login. Write is in the manifest. On the test phone both were granted, which is more than a read-only social app needs.
GET_ACCOUNTS and the account-manager permissions Sign in with an account on the phone FB4A account authenticator. Creates a sync account. Granted on the test phone.
READ_CALENDAR / WRITE_CALENDAR Add an event Event sharing. Both denied on the test phone.
READ_CALL_LOG See who you called Click-to-call lead ads. There is a call-state receiver for that path.
CALL_PHONE Dial a business from the app That. Denied on the test phone.
READ_MEDIA_* and selected-photos Pick a photo to post Media picker. Android 14 “selected photos only” is honored. All denied on the test phone.
Ads ID and AD_SERVICES_* including Topics Ads GAID, plus Android 13 attribution, custom audience, and per-app Topics. That last one is another ID on top of the advertising ID.
ACCESS_NETWORK_STATE Online or not NTS Wi-Fi diagnostics on radio events, not a timer: BSS load, signal, frequency, center frequencies, capabilities, timestamp.
POST_NOTIFICATIONS The banners The banners. Silent push is FCM (c2dm), which is install-time. Denying notifications does not stop the wake.
RECEIVE_BOOT_COMPLETED and the locked/pre-boot receivers Start at boot Start at boot.
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS A battery prompt Asks to sit outside Doze so background work is less likely to be deferred. Separate user action from the runtime grants.
SYSTEM_ALERT_WINDOW Draw over other apps Overlays. Broad, and not required for the feed.
BATTERY_STATS Battery screen Session drain attribution. Battery percent at the start and end of a session, plus USB / AC / wireless charging from BATTERY_CHANGED.
DETECT_SCREEN_CAPTURE / DETECT_SCREEN_RECORDING Nothing, you never saw a prompt Install-time. Knows when you screenshot or screen-record their video. Copyright enforcement.
ANSWER_PHONE_CALLS, MANAGE_OWN_CALLS, READ_PHONE_NUMBERS Calls In-call UI for watch-party and business calls. Phone-number read was denied on the test phone.
USE_BIOMETRIC / USE_FINGERPRINT They read your fingerprint Only the MFA authenticator (com.meta.mfa). The OS shows the prompt. The app gets a yes or no.
MODIFY_AUDIO_SETTINGS and VIBRATE Ringtones and buzzes Playback and a parametric haptic player. That is a waveform vibrator, which is the usual building block people point at for an ultrasonic side channel. I did not find it used as a microphone.
USE_FULL_SCREEN_INTENT A full-screen alert High-priority notifications that can take the screen.
WAKE_LOCK Keep the CPU up Sync and collection. The manifest has a Play policy note justifying the wake lock.
GET_TASKS / REORDER_TASKS See other apps Own-task and foreground state. Not a full app-switcher dump on modern Android.
INTERNET plus FCM, Amazon, and Nokia push The network Three vendor push channels, not one. Denying the notification banner does not remove these.
DOWNLOAD_WITHOUT_NOTIFICATION Downloads Large fetches without the system “downloading” notice.
INSTALL_SHORTCUT A home-screen icon A home-screen icon.

What still runs when every runtime permission is denied

None of this needs a grant on stock Android:

What How Why it matters
Accelerometer HeartbeatCollectorAppJob asks for sensor type 1. There is also a rage-shake registration and a feed-scroll “originator.” A background job can see how the phone is moving. No prompt.
Ambient light Sensor type 5, plus AmbientLightMonitorAppJob. A light sample on a timer. No prompt.
Battery and charging BATTERY_CHANGED, start and end percent per session. They can attribute drain to a session.
App open and close app_use_state on the uploads, SignalHooks on foreground transitions. A heartbeat of when you actually use it.
Installed apps getInstalledPackages(0) on the Odin device context. getRunningAppProcesses() shows up in a bunch of perf counters. On Android 11+ this is limited to the <queries> list plus system apps. On 10 and below it was the whole install list.
Device fingerprint device_id is on a huge number of upload fields, plus model, manufacturer, screen, and family_device_id. The ID that ties the rest together. No prompt.
Experiment bucket experiment_name on the location upload. Your session is labeled with which test you are in.

If you only half-deny it

  • Approximate location only. Coarse GPS still uploads, flagged approximate. Cell can still coarse-locate you. The cycle slows down the backoff curve. The Wi-Fi AP scan stops, because that gate wants fine location.
  • Bluetooth allowed, but hide nearby devices. On 12+ the advertisement bytes and RSSI can still go up. MACs come back randomized.
  • Bluetooth denied. The census is skipped. The in-app browser’s navigator.bluetooth goes with it.
  • Notifications denied. FCM can still wake the process. Boot receivers still run.
  • Selected photos only. That limit is real in this build.

The in-app browser is a second browser with your grants

The Helium / Chromium split is a full browser. Pages inside it can use navigator.bluetooth, the sensor APIs, and WebRTC audio. Those are per-site prompts, sitting on top of the same Facebook grants. It also keeps its own cache, cookies, and IndexedDB in the app’s private storage.

Private storage is the other half of this. The code opens fb.db, notifications, bookmarks, an upload queue, and Omnistore (the native SQLite cache Messenger and Instagram share, synced over MQTT). MobileConfig lands in mc_overrides.json and mc_silent_push.json. There is an fbpermissions.json that shadows what you granted. A normal adb shell cannot read /data/data/com.facebook.katana. The filenames are visible in the bytecode. The rows are not, without root.

What I would actually change

Approximate location, and deny Bluetooth scan. That kills the AP census and the BLE census, and it slows the GPS trace. Device id, battery, light, accelerometer, lifecycle, and experiment tags do not care what you tap. Uninstall is the off switch for those.

Smali class names are Redex-obfuscated. Where a human name survived it was in __redex_internal_original_name. I am not claiming every server flag is on for every account. I am claiming the code paths above are in this build and wired to that 90 second job. Because the phone was not rooted, I did not watch the upload leave the radio. I watched the code that builds it, and the permission state that lets it run.

What this means if you do not live in the code

Facebook on Android is a watcher with a clock. Open the app and, about every minute and a half, it can send Meta three pictures of the moment: where you are, which wireless networks are around you, and which Bluetooth devices are around you. Do that all afternoon and it is not a check-in. It is a log.

The location prompt does not say that. It reads like maps and nearby places. The Wi-Fi part is the one that places you inside a building when GPS is fuzzy, and it only runs if you allowed precise location. The Bluetooth part is a census of devices in the room. Devices stand in for people. Store beacons stand in for which shop you walked through.

The risks that follow from this evidence, not from a guess:

  • A trail, not a pin. Speed, direction, and a spoof check mean they can tell a real walk from a fake location, and they can reconstruct the route.
  • A map of routers, contributed by your phone. Home, work, and the neighbors’ network names are useful to you as a fingerprint and useful to them as a location database.
  • Who was nearby. An unfiltered Bluetooth scan is not “connect my headphones.” It is a list.
  • The ID survives the prompts. Deny location and Bluetooth and the 90 second bundle gets thinner. The device identifier, battery, light, motion, and “the app was open” still go out. Those need no permission on a stock Android phone.
  • The rate is not fixed for everyone. The 90 seconds is in this version of the app. How aggressive each scan is can be changed from their side, for some accounts and not others.

What I am not saying. I did not find a microphone running on that timer. Camera and mic are real, and they are gated by the permissions you tap, and on the test phone both were denied. The quiet part is location, Wi-Fi, and Bluetooth while the app is on screen.

If you keep the app: approximate location, and deny Bluetooth scanning. That is the pair that hits this clock. Uninstall is the off switch for the rest. The same company built Muse to go further than watching. On iOS the same location kit is in the app, and on the jailbroken phone I checked, this 90 second sampler was not the thing that actually ran.

Leave a Reply

Your email address will not be published. Required fields are marked *