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
74 changes: 74 additions & 0 deletions docs/store/play-data-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Google Play — Data Safety declaration

This document records the answers to fill into the **Play Console → App content →
Data safety** form, with the reasoning behind each answer so it can be defended
or revisited. It reflects the app as of 2026-07-06. Update it if data handling
changes.

See also: [`privacy-policy.md`](./privacy-policy.md).

## Key facts the form is based on

- The app stores everything **locally on the device** (SQLite DB + OS secure
storage + a local log file). Nothing is sent to the developer.
- The only network traffic is a **direct, user-initiated connection to the
game (MUD/MUSH) server the user typed in.** That server is a third party the
user chooses, not an endpoint we operate.
- No analytics, ads, crash-reporting SDKs, or other third-party SDKs are bundled.

## Form answers

### 1. Data collection and sharing

**Does your app collect or share any of the required user data types?** → **No.**

Rationale: In Play's model, "collected" means data transmitted off the device to
the developer or a service acting on the developer's behalf, and "shared" means
transferred to a third party. SharpClient does neither. Passwords, character
names, server addresses, transcripts, triggers, and logs are all kept in the
app's private on-device storage and are never transmitted to us.

The character name and password **are** sent to the game server at login, but:
- the server is an endpoint **the user explicitly enters and connects to**, and
- the transfer is core, user-initiated functionality (the same category as an
SSH, FTP, email, or IRC client sending credentials to a user-specified host),
which Play's guidance does not treat as developer "collection" or "sharing."

> ⚠️ **Confirm before submitting:** this "No data collected/shared" position
> rests on the user-directed-server interpretation above. It is the standard
> position for this class of app, but you own the final declaration. If you
> prefer maximum caution, you can instead declare *App activity / "Other
> user-generated content"* and *Personal info / "User IDs"* as **collected but
> not shared**, processed for **App functionality**, and mark it not required —
> at the cost of a more alarming store label. Recommended answer remains **No.**

### 2. Security practices

- **Is data encrypted in transit?**
Disclose honestly: connections to game servers use **Telnet, which is not
encrypted**. If the form scopes this question only to *collected* data (of
which there is none), it may be N/A — but the privacy policy states the
cleartext fact plainly regardless.
- **Data stored on device is protected:** saved passwords use Android
Keystore–backed secure storage; automatic cloud backup is disabled
(`allowBackup=false`).

### 3. Data deletion

- **Can users request that their data be deleted?** → Users delete data
themselves in-app, and uninstalling removes all local data. No server-side
data exists to delete, so no deletion request mechanism is needed.

## Privacy policy URL (required by Play)

The Data Safety form and the store listing both require a public **Privacy
policy URL**. `privacy-policy.md` is the content; it needs to be reachable at a
stable public URL. Options, easiest first:

1. **GitHub Pages** — enable Pages for the repo and publish `privacy-policy.md`,
giving e.g. `https://sharpmush.github.io/SharpClient/privacy-policy`.
2. **Raw GitHub URL** — Play accepts a link to the rendered file, e.g.
`https://github.com/SharpMUSH/SharpClient/blob/master/docs/store/privacy-policy.md`.

Pick one and paste it into both **Data safety** and **Store listing → Privacy
policy**.
Binary file added docs/store/play-feature-graphic-1024x500.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/store/play-listing-icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions docs/store/privacy-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# SharpClient — Privacy Policy

**Effective date:** 2026-07-06
**Applies to:** the SharpClient Android application ("the app") published by the
SharpMUSH project ("we", "us").

## Summary

SharpClient is a client for connecting to text-based multiplayer games (MUD/MUSH
servers). **We do not collect or receive any of your personal data.** The app
sends what you type only to the game server you choose to connect to — never to
us or any third party. It has no analytics, no advertising, no tracking, no user
accounts with us, and no third-party SDKs. All data the app stores stays **on
your device**.

## What the app stores on your device

All of the following is stored only in the app's private storage on your device
and is never sent to us:

| Data | Where it is stored | Purpose |
| --- | --- | --- |
| Game server addresses (name, host, port) | Local SQLite database (`sharpclient.db`) | So you can reconnect to games you've saved |
| Character names | Local SQLite database | To identify your login on each game |
| Character passwords / connect strings | **OS-encrypted secure storage** (Android Keystore–backed) | To auto-log-in when you choose to save them |
| Triggers and aliases you create | Local SQLite database | Your automation rules |
| Session history / game transcripts | Local SQLite database (full-text index) | So you can scroll back and search what happened |
| Diagnostic and crash logs | Local file in the app's private folder (`logs/sharpclient.log`) | So you can export logs to report a bug |

The database and logs live in the app's private, sandboxed storage. Automatic
cloud backup is **disabled** (`allowBackup=false`), so this data is not copied to
Google Drive or any other backup service.

## Data sent over the network

When you connect to a game, the app opens a direct network connection **to the
server address you entered** and sends what you type — including your character
name and password at login — to **that server**. It does not send anything to us
or to any other party.

> **Important — connections are unencrypted.** Like virtually all MUD/MUSH
> servers, connections use the Telnet protocol, which is **not encrypted**. Your
> username, password, and everything you send and receive travel in plain text
> between your device and the game server. Only connect to servers you trust, and
> avoid reusing important passwords for game characters.

We are not the operator of the game servers you connect to. Those servers are
run by independent third parties and have their own privacy practices, which we
do not control.

## Data sharing

We do not share your data with anyone, because we never receive it in the first
place.

## Children's privacy

The app does not knowingly collect any personal information from anyone,
including children.

## Deleting your data

- Delete individual saved games, characters, or history from within the app.
- Uninstalling the app removes all of its local data, including the database,
saved passwords, and logs.

## Changes to this policy

If this policy changes, the updated version will be published at the same
location with a new effective date.

## Contact

Questions about this policy can be raised via the project's issue tracker:
<https://github.com/SharpMUSH/SharpClient/issues>.
96 changes: 96 additions & 0 deletions docs/store/release-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# First Google Play release — checklist

End-to-end sequence to get SharpClient onto the Play Store (internal testing
first). Ordered; each step's prerequisites are the steps above it.

## 0. Prerequisites (one-time)

- [ ] A Google Play Developer account (one-time US$25 registration).
- [ ] Decide the **package name is final**: `com.sharpmush.sharpclient`. This is
permanent once uploaded and can never be changed.

## 1. Land the code changes

- [ ] Merge the branch carrying: applicationId rename, `ForegroundScale="0.65"`
icon fix, and `docs/store/` (this checklist, privacy policy, data-safety,
512 icon). CI (tests + Android head build) must be green.

## 2. Create the signing keystore + configure secrets ⚠️ REQUIRED before any release

The release workflow (`.github/workflows/release-apk.yml`) **fails fast** if the
keystore secret is absent — it will never ship an unsigned/debug build. So this
must be done before tagging. Full details in
[`../release-signing.md`](../release-signing.md).

- [ ] Generate an upload keystore:
```bash
keytool -genkeypair -v -keystore release.keystore \
-alias sharpclient -keyalg RSA -keysize 2048 -validity 10000
```
- [ ] Store it + passwords as repository secrets (Settings → Secrets → Actions):
`ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`,
`ANDROID_KEY_PASSWORD`.
- [ ] **Back up the keystore + passwords somewhere safe.** With Play App Signing
(below) a lost *upload* key is recoverable; without it, losing the key means
you can't publish updates under this app identity.

## 3. Pick the first version and tag

`release-apk.yml` derives the Android `versionName`/`versionCode` from the release
tag (`vMAJOR.MINOR.PATCH`):
`versionCode = major*100000000 + minor*10000 + patch`.

- [ ] Choose the version by **continuing the existing release-tag line**, not
restarting. The GitHub release tags run `v0.1 … v1.3` (v1.3 =
`versionName 1.3 / versionCode 100030000`), so the next release is **`v1.4`**
(→ versionCode `100040000`). A `v0.x` reset would derive a *lower* code and is
wrong. A Play `versionCode` can never be reused or decreased.
- [ ] Publish a **GitHub Release** with that tag. This triggers the workflow,
which builds and attaches a **signed `.aab`** (for Play) and a signed `.apk`
(for sideloading) to the release.

## 4. Play Console — create the app + upload

- [ ] Create the app in Play Console (default language, app name "SharpClient",
app/not game — though it's a game *client*, "App" is fine).
- [ ] Accept **Play App Signing** (recommended): Google holds the app signing
key; your keystore from step 2 is the *upload* key.
- [ ] Create an **Internal testing** release and upload the `.aab` from step 3.
Add tester email addresses.

## 5. Play Console — required listing content

- [ ] **App icon:** `docs/store/play-listing-icon-512.png` (512×512).
- [ ] **Feature graphic:** `docs/store/play-feature-graphic-1024x500.png`
(1024×500, required) — green M on near-black with a terminal-prompt tagline.
- [ ] **Screenshots:** at least 2 phone screenshots (existing shots live in
`.github/assets/` and may be reused/resized).
- [ ] **Short + full description.**
- [ ] **Privacy policy URL:** host `docs/store/privacy-policy.md` publicly (see
that file / the data-safety doc for options) and paste the URL.
- [ ] **Data safety form:** fill per [`play-data-safety.md`](./play-data-safety.md).
- [ ] **Content rating** questionnaire.
- [ ] **Target audience**, **ads declaration** (no ads), **news/COVID** (no).

## 6. Submit

- [ ] Send the internal-testing release for review; once approved, testers can
install via the opt-in link. Promote to closed/open testing → production
when ready.

---

### What's already done in-repo

- Signed `.aab` + `.apk` CI pipeline (`release-apk.yml`), tag→version derivation.
- Target API 35 (net10 android default), minSdk 24, `allowBackup=false`.
- Final package name `com.sharpmush.sharpclient`.
- Adaptive icon fixed (`ForegroundScale=0.65`) + 512 listing icon.
- Privacy policy + data-safety content.

### Still needs a human decision / action

- Keystore generation + secret upload (step 2).
- Version choice + tagging (step 3) — next in line is **`v1.4`**; held pending
keystore secrets + your go-ahead.
- Play Console account, listing copy, feature graphic, content rating (steps 4–6).
2 changes: 1 addition & 1 deletion src/SharpClient.App/Platforms/Android/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
components. Explicit declaration required for Android 12+ (API 31).
-->
<service
android:name="com.sharpmush.sharpclient.app.ConnectionKeepAliveService"
android:name="com.sharpmush.sharpclient.ConnectionKeepAliveService"
android:exported="false"
android:foregroundServiceType="connectedDevice" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ namespace SharpClient.App;
// semantically correct type. Crucially, connectedDevice has NO 6-hour time cap (unlike dataSync
// which is capped at 6 h/24 h on Android 15+), which matters for sessions that last all day.
//
// The Name is pinned to the real ApplicationId (com.sharpmush.sharpclient.app) so the static
// The Name is pinned to the real ApplicationId (com.sharpmush.sharpclient) so the static
// AndroidManifest.xml can reference this service with a stable android:name to attach the
// foregroundServiceType attribute (which cannot be set via the C# ServiceAttribute). It MUST stay
// in lock-step with <ApplicationId> in SharpClient.App.csproj and the <service> entry in the
// manifest, otherwise the merger emits two distinct services and startForeground throws.
[Service(Exported = false, Name = "com.sharpmush.sharpclient.app.ConnectionKeepAliveService")]
//
[Service(Exported = false, Name = "com.sharpmush.sharpclient.ConnectionKeepAliveService")]
[SupportedOSPlatform("android26.0")] // Foreground services + NotificationChannel require API 26
internal sealed class ConnectionKeepAliveService : Service
{
Expand Down
8 changes: 5 additions & 3 deletions src/SharpClient.App/SharpClient.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
<ApplicationTitle>SharpClient</ApplicationTitle>

<!-- App Identifier -->
<ApplicationId>com.sharpmush.sharpclient.app</ApplicationId>
<ApplicationId>com.sharpmush.sharpclient</ApplicationId>

<!-- Versions -->
<!-- Versions (release-apk.yml overrides these from the git tag). -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>

Expand Down Expand Up @@ -74,8 +74,10 @@
cleanly on it (adaptive icons, Android 8+); the teal base blends into
the matching green so the background looks solid. Previously #0b0e12,
which put the dark mark on near-black and rendered as "black on grey".
ForegroundScale="0.65" insets the foreground into the adaptive-icon safe zone
(MAUI renders it full-bleed otherwise, so launcher masks crop the mark).
-->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#00f5b7" />
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" ForegroundScale="0.65" Color="#00f5b7" />

<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
Expand Down
Loading