diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 000000000..6438618bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,43 @@ +name: Bug +description: The launcher, a script or the prompt did something wrong +labels: [bug] +body: + - type: dropdown + id: part + attributes: + label: Which part + options: + - Launcher (the home screen) + - Install script (build-and-install) + - Debloat or restore scripts + - tools/tv.py or b64push.py + - The prompt (PROMPT.md) + - Website (cleanyour.tv) + validations: + required: true + - type: input + id: tv + attributes: + label: TV model and Android version + placeholder: Xiaomi MiTV-MOOQ1, Android TV 10 + validations: + required: true + - type: textarea + id: what + attributes: + label: What happened, and what you expected + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + placeholder: | + 1. Press Down twice from the dock + 2. Press Home + - type: textarea + id: logs + attributes: + label: Output or screenshot + description: Script output, a `logcat` excerpt, or a screenshot pulled from the TV. + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..5f9d0f517 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: Question or "does it work on my TV?" + url: https://github.com/Gathin23/cleanyourtv/discussions/categories/q-a + about: Ask in Discussions. Issues are for bugs and TV lists. + - name: Show what you built + url: https://github.com/Gathin23/cleanyourtv/discussions/categories/show-and-tell + about: Screenshots, themes, forks. Launchers and ports also go in SHOWCASE.md by pull request. + - name: cleanyour.tv + url: https://cleanyour.tv + about: The site, with the prompt and the story. diff --git a/.github/ISSUE_TEMPLATE/tv-list.yml b/.github/ISSUE_TEMPLATE/tv-list.yml new file mode 100644 index 000000000..b52d1785e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/tv-list.yml @@ -0,0 +1,48 @@ +name: My TV +description: A TV that is not in examples/ yet, or a list that needs a change +title: "[TV] Brand Model (Android version)" +labels: [tv-list] +body: + - type: markdown + attributes: + value: | + Lists people have tested live in [`examples/`](../tree/main/examples). If you already have a working list, a pull request is even better: see [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md). + - type: input + id: model + attributes: + label: TV model + description: "`tools/tv.py --probe` prints the model, device codename, Android version and build." + placeholder: Sony BRAVIA KD-55X8509C (SVP-DTV15) + validations: + required: true + - type: input + id: android + attributes: + label: Android version and firmware build + placeholder: Android TV 11, build RTT1.230922.001 + validations: + required: true + - type: dropdown + id: stage + attributes: + label: How far did you get? + options: + - Could not connect over ADB + - Debloat worked, launcher did not + - Both worked, here is my list + - Something broke after the debloat + validations: + required: true + - type: textarea + id: details + attributes: + label: What happened + description: What you ran, what the TV did, what you expected. Paste the packages you disabled if you have them. + render: shell + validations: + required: true + - type: textarea + id: screenshot + attributes: + label: Screenshot + description: "Optional. `tools/tv.py 'screencap -p /sdcard/s.png'` then `tools/tv.py --pull /sdcard/s.png s.png`, and drop the file here." diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..ed59162c9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ + + +**Tested on:** + +### If this adds or changes a TV list +- [ ] `examples/-/debloat.txt`, header with model, Android version and firmware build +- [ ] Only packages I actually disabled and lived with; anything I had to re-enable is noted +- [ ] A row in `examples/README.md` and one in the README's TV table +- [ ] Nothing in the list uninstalls + +### If this touches the launcher +- [ ] Verified on a real TV, screenshot attached +- [ ] D-pad checked from every edge (Up, Down, Left, Right) +- [ ] No network, no background service, no new permission +- [ ] New strings in `values/strings.xml` and the other `values-*` folders +- [ ] Anything TV-specific is in `config.xml` or a theme, not in code diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..d6f0c9f89 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + lists-and-tools: + name: Lists, tools and docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Tools virtualenv + run: | + python3 -m venv tools/venv + tools/venv/bin/pip install -q -r tools/requirements.txt + - name: Example lists, README tables, shell helpers + run: python3 -m unittest discover -s tests -v + + launcher: + name: Launcher build and unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + - uses: android-actions/setup-android@v3 + with: + packages: platforms;android-34 build-tools;34.0.0 + - uses: gradle/actions/setup-gradle@v4 + - name: Unit tests and release build + working-directory: launcher + run: | + chmod +x gradlew + ./gradlew --no-daemon testReleaseUnitTest assembleRelease + - name: The launcher stays small + run: | + size=$(stat -c %s launcher/app/build/outputs/apk/release/app-release.apk) + echo "release APK: $((size / 1024)) KB" + test "$size" -lt 409600 || { echo "APK is over 400 KB; the launcher targets 1 GB TVs, keep it small"; exit 1; } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..ea4dc47aa --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,36 @@ +# Code of conduct + +This project exists because a few thousand people helped each other fix their TVs in the replies +to a tweet. Keep it like that. + +## Expected + +- Be kind. Many contributors are doing this on a parent's or grandparent's TV, in a second + language, on their first pull request. +- Assume good faith. A list that breaks something was tested on different firmware, not written to + hurt anyone. +- Say what you tested on. Specifics ("Android TV 11, build RTT1, Hisense A4") help; verdicts + ("doesn't work") do not. +- Respect the maintainers' decisions on scope. The launcher stays small, offline and without + prebuilt binaries; if your idea does not fit, a fork with a SHOWCASE row is a good outcome, not a + rejection of you. + +## Not accepted + +- Insults, harassment, slurs, sexualised language or imagery, or personal attacks, in issues, + pull requests, Discussions or the showcase. +- Publishing other people's private information: device identifiers, Wi-Fi names, IP addresses, + ADB keys or anything that could identify a TV or a home. +- Lists or instructions that uninstall, root, or intentionally damage a TV, or that target a + specific person's device. +- Spam, affiliate links, or passing off someone else's work as your own. + +## Enforcement + +Maintainers may edit, hide or remove content and block accounts that break these rules, with a +short explanation. To report something, open an issue, or if it is private, message +[@gathint on X](https://x.com/gathint). Reports are read by the maintainer and handled within a +few days. + +This is a short, plain-language code adapted in spirit from the +[Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). diff --git a/tests/test_examples.py b/tests/test_examples.py new file mode 100644 index 000000000..6661cd356 --- /dev/null +++ b/tests/test_examples.py @@ -0,0 +1,88 @@ +"""Checks every TV list in examples/ and the two tables that index them. + +Run from the repository root: python3 -m unittest discover -s tests -v +""" +import re +from pathlib import Path +import unittest + + +ROOT = Path(__file__).resolve().parents[1] +EXAMPLES = ROOT / "examples" +PACKAGE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)+$") + + +def folders(): + return sorted(p for p in EXAMPLES.iterdir() if p.is_dir()) + + +def table_rows(path, header_prefix): + """Rows of the first markdown table whose header starts with header_prefix.""" + lines = path.read_text(encoding="utf-8").split("\n") + start = next(i for i, l in enumerate(lines) if l.startswith(header_prefix)) + rows = [] + for line in lines[start + 2:]: + if not line.startswith("|"): + break + rows.append(line) + return rows + + +class ExampleListsTest(unittest.TestCase): + def test_every_folder_has_a_list(self): + for folder in folders(): + with self.subTest(folder=folder.name): + self.assertTrue((folder / "debloat.txt").is_file(), "no debloat.txt") + + def test_list_format(self): + for folder in folders(): + path = folder / "debloat.txt" + if not path.is_file(): + continue + with self.subTest(folder=folder.name): + lines = path.read_text(encoding="utf-8").split("\n") + self.assertTrue(lines and lines[0].startswith("#"), + "first line should be a comment naming the TV, Android version and build") + packages = [] + for n, line in enumerate(lines, 1): + body = line.split("#", 1)[0].strip() + if not body: + continue + self.assertRegex(body, PACKAGE, f"line {n}: not a package name: {line!r}") + packages.append(body) + self.assertTrue(packages, "no packages: notes without a list belong in docs/devices/") + dupes = sorted({p for p in packages if packages.count(p) > 1}) + self.assertFalse(dupes, f"listed twice: {dupes}") + text = path.read_text(encoding="utf-8") + self.assertNotIn("pm uninstall", text, "lists disable, they never uninstall") + + def test_examples_table_matches_folders(self): + rows = table_rows(EXAMPLES / "README.md", "| Folder |") + listed = [] + for row in rows: + m = re.match(r"\|\s*`([^`/]+)/`\s*\|", row) + self.assertIsNotNone(m, f"row does not start with a `folder/` cell: {row[:60]}") + listed.append(m.group(1)) + names = [f.name for f in folders()] + self.assertEqual(sorted(listed), sorted(names), + "examples/README.md rows and examples/ folders differ") + self.assertEqual(len(listed), len(set(listed)), "a folder is listed twice") + + def test_readme_table_has_one_row_per_list(self): + readme_rows = table_rows(ROOT / "README.md", "| TV | OS |") + example_rows = table_rows(EXAMPLES / "README.md", "| Folder |") + self.assertEqual(len(readme_rows), len(example_rows), + "README.md TV table and examples/README.md have a different number of rows") + readme_tvs = sorted(r.split("|")[1].strip() for r in readme_rows) + example_tvs = sorted(r.split("|")[2].strip() for r in example_rows) + self.assertEqual(readme_tvs, example_tvs, "the TV column differs between the two tables") + + def test_readme_counts(self): + text = (ROOT / "README.md").read_text(encoding="utf-8") + n = len(folders()) + self.assertIn(f"{n} TV lists", text, "nav link count is stale") + self.assertIn(f"{n} TVs with tested lists", text, "numbers line is stale") + + +if __name__ == "__main__": + unittest.main()