Dish for Android privacy policy
Effective 2026-05-21. This policy applies to the Dish for Android application published by TinkerNorth ("we", "TinkerNorth", "Dish") and distributed through GitHub Releases. If you're reading this from inside the app, you can also find the latest version at https://dish.tinkernorth.com/privacy/dish-android/.
The short version
Dish for Android does not collect any personal information, device identifiers, usage analytics, or advertising data. It does not have an account system. The only TinkerNorth-operated processor it ever contacts is Firebase Crashlytics, and only when the app crashes. Never during normal use.
Crash reports carry a stack trace, the device model, the Android version, and an auto-generated install UUID. They do not carry your gamepad input, the names or IP addresses of Satellites you pair with, your Wi-Fi SSID, or any contact information. The full Crashlytics scope is in section 5 below. You can opt out at any time from the gear icon on the main screen → Settings → Diagnostics → Share crash reports; the choice persists across launches and is honoured before any crash can be uploaded on the next start.
Everything else the app does happens on your device or between your device and the Satellite servers you have explicitly paired with on your own LAN. The encrypted UDP gamepad stream is local traffic only; we do not see or process it.
The longer version below explains exactly what the app does on device, what permissions it requests and why, what data lives where, and what your rights are under GDPR (EU), UK GDPR, CCPA / CPRA (California), and LGPD (Brazil).
Who we are
TinkerNorth is a single-developer project that publishes Dish and the Satellite suite as free, open-source software. We do not operate a commercial service tied to Dish for Android. For privacy questions, contact privacy@tinkernorth.com.
1. Information we do not collect
For clarity, here is what Dish for Android does not collect, even though many apps in this category do:
- No name, email address, phone number, or account credentials.
- No advertising ID (AAID), Android ID, IMEI, or other ad-tracking device IDs.
- No precise or approximate location.
- No contacts, calendar, photos, files, or microphone audio.
- No general usage analytics. We do not run Firebase Analytics, Google Analytics, or any other event-tracking pipeline. The only off-device data is the crash-only Crashlytics scope described in section 5.
- No advertising data and no in-app advertising.
- No metadata about the games you play, the controllers you use, or the times you use them.
2. Information processed locally on your device
The following information is processed locally on your device. It does not leave your device, except where explicitly noted that it goes to a Satellite server you have paired with on your own LAN:
- Controller inputs. Button presses, analog-stick positions, and trigger pressures from the on-screen touch overlay or a physical gamepad attached to your phone. These are end-to-end encrypted (ChaCha20-Poly1305) and sent only to the Satellite servers you have paired with, over UDP on your LAN.
- Motion sensor readings. If you enable gyro / accelerometer forwarding, the phone's IMU output is sampled at up to 250 Hz, encrypted, and sent to the paired Satellite. This is off by default and toggled in the app's overlay activity.
- Battery state. The app reads your phone's battery level and charging status from the Android
BatteryManagerso it can forward a battery capability to your Satellite once every 30 seconds and on state transitions. This is sent encrypted to the paired Satellite only. - Paired Satellite list. When you pair with a Satellite, the app stores the satellite's IP address, port, display name, device identifier, and the per-satellite 256-bit symmetric key derived during the X25519 pairing handshake. This is held in Android's per-app
SharedPreferencesand is sandboxed to the app. - Remembered Bluetooth hosts. If you use the Bluetooth HID passthrough feature, the app stores the MAC address and name of each remembered host in
SharedPreferencesso you can reconnect without re-pairing. - Discovered LAN servers. The IP address, port, and name of any Satellite advertised on your local network via mDNS or UDP broadcast, held in memory for the duration of the discovery cycle. Not persisted across launches unless you choose to pair.
- App preferences. Per-slot controller type (Xbox / PlayStation), per-game touch overlay layouts, locale selection. Stored in
SharedPreferenceson your device. - Android logs. Like every Android app, Dish writes diagnostic messages to the Android system log (
logcat). These never leave your device; they're visible to you (and to anyone with USB debugging access to your phone) but they are not transmitted anywhere.
3. Permissions the app requests, and why
The permissions Dish for Android declares in its AndroidManifest.xml are listed below with the exact reason each one exists.
INTERNET. Required to send encrypted UDP gamepad packets to a Satellite on your LAN and to perform the HTTPS pairing handshake. We do not contact any servers we operate.ACCESS_NETWORK_STATE. Lets the app notice when you have no Wi-Fi and surface a helpful banner instead of silently failing.ACCESS_WIFI_STATE. Used to detect whether you are on Wi-Fi at all, so the app can warn you that Satellite is unreachable over mobile data.CHANGE_WIFI_MULTICAST_STATE. Required for mDNS / Bonjour discovery of Satellite servers on your LAN. Without it, the only discovery path is the legacy UDP broadcast.WAKE_LOCK. Holds the screen / CPU awake during an active streaming session so a button press doesn't get delayed by Doze.FOREGROUND_SERVICEandFOREGROUND_SERVICE_CONNECTED_DEVICE. Android requires a foreground service to keep a long-running connected-device session alive when the app is in the background. The connected-device service type matches the controller-to-host use case.POST_NOTIFICATIONS(Android 13+). Posts the ongoing-session notification required by the foreground service, plus optional banners for connection state changes. You can decline and the app still functions; the notification just isn't visible.VIBRATE. Routes rumble events forwarded from your game to the phone's vibrator(s) so you feel them in your hand.BLUETOOTH/BLUETOOTH_ADMIN(Android ≤ 11),BLUETOOTH_CONNECTandBLUETOOTH_ADVERTISE(Android 12+). Required only if you use the Bluetooth HID passthrough feature to register your phone as a virtual Bluetooth controller for another host. We never scan for or log devices you have not explicitly chosen to pair.
The app does not request any of these permissions: location (fine or coarse), camera, microphone, contacts, calendar, SMS, call log, body sensors, files / media. If a future version needs any of them, this policy will be updated before the release.
4. Network behavior
Dish for Android initiates network traffic in four places:
- LAN discovery. The app sends and listens for mDNS / Bonjour queries on the multicast address
224.0.0.251:5353, and listens for legacy UDP broadcast beacons on port 9879. This traffic stays on your local network. - Pairing handshake. When you tap a discovered Satellite and enter the 4-digit PIN displayed on the Satellite, the app makes one HTTPS POST request to the Satellite's IP on TCP port 9443. The request body contains a 32-byte X25519 public key, the PIN, the device identifier, and the device label. The Satellite's certificate is self-signed and not validated against a public CA; trust is established by the PIN, which is consumed once and then destroyed.
- Streaming session. Once paired, the app sends ChaCha20-Poly1305-authenticated UDP packets to the Satellite's IP on port 9876 and listens for return-path packets (rumble, lightbar, status events) on the same socket. Heartbeats fire every 2 seconds.
- Crash reports. If (and only if) the app crashes or ANRs, the bundled Firebase Crashlytics SDK uploads a crash report to Google. The detailed scope and exclusions are in section 5. There is no normal-operation telemetry path: no analytics endpoint, no "check for updates" endpoint, no remote configuration endpoint. Update checks are performed by the Google Play Store or by you manually.
All outbound HTTPS traffic is also gated by an explicit network_security_config.xml that denies cleartext to every host, so a future regression cannot silently downgrade a connection to plain HTTP.
5. Crash reporting (Firebase Crashlytics)
Dish for Android uses Firebase Crashlytics (provided by Google LLC) to collect crash and ANR reports so we can diagnose bugs that escape testing. Crashlytics is the only data flow from your device to a TinkerNorth-operated processor.
When the app crashes (an unhandled Kotlin exception, a fatal signal in the native JNI layer, or an Application Not Responding event), Crashlytics uploads:
- The stack trace (Kotlin and/or native).
- Device model, manufacturer, Android version, locale, free RAM, and free disk at crash time.
- The app's version code and version name.
- A Firebase Installation ID: an auto-generated UUID that lets us count unique affected installs without identifying you personally.
- The last few log lines we explicitly flagged as relevant. We do not upload general
logcatcontent.
Crashlytics does not receive:
- The names, IPs, or MAC addresses of Satellites or Bluetooth hosts you pair with.
- Your gamepad input events, including motion and touchpad streams.
- Your Wi-Fi SSID, BSSID, or IP address.
- Any contact information, email address, or name.
- The contents of the encrypted UDP gamepad wire.
Crashlytics retains crash data for 90 days, then deletes it. See Google's Firebase Privacy and Security policy for Google's role as a processor. TinkerNorth is the data controller; Google is the processor.
Opting out. Tap the gear icon on the main screen, then in Settings under Diagnostics flip Share crash reports off. The switch is on by default. Flipping it off calls FirebaseCrashlytics.setCrashlyticsCollectionEnabled(false) and persists the choice in app-private storage. The next app start applies the saved preference before any code path that could produce a crash report, and the controller observes the preference for the rest of the session so a mid-session opt-out takes effect immediately. The preference is included in Android cloud backup, so it survives device transfers. Crash reports already collected before you opt out remain on Firebase until its standard 90-day retention expires, then are deleted.
Build variants that ship without a google-services.json (for example, anyone building from source themselves) have no Crashlytics SDK initialized at all, even if the toggle is on. The controller detects this via FirebaseApp.getApps and no-ops.
We do not run Firebase Analytics. Crashlytics and Analytics are separate Firebase products and this app links only Crashlytics. We have deliberately kept the firebase-analytics dependency out of the build. Bundling it would auto-collect events (first_open, session_start, screen_view, app_remove, etc.) and would pull com.google.android.gms.permission.AD_ID into the production manifest. Neither is true today. If Analytics is ever added, this policy will be updated before the change ships.
6. Third-party services and SDKs
Dependencies that ship inside the APK:
- AndroidX (Google): standard Android support libraries.
- Material Components for Android (Google): Material 3 UI widgets.
- AndroidX GameActivity (Google): native game-loop bridge.
- libsodium (built from source via NDK): the cryptographic primitives library, used for ChaCha20-Poly1305 and X25519. Does not initiate any network traffic.
- Firebase Crashlytics + Firebase Installations (Google): crash + ANR reporting only, scope detailed in section 5. The SDK is always included in the APK; it only initializes when the build was assembled with a valid
google-services.json. Without that file (for example, anyone building from source without a Firebase project), Crashlytics no-ops at runtime and nothing leaves the device.
Firebase Analytics is deliberately not in the build. Including it would auto-collect events and pull the AD_ID permission into the manifest, both of which conflict with this policy. The app/build.gradle.kts file has a comment explaining the choice so a future contributor sees the constraint before re-adding the dependency.
The full dependency list is in app/build.gradle.kts in the dish-android repo and the software bill of materials (SPDX + CycloneDX) shipped with every tagged release.
If you obtained Dish for Android through Google Play, the Play Store itself is governed by Google's privacy policy. That is separate from this policy and outside our control.
7. Data we share
We do not sell, rent, or share personal information for advertising, marketing, or any commercial purpose. We have no relationships with advertising networks, analytics vendors, fraud-detection vendors, or marketing partners. The only third party that ever receives any data from this app is Google LLC, acting as a processor for Firebase Crashlytics under the scope described in section 5.
8. Data retention
On-device data lives until you uninstall the app or use Android's "Clear storage" option in Settings > Apps > Dish.
Two pieces of app-private data interact with Android cloud backup differently:
- Paired-Satellite shared keys and remembered Bluetooth hosts (file
connection_store.xml) are excluded from auto-backup and device transfer, so the encryption material never leaves your device. Re-pairing is the right answer when you move to a new phone. - The crash-reporting opt-out preference (file
user_preferences.xml) is included in cloud backup, so a deliberate "no thanks" carries over to a replacement device without you having to flip the toggle again.
Crashlytics retains crash reports for 90 days on Google's infrastructure, then deletes them.
9. Children's privacy
Dish for Android is not directed at children under 13 (or under the equivalent age in your jurisdiction: 14 in South Korea, 16 in most of the EU). The Crashlytics scope described in section 5 contains no information that could identify a child as a child (no name, no birthdate, no email). If you believe a child's data has been collected through this app, email privacy@tinkernorth.com with the Firebase Installation ID or a recent crash timestamp and we will purge the associated reports.
10. International transfers
On-device data does not leave your device. The Crashlytics crash-report payload is sent to Google LLC and may be processed in any country where Google operates infrastructure, including the United States. Transfers from the EEA / UK to the US rely on Standard Contractual Clauses; see Google's Data Processing and Security Terms for the full transfer mechanism documentation.
11. Your rights
11.1 GDPR (European Economic Area)
If you are in the EEA, the General Data Protection Regulation (GDPR) gives you the rights of access, rectification, erasure, restriction of processing, data portability, and objection. The only personal data we process for users in the EEA is the Firebase Installation ID and the crash-report payload described in section 5. To exercise any of those rights:
- Stop collection (right to object / withdraw consent): open the app, tap the gear icon on the main screen, then in Settings under Diagnostics flip Share crash reports off. The preference takes effect for the rest of the session immediately and is re-applied before any crash can be uploaded on every subsequent app start. Uninstalling the app or building from source without a
google-services.jsonare equivalent stop-collection paths. - Access, correction, deletion, portability: email privacy@tinkernorth.com with the approximate date / time of the crash and your device model. We do not surface the Firebase Installation ID in the in-app UI, so we identify reports by timestamp + device model rather than asking you to dig the ID out of debug logs. We will look up whether any reports match and respond within 30 days as required by GDPR Art. 12(3).
Legal bases. Local on-device processing (controller input, pairing keys, remembered hosts) relies on your consent, expressed by installing the app and granting permissions, and the performance of a contract when you ask the app to pair with and stream to your Satellite. Crashlytics processing relies on our legitimate interest (GDPR Art. 6(1)(f)) in maintaining a working app, balanced against the strict no-PII scope described in section 5.
International transfers. Crashlytics data is processed by Google LLC, which may transfer data outside the EEA under Standard Contractual Clauses. See Google's Data Processing and Security Terms.
You have the right to lodge a complaint with your national data protection authority. A list is maintained by the European Data Protection Board.
11.2 UK GDPR (United Kingdom)
If you are in the UK, the UK GDPR mirrors the rights in section 11.1 above. Your supervisory authority is the Information Commissioner's Office (ICO). The same Crashlytics scope and opt-out mechanics apply.
11.3 CCPA / CPRA (California)
If you are a California resident, the California Consumer Privacy Act (as amended by the CPRA) gives you the rights to know, delete, correct, opt out of sale or sharing, and limit use of sensitive personal information. We do not sell or share personal information (as defined under CCPA / CPRA), and we have not done so in the preceding 12 months. No "Do Not Sell or Share My Personal Information" link is required because there is no sale or sharing to opt out of.
To exercise a "right to know" or "right to delete" against the crash-report scope in section 5, email privacy@tinkernorth.com with your Firebase Installation ID (or a recent crash timestamp and device model). You may also designate an authorized agent to make a request on your behalf.
11.4 Other US states
Residents of Virginia (VCDPA), Colorado (CPA), Connecticut (CTDPA), Utah (UCPA), Texas (TDPSA), and other states with comprehensive privacy laws have equivalent rights. The same Crashlytics scope and opt-out mechanics apply.
11.5 LGPD (Brazil)
If you are in Brazil, the Lei Geral de Proteção de Dados (LGPD) gives you the rights of confirmation, access, correction, anonymization, portability, and deletion. The same Crashlytics scope and opt-out mechanics apply. To exercise any right, email privacy@tinkernorth.com. Our DPO contact is the same address.
12. Security
Every packet exchanged between Dish and Satellite is sealed with ChaCha20-Poly1305 IETF using a 256-bit symmetric key derived from an X25519 key exchange seeded by a four-digit pairing PIN. The key never leaves your device or the paired Satellite, and the PIN is destroyed after pairing. For the full threat model see /security/ on this site.
To report a vulnerability, see SECURITY.md in the Satellite repo.
13. Changes to this policy
We will update this policy if the app's behavior changes in a way that affects what's described here. Material changes will be announced in the app's release notes and the "Effective" date at the top of this page will be updated. The previous version of each policy is preserved in this site's git history.
14. Contact
Privacy: privacy@tinkernorth.com
Security: see SECURITY.md in the Satellite repo
General: open a GitHub issue on the dish-android repo
← Back to the privacy index