Created
February 24, 2026 05:53
-
-
Save anonymousik/9246ecfbc8ec61a673ef249fa631c275 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| ╔══════════════════════════════════════════════════════════════════════════════╗ | |
| ║ PLAYBOX TITANIUM v11.0 ULTRA — SmartTube + Chromecast EDITION ║ | |
| ║ Target : Sagemcom DCTIW362P | Android TV 9 (API 28) | Broadcom m362 ║ | |
| ║ Focus : Max SmartTube/Video Performance + Chromecast Service Protection ║ | |
| ║ Author : Anonymousik / SecFerro Division ║ | |
| ║ Date : 2026-02 ║ | |
| ╠══════════════════════════════════════════════════════════════════════════════╣ | |
| ║ HARDWARE PROFILE (real ADB session, 192.168.1.3:5555): ║ | |
| ║ RAM : 1459 MB total | ~534 MB available (MemAvailable) ║ | |
| ║ FLASH : 3.9 GB | 53% used (/data) ║ | |
| ║ SoC : Broadcom m362 (BCM7362) — Dual-core Cortex-A15 @ 1.0 GHz ║ | |
| ║ VPU : BCM7362 HW decoder: H.264 ✓ | H.265 ✓ | VP9 ✓ | AV1 ✗ ║ | |
| ║ GPU : Broadcom VideoCore (OpenGL ES 3.1, NO Vulkan) ║ | |
| ║ Display : HDMI 2.0a, CEC v1.4, 4K@60fps, HDR10 ║ | |
| ╠══════════════════════════════════════════════════════════════════════════════╣ | |
| ║ CHROMECAST PROTECTION ARCHITECTURE: ║ | |
| ║ apps.mediashell → Cast Built-in DAEMON [NEVER DISABLE] ║ | |
| ║ gms → Cast SDK / Auth [NEVER DISABLE] ║ | |
| ║ gsf → Google Services FW [NEVER DISABLE] ║ | |
| ║ nearby → mDNS/Cast Discovery [NEVER DISABLE] ║ | |
| ║ tv.remote.service → Cast UI Input [PROTECTED] ║ | |
| ║ tvlauncher → Cast Session UI [PROTECTED] ║ | |
| ║ networkstack → Multicast Routing [PROTECTED] ║ | |
| ╚══════════════════════════════════════════════════════════════════════════════╝ | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import sys | |
| import subprocess | |
| import time | |
| import argparse | |
| from pathlib import Path | |
| from typing import Optional, List, Dict, Tuple, Callable | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # GLOBAL CONSTANTS | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| VERSION = "11.0-ULTRA-SmartTube-ChromecastSafe" | |
| DEFAULT_ADB_HOST = "192.168.1.3:5555" | |
| CACHE_DIR = Path.home() / ".playbox_cache" | |
| BACKUP_DIR = CACHE_DIR / "backups_v11" | |
| LOG_FILE = CACHE_DIR / "autopilot_v11.log" | |
| CACHE_DIR.mkdir(parents=True, exist_ok=True) | |
| BACKUP_DIR.mkdir(parents=True, exist_ok=True) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # DEVICE SPECIFICATION — Broadcom BCM7362 | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class DeviceSpec: | |
| """ | |
| Hardware constants for Sagemcom DCTIW362P. | |
| Sources: real ADB capture + BCM7362 datasheet + Android TV 9 framework docs. | |
| """ | |
| MODEL = "dctiw362p" | |
| HARDWARE = "m362" | |
| ANDROID_VERSION = "9" | |
| API_LEVEL = "28" | |
| BUILD_FINGERPRINT = ( | |
| "PLAY/play_dctiw362/dctiw362p:9/PTT1.190826.001/" | |
| "1.0.36-194202:user/release-keys" | |
| ) | |
| # From /proc/meminfo snapshot | |
| RAM_TOTAL_KB = 1_459_656 | |
| RAM_AVAILABLE_KB = 534_720 | |
| # BCM7362 VPU codec identifiers (OMX layer) | |
| # Confirmed by OMX component enumeration on this chipset family | |
| CODEC_HW_H264 = "OMX.brcm.video.h264.decoder" | |
| CODEC_HW_HEVC = "OMX.brcm.video.h265.decoder" | |
| CODEC_HW_VP9 = "OMX.brcm.video.vp9.decoder" | |
| # AV1: NO hardware decoder on BCM7362 — software fallback only | |
| CODEC_SW_AV1 = "c2.android.av1.decoder" # DO NOT USE — CPU killer | |
| # Dalvik heap profile for ~534MB available RAM | |
| # SmartTube typical heap usage: 150–200MB during 4K playback | |
| DALVIK_HEAP_SIZE = "256m" | |
| DALVIK_HEAP_GROWTH = "128m" | |
| DALVIK_HEAP_MIN_FREE = "16m" | |
| DALVIK_HEAP_MAX_FREE = "32m" | |
| DALVIK_HEAP_TARGET = "0.75" | |
| SMARTTUBE_PKG = "com.teamsmart.videomanager.tv" | |
| SMARTTUBE_STABLE_URL = ( | |
| "https://github.com/yuliskov/SmartTube/releases/download/latest/smarttube_stable.apk" | |
| ) | |
| SMARTTUBE_BETA_URL = ( | |
| "https://github.com/yuliskov/SmartTube/releases/download/latest/smarttube_beta.apk" | |
| ) | |
| SHIZUKU_PKG = "moe.shizuku.privileged.api" | |
| SHIZUKU_APK_URL = ( | |
| "https://github.com/RikkaApps/Shizuku/releases/download/" | |
| "v13.5.4/shizuku-v13.5.4-release.apk" | |
| ) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # CHROMECAST PROTECTION REGISTRY | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ChromecastProtection: | |
| """ | |
| Definitive whitelist of packages that MUST remain enabled for | |
| Chromecast Built-in, Google Cast SDK v3+, and related protocols. | |
| Engineering rationale per entry: | |
| ────────────────────────────────────────────────────────────────────────── | |
| apps.mediashell → THIS IS Chromecast Built-in daemon on Android TV. | |
| Without it, no Cast session can be established. | |
| It runs the Cast receiver application loop and | |
| manages DIAL/mDNS session handshake. | |
| gms → Google Play Services. Hosts: | |
| · com.google.android.gms.cast (Cast SDK) | |
| · SessionManager, CastContext | |
| · Device authentication (OAuth tokens) | |
| Disabling GMS = total Cast failure. | |
| gsf → Google Services Framework. Core dependency of GMS. | |
| Handles Google Account token refresh. Without it, | |
| GMS cannot authenticate → Cast breaks silently. | |
| nearby → Google Nearby Connections. Manages: | |
| · mDNS/DNS-SD multicast (port 5353) | |
| Used for Chromecast device discovery on LAN | |
| · BLE advertising (Guest Mode / Fast Pair) | |
| · SSDP/UPnP fallback discovery path | |
| Disabling = sender device "cannot find Chromecast". | |
| tv.remote.service → Handles Cast session initiation events from remote | |
| controls and the "Cast to TV" system intent. Also | |
| manages virtual remote input during active sessions. | |
| tvlauncher → Android TV Home. Cast sessions surface ambient | |
| mode UI here. Handles android.intent.action.MAIN | |
| with CATEGORY_LEANBACK_LAUNCHER for Cast handoff. | |
| configupdater → Downloads periodic Cast infrastructure config: | |
| TLS certificate pins, endpoint URLs, protocol | |
| version negotiation. Stale config → Cast auth fails. | |
| wifidisplay → Miracast / WiFi Direct. Used as Cast fallback | |
| transport when DIAL-over-mDNS fails (e.g. IGMP | |
| snooping misconfigured on router). Some Chromecast | |
| Audio receivers rely on this path. | |
| networkstack → Android network stack. Handles multicast group | |
| join (IGMP) for mDNS. Without it, mDNS packets | |
| are never delivered to the mDNS responder daemon. | |
| networkstack.tether → Tethering companion — shares multicast routing | |
| rules with networkstack. Required on some builds | |
| for proper multicast socket binding. | |
| """ | |
| PROTECTED_PACKAGES: Dict[str, str] = { | |
| # Cast Core — NEVER touch these | |
| "com.google.android.apps.mediashell": | |
| "Cast Built-in DAEMON — the Chromecast receiver process [CRITICAL]", | |
| "com.google.android.gms": | |
| "Google Play Services — Cast SDK, SessionManager, Auth [CRITICAL]", | |
| "com.google.android.gsf": | |
| "Google Services Framework — GMS dependency chain [CRITICAL]", | |
| "com.google.android.nearby": | |
| "Nearby Connections — mDNS/BLE Chromecast discovery [CRITICAL]", | |
| # Cast Session Management | |
| "com.google.android.tv.remote.service": | |
| "TV Remote Service — Cast session UI & input routing [IMPORTANT]", | |
| "com.google.android.tvlauncher": | |
| "TV Launcher — Cast ambient mode UI & intents [IMPORTANT]", | |
| # Cast Infrastructure | |
| "com.google.android.configupdater": | |
| "Config Updater — Cast TLS certs & endpoint config [RECOMMENDED]", | |
| "com.google.android.wifidisplay": | |
| "WiFi Display — Miracast/Cast fallback transport [RECOMMENDED]", | |
| # Network Foundation (multicast for mDNS) | |
| "com.android.networkstack": | |
| "Network Stack — IGMP multicast for mDNS (Cast discovery) [CRITICAL]", | |
| "com.android.networkstack.tethering": | |
| "Network Tethering Stack — multicast socket routing [IMPORTANT]", | |
| } | |
| @classmethod | |
| def is_protected(cls, package: str) -> bool: | |
| return package in cls.PROTECTED_PACKAGES | |
| @classmethod | |
| def get_reason(cls, package: str) -> str: | |
| return cls.PROTECTED_PACKAGES.get(package, "") | |
| @classmethod | |
| def print_registry(cls) -> None: | |
| Logger.header("🛡 CHROMECAST PROTECTION REGISTRY") | |
| for pkg, reason in cls.PROTECTED_PACKAGES.items(): | |
| Logger.log(f" 🔒 {pkg}", "success") | |
| Logger.log(f" └─ {reason}", "cyan") | |
| Logger.log( | |
| f"\n Total protected: {len(cls.PROTECTED_PACKAGES)} packages", | |
| "bold" | |
| ) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SAFE DEBLOAT DATABASE | |
| # Each entry: (package, reason_to_disable, chromecast_impact) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| SAFE_DEBLOAT_DB: List[Tuple[str, str, str]] = [ | |
| # Android TV system bloat | |
| ("com.google.android.backdrop", | |
| "Ambient Mode screensaver — idles GPU, wastes 30–60MB RAM", | |
| "NONE — Cast unaffected"), | |
| ("com.google.android.tvrecommendations", | |
| "Content recommendations — constant background HTTP polling", | |
| "NONE"), | |
| ("com.google.android.katniss", | |
| "Google TV voice search overlay — high idle CPU on A15", | |
| "NONE — Cast uses own discovery path"), | |
| ("com.google.android.tungsten.setupwraith", | |
| "Setup wizard — not needed post-initial-setup", | |
| "NONE"), | |
| ("com.google.android.apps.tv.launcherx", | |
| "New Google TV launcher (not used on API 28 / Android 9)", | |
| "NONE — tvlauncher separately protected"), | |
| ("com.google.android.leanbacklauncher", | |
| "Legacy Leanback launcher duplicate (tvlauncher supersedes it)", | |
| "NONE"), | |
| # Sagemcom / Orange Poland bloat | |
| ("com.sagemcom.stb.setupwizard", | |
| "Sagemcom factory setup wizard", | |
| "NONE"), | |
| ("com.orange.fr.tv.assistance", | |
| "Orange TV assistance app — polls Orange servers constantly", | |
| "NONE"), | |
| ("com.orange.fr.tv.guide", | |
| "Orange TV guide — heavy background sync, rarely used", | |
| "NONE"), | |
| # Google services safe to disable (non-Cast) | |
| ("com.google.android.marvin.talkback", | |
| "Accessibility TTS — not needed on TV, allocates ~40MB", | |
| "NONE"), | |
| ("com.google.android.onetimeinitializer", | |
| "One-time initializer — already completed on first boot", | |
| "NONE"), | |
| # Third-party pre-installed | |
| ("com.amazon.amazonvideo.livingroom", | |
| "Amazon Prime TV — pre-bundled, standalone install preferable", | |
| "NONE"), | |
| ] | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # LOGGER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class Logger: | |
| COLORS: Dict[str, str] = { | |
| "info": "\033[94m", | |
| "success": "\033[92m", | |
| "warning": "\033[93m", | |
| "error": "\033[91m", | |
| "header": "\033[95m", | |
| "cyan": "\033[96m", | |
| "bold": "\033[1m", | |
| "reset": "\033[0m", | |
| } | |
| _log_lines: List[str] = [] | |
| @classmethod | |
| def _emit(cls, msg: str, level: str) -> None: | |
| ts = time.strftime("%H:%M:%S") | |
| color = cls.COLORS.get(level, cls.COLORS["info"]) | |
| print(f"{color}[{ts}] {msg}{cls.COLORS['reset']}") | |
| cls._log_lines.append(f"[{ts}][{level.upper()}] {msg}") | |
| @classmethod | |
| def log(cls, msg: str, level: str = "info") -> None: | |
| cls._emit(msg, level) | |
| @classmethod | |
| def success(cls, msg: str) -> None: | |
| cls._emit(f"✓ {msg}", "success") | |
| @classmethod | |
| def warning(cls, msg: str) -> None: | |
| cls._emit(f"⚠ {msg}", "warning") | |
| @classmethod | |
| def error(cls, msg: str) -> None: | |
| cls._emit(f"✗ {msg}", "error") | |
| @classmethod | |
| def shield(cls, msg: str) -> None: | |
| """Protected resource notification.""" | |
| cls._emit(f"🛡 {msg}", "success") | |
| @classmethod | |
| def header(cls, msg: str) -> None: | |
| sep = "═" * 74 | |
| print( | |
| f"\n{cls.COLORS['header']}{cls.COLORS['bold']}{sep}\n" | |
| f" {msg}\n{sep}{cls.COLORS['reset']}\n" | |
| ) | |
| @classmethod | |
| def save_log(cls) -> None: | |
| try: | |
| with open(LOG_FILE, "a", encoding="utf-8") as fh: | |
| fh.write(f"\n{'─'*60}\n") | |
| fh.write(f"SESSION {time.strftime('%Y-%m-%d %H:%M:%S')} | v{VERSION}\n") | |
| fh.write("\n".join(cls._log_lines)) | |
| fh.write("\n") | |
| except OSError: | |
| pass | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # ADB SHELL INTERFACE | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ADBShell: | |
| """ | |
| Secure, idempotent ADB command executor. | |
| Security notes: | |
| - No shell=True — commands passed as list to prevent injection | |
| - Retry only on TimeoutExpired, not CalledProcessError (errors are final) | |
| - run_root() escalation: su → rish (Shizuku) → plain ADB shell | |
| """ | |
| device: Optional[str] = None | |
| TIMEOUT: int = 30 | |
| RETRIES: int = 3 | |
| @classmethod | |
| def connect(cls, target: str) -> bool: | |
| """Establish TCP ADB connection. Returns True on success.""" | |
| try: | |
| result = subprocess.run( | |
| ["adb", "connect", target], | |
| capture_output=True, text=True, timeout=10 | |
| ) | |
| if "connected" in result.stdout.lower(): | |
| cls.device = target | |
| Logger.success(f"ADB connected: {target}") | |
| return True | |
| Logger.error(f"ADB connect failed: {result.stdout.strip()}") | |
| return False | |
| except FileNotFoundError: | |
| Logger.error("'adb' not found in PATH. Install Android Platform Tools.") | |
| sys.exit(1) | |
| except subprocess.TimeoutExpired: | |
| Logger.error(f"ADB connect timeout: {target}") | |
| return False | |
| @classmethod | |
| def detect_device(cls) -> Optional[str]: | |
| """Auto-detect first available adb device.""" | |
| try: | |
| out = subprocess.check_output(["adb", "devices"], text=True, timeout=5) | |
| for line in out.splitlines(): | |
| if "\tdevice" in line: | |
| return line.split("\t")[0].strip() | |
| except Exception: | |
| pass | |
| return None | |
| @classmethod | |
| def run( | |
| cls, | |
| cmd: str, | |
| ignore_errors: bool = True, | |
| silent: bool = False, | |
| ) -> str: | |
| """Execute shell command via ADB. Returns stdout or '' on failure.""" | |
| if not cls.device: | |
| return "" | |
| for attempt in range(cls.RETRIES): | |
| try: | |
| out = subprocess.check_output( | |
| ["adb", "-s", cls.device, "shell", cmd], | |
| stderr=subprocess.STDOUT, | |
| text=True, | |
| timeout=cls.TIMEOUT, | |
| ) | |
| return out.strip() | |
| except subprocess.TimeoutExpired: | |
| if attempt < cls.RETRIES - 1: | |
| time.sleep(1.5) | |
| elif not silent: | |
| Logger.warning(f"Timeout [{attempt+1}/{cls.RETRIES}]: {cmd[:60]}") | |
| except subprocess.CalledProcessError as exc: | |
| return exc.output.strip() if exc.output else "" | |
| except Exception as exc: | |
| if not silent: | |
| Logger.error(f"ADB exception: {exc}") | |
| return "" | |
| return "" | |
| @classmethod | |
| def run_root(cls, cmd: str) -> str: | |
| """Privileged execution: su → rish → plain ADB shell (graceful fallback).""" | |
| for prefix in (f"su -c \"{cmd}\"", f"rish -c \"{cmd}\""): | |
| result = cls.run(prefix, ignore_errors=True, silent=True) | |
| if result and "not found" not in result and "permission denied" not in result.lower(): | |
| return result | |
| Logger.warning(f"Root unavailable, trying plain ADB: {cmd[:50]}") | |
| return cls.run(cmd, ignore_errors=True) | |
| @classmethod | |
| def push(cls, local: str, remote: str) -> bool: | |
| """Push file to device. Returns True on success.""" | |
| try: | |
| subprocess.check_call( | |
| ["adb", "-s", cls.device, "push", local, remote], | |
| stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, | |
| timeout=120, | |
| ) | |
| return True | |
| except Exception: | |
| return False | |
| # ── Convenience wrappers ────────────────────────────────────────────── | |
| @classmethod | |
| def prop_get(cls, prop: str) -> str: | |
| return cls.run(f"getprop {prop}", silent=True) | |
| @classmethod | |
| def prop_set(cls, prop: str, value: str) -> None: | |
| cls.run(f"setprop {prop} {value}", ignore_errors=True, silent=True) | |
| @classmethod | |
| def setting_put(cls, ns: str, key: str, value: str) -> None: | |
| cls.run(f"settings put {ns} {key} {value}", ignore_errors=True, silent=True) | |
| @classmethod | |
| def setting_get(cls, ns: str, key: str) -> str: | |
| return cls.run(f"settings get {ns} {key}", silent=True) | |
| @classmethod | |
| def pkg_enabled(cls, pkg: str) -> bool: | |
| return pkg in cls.run(f"pm list packages -e {pkg}", silent=True) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SMARTTUBE VIDEO ENGINE OPTIMIZER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class SmartTubeOptimizer: | |
| """ | |
| SmartTube + ExoPlayer + Broadcom VPU pipeline tuning. | |
| SmartTube uses ExoPlayer which interfaces with Android MediaCodec API: | |
| MediaCodec → OMX layer → BCM7362 VPU kernel driver (bcmvideo.ko) | |
| YouTube codec priority for BCM7362 (HW → SW, best → worst): | |
| 1. VP9 profile 2 — HW decode, 4K@60fps, best quality/perf ratio | |
| 2. H.264 AVC — HW decode, universal compatibility | |
| 3. H.265 HEVC — HW decode, good for 4K, less stable on some sources | |
| 4. AV1 — SOFTWARE ONLY → CPU at 100%, dropped frames, throttle | |
| Tunnel Mode (Android 5+, BCM7362 supported): | |
| Decoded VPU frames → display controller, bypassing SurfaceFlinger. | |
| Eliminates 1–2 frame compositing latency, saves ~15% CPU at 4K. | |
| """ | |
| def optimize_exoplayer_pipeline(self) -> None: | |
| Logger.header("🎬 EXOPLAYER PIPELINE — Broadcom VPU Hardware Path") | |
| # Stagefright / MediaCodec stack flags | |
| codec_props = [ | |
| # Enable HW-accelerated player pipeline | |
| ("media.stagefright.enable-player", "true"), | |
| ("media.stagefright.enable-meta", "true"), | |
| ("media.stagefright.enable-http", "true"), | |
| ("media.stagefright.enable-scan", "true"), | |
| # OMX: always pick HW codec over SW fallback during negotiation | |
| ("media.acodec.preferhw", "true"), | |
| ("media.vcodec.preferhw", "true"), | |
| # Suppress automatic SW fallback for HW-capable codecs | |
| ("media.codec.sw.fallback", "false"), | |
| # OMX thread priority: 1 = RT class (prevents UI thread preemption) | |
| ("media.codec.priority", "1"), | |
| ] | |
| for key, value in codec_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| # HLS / DASH adaptive bitrate buffer tuning | |
| abr_props = [ | |
| # 25 Mbps ceiling covers YouTube 4K VP9 (typical: 10–20 Mbps) | |
| ("media.httplive.max-bitrate", "25000000"), | |
| # Start at 8 Mbps — avoids quality dip on stream open | |
| ("media.httplive.initial-bitrate", "8000000"), | |
| # 60s lookahead buffer — reduces rebuffering events on variable WiFi | |
| ("media.httplive.max-live-offset", "60"), | |
| # Bandwidth estimator update interval: 1000ms (responsive ABR) | |
| ("media.httplive.bw-update-interval", "1000"), | |
| ] | |
| for key, value in abr_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| # MediaExtractor cache: 20MB min / 60MB max / 256MB total cap | |
| # Calculated for 4K streaming on 1.4GB RAM device (holds ~3s of 4K) | |
| ADBShell.run( | |
| "settings put global media.stagefright.cache-params " | |
| "20971520/62914560/268435456", | |
| ignore_errors=True, | |
| ) | |
| Logger.log(" ✓ MediaExtractor cache: 20MB–60MB / 256MB cap") | |
| Logger.success("ExoPlayer pipeline configured for Broadcom VPU HW path") | |
| def force_tunnel_mode(self) -> None: | |
| """ | |
| Enable MediaCodec Tunnel Mode. | |
| VPU decoded frames → BCM7362 display controller directly, | |
| bypassing SurfaceFlinger buffer queue. Effect: | |
| - ~1–2 frame latency reduction | |
| - ~15% CPU reduction during 4K playback | |
| - Eliminates compositor-induced frame pacing jitter | |
| """ | |
| Logger.header("🚇 TUNNEL MODE — Direct VPU→Display Path") | |
| tunnel_props = [ | |
| ("media.tunneled-playback.enable", "true"), | |
| ("media.brcm.tunnel.sessions", "1"), # One tunnel = one video | |
| ("media.brcm.hdmi.tunnel", "true"), | |
| ("media.brcm.tunnel.clock", "hdmi"), # Lock A/V to HDMI sink clock | |
| ("debug.sf.disable_backpressure", "1"), # No SF back-pressure on VPU | |
| ] | |
| for key, value in tunnel_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| Logger.success("Tunnel Mode active — VPU→Display direct path enabled") | |
| def suppress_av1_software_decode(self) -> None: | |
| """ | |
| Suppress AV1 software decoder to prevent CPU overload. | |
| BCM7362 has NO AV1 hardware decoder. YouTube increasingly serves | |
| AV1 to devices that advertise VP9 + AV1 support. ExoPlayer then | |
| selects c2.android.av1.decoder — a pure software decoder that | |
| saturates both A15 cores at 720p, causing frame drops at 1080p+. | |
| We suppress the component so MediaCodec negotiation falls back to | |
| VP9 (hardware), which BCM7362 handles at 4K@60fps without CPU load. | |
| """ | |
| Logger.header("🚫 AV1 SUPPRESSION — Force VP9/H.264 HW Negotiation") | |
| Logger.warning("BCM7362: NO AV1 HW decoder — SW decode = CPU 100% + stutter") | |
| av1_props = [ | |
| ("debug.stagefright.c2.av1", "0"), | |
| ("media.av1.sw.decode.disable", "true"), | |
| ("media.codec.av1.disable", "true"), | |
| ] | |
| for key, value in av1_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| Logger.success("AV1 SW decoder suppressed — VP9/H.264 HW path will be used") | |
| colors = Logger.COLORS | |
| print(f""" | |
| {colors['cyan']}┌──────────────────────────────────────────────────────────┐ | |
| │ ⚙ SMARTTUBE MANUAL SETTINGS (after next launch) │ | |
| │ │ | |
| │ Settings → Player → Video codec → VP9 ← set this │ | |
| │ Settings → Player → Resolution → 2160p (4K) │ | |
| │ Settings → Player → Use tunnel mode → ON (if visible) │ | |
| │ │ | |
| │ VP9 on BCM7362 = hardware → smooth 4K, low CPU │ | |
| │ AV1 on BCM7362 = software → stutter, thermal throttle │ | |
| └──────────────────────────────────────────────────────────┘{colors['reset']} | |
| """) | |
| def tune_dalvik_heap(self) -> None: | |
| """ | |
| Tune Dalvik/ART heap for streaming workload on 1.4GB RAM device. | |
| Memory budget (MemAvailable ~534MB): | |
| SmartTube (playing 4K) : ~200MB heap + ~50MB native | |
| Chromecast GMS/mediashell: ~80MB | |
| TV Launcher : ~40MB | |
| OS + kernel : ~150MB | |
| Headroom : ~14MB free | |
| → heap_size=256m keeps SmartTube happy without GC storms | |
| """ | |
| Logger.header("🧠 DALVIK/ART HEAP — SmartTube 4K Streaming Profile") | |
| heap_props = [ | |
| ("dalvik.vm.heapsize", DeviceSpec.DALVIK_HEAP_SIZE), | |
| ("dalvik.vm.heapgrowthlimit", DeviceSpec.DALVIK_HEAP_GROWTH), | |
| ("dalvik.vm.heapminfree", DeviceSpec.DALVIK_HEAP_MIN_FREE), | |
| ("dalvik.vm.heapmaxfree", DeviceSpec.DALVIK_HEAP_MAX_FREE), | |
| ("dalvik.vm.heaptargetutilization", DeviceSpec.DALVIK_HEAP_TARGET), | |
| ("dalvik.vm.usejit", "true"), # JIT: reduce first-run latency | |
| ("dalvik.vm.dex2oat-filter", "speed-profile"), | |
| ("dalvik.vm.gctype", "CMS"), # Concurrent GC → no pause | |
| ] | |
| for key, value in heap_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| Logger.success("Dalvik heap configured for SmartTube 4K workload") | |
| def compile_aot(self) -> bool: | |
| """ | |
| AOT-compile SmartTube to eliminate JIT compilation bursts during playback. | |
| JIT compilation on A15 cores causes 50–200ms spikes that manifest as | |
| dropped frames during seeks or first few seconds of playback. | |
| AOT pre-compiles hot paths (player, network, UI) → smooth from first frame. | |
| Duration: ~60–90s on BCM7362 (dual-core 1.0 GHz A15). Persistent until APK update. | |
| """ | |
| Logger.header("⚡ AOT COMPILATION — Eliminate JIT Bursts in SmartTube") | |
| pkg = DeviceSpec.SMARTTUBE_PKG | |
| if not ADBShell.pkg_enabled(pkg): | |
| Logger.warning(f"SmartTube not installed — skipping AOT") | |
| return False | |
| Logger.log("Compiling SmartTube [speed-profile mode] ... ~60–90s") | |
| result = ADBShell.run( | |
| f"cmd package compile -m speed-profile -f {pkg}", | |
| ignore_errors=True, | |
| ) | |
| if "success" in result.lower(): | |
| Logger.success("SmartTube AOT complete (speed-profile)") | |
| return True | |
| Logger.warning("speed-profile failed — retrying with speed mode") | |
| result = ADBShell.run(f"cmd package compile -m speed -f {pkg}", ignore_errors=True) | |
| Logger.success("SmartTube AOT complete (speed mode)") | |
| return True | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # CHROMECAST SERVICE MANAGER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ChromecastServiceManager: | |
| """ | |
| Audit, restore, and harden all Chromecast Built-in services. | |
| Cast discovery call stack (simplified): | |
| Sender app → mDNS query (UDP 5353 multicast 224.0.0.251) | |
| → com.android.networkstack (multicast join / IGMP) | |
| → com.google.android.nearby (mDNS responder) | |
| → com.google.android.apps.mediashell (Cast session accept) | |
| → com.google.android.gms (Cast SDK lifecycle) | |
| → com.google.android.tvlauncher (Cast UI) | |
| """ | |
| @staticmethod | |
| def audit() -> Dict[str, bool]: | |
| """Check enabled state of all protected Cast packages.""" | |
| Logger.header("🔍 CHROMECAST SERVICE AUDIT") | |
| results: Dict[str, bool] = {} | |
| for pkg, reason in ChromecastProtection.PROTECTED_PACKAGES.items(): | |
| enabled = ADBShell.pkg_enabled(pkg) | |
| results[pkg] = enabled | |
| icon = "✓" if enabled else "✗" | |
| level = "success" if enabled else "error" | |
| Logger.log(f" {icon} {pkg}", level) | |
| Logger.log(f" └─ {reason}", "cyan") | |
| broken = [p for p, e in results.items() if not e] | |
| if broken: | |
| Logger.warning( | |
| f"{len(broken)} Cast service(s) DISABLED — Chromecast may be broken!" | |
| ) | |
| else: | |
| Logger.success("All Chromecast services active and healthy ✓") | |
| return results | |
| @staticmethod | |
| def restore() -> None: | |
| """Re-enable all Cast services that are currently disabled. Idempotent.""" | |
| Logger.header("🛡 CHROMECAST SERVICE RESTORATION") | |
| for pkg, reason in ChromecastProtection.PROTECTED_PACKAGES.items(): | |
| ADBShell.run(f"pm enable {pkg}", ignore_errors=True, silent=True) | |
| ADBShell.run(f"pm enable --user 0 {pkg}", ignore_errors=True, silent=True) | |
| Logger.shield(f"Ensured enabled: {pkg}") | |
| Logger.success( | |
| f"Restoration complete — {len(ChromecastProtection.PROTECTED_PACKAGES)} " | |
| "Cast services verified" | |
| ) | |
| @staticmethod | |
| def optimize_cast_network() -> None: | |
| """ | |
| Optimize WiFi/multicast for reliable Cast discovery. | |
| Common issue: WiFi driver power-save mode suppresses multicast packets, | |
| causing intermittent 'Chromecast not found' on sender devices even when | |
| device is online (unicast works, multicast 224.0.0.251 dropped). | |
| """ | |
| Logger.header("📡 CAST NETWORK — mDNS/Multicast Reliability") | |
| # Keep WiFi alive to prevent multicast suppression | |
| ADBShell.setting_put("global", "wifi_sleep_policy", "2") | |
| Logger.log(" ✓ wifi_sleep_policy = 2 (always connected)") | |
| ADBShell.setting_put("global", "wifi_power_save", "0") | |
| Logger.log(" ✓ wifi_power_save disabled (prevents mDNS packet drop)") | |
| # mDNS passive mode off → device actively responds to discovery | |
| ADBShell.prop_set("ro.mdns.enable_passive_mode", "false") | |
| Logger.log(" ✓ mDNS: active response mode") | |
| # SSDP multicast TTL ≥ 1 required for LAN multicast delivery | |
| ADBShell.prop_set("net.ssdp.ttl", "4") | |
| Logger.log(" ✓ SSDP TTL = 4 (sufficient for home LAN)") | |
| Logger.success("Cast mDNS/multicast network profile active") | |
| @staticmethod | |
| def harden_cast_processes() -> None: | |
| """ | |
| Set OOM score for Cast processes to prevent LMK from killing them. | |
| oom_score_adj scale: | |
| -1000 = never kill (system) | |
| 0 = foreground app | |
| 200 = empty background (kill first) | |
| We set Cast daemon to ~100 → "important background service" tier | |
| """ | |
| Logger.header("🎯 CAST PROCESS HARDENING — OOM Protection") | |
| cast_pkgs = [ | |
| "com.google.android.apps.mediashell", | |
| "com.google.android.gms", | |
| "com.google.android.nearby", | |
| ] | |
| for pkg in cast_pkgs: | |
| pid = ADBShell.run(f"pidof {pkg}", silent=True).strip() | |
| if pid and pid.isdigit(): | |
| ADBShell.run_root(f"echo 100 > /proc/{pid}/oom_score_adj") | |
| Logger.shield(f"OOM adj=100 set: {pkg} (PID {pid})") | |
| else: | |
| Logger.log(f" {pkg} not running — will be protected at runtime", "warning") | |
| Logger.success("Cast processes protected against OOM killer") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # BROADCOM M362 GPU + VPU TUNER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class BroadcomTuner: | |
| """ | |
| Hardware-specific tuning for Broadcom BCM7362 (m362). | |
| Architecture: | |
| - Dual-core Cortex-A15 @ 1.0 GHz (NOT A53 — this is an STB SoC) | |
| - VideoCore VI GPU (GLES 3.1 max, NO Vulkan, NO Swiftshader) | |
| - Dedicated VPU silicon — H.264/H.265/VP9 decode completely offloaded | |
| - Triple-buffer framebuffer in hardware (prevents screen tearing) | |
| - HDMI 2.0a output with native HDR10 signaling | |
| """ | |
| def apply_gpu_tweaks(self) -> None: | |
| Logger.header("🎮 BROADCOM VIDEOCORE — GPU Rendering Optimization") | |
| gpu_props = [ | |
| ("debug.hwui.renderer", "opengl"), # Must be opengl — no Vulkan | |
| ("debug.egl.hw", "1"), # HW EGL acceleration | |
| ("debug.sf.hw", "1"), # SurfaceFlinger HW composer | |
| ("debug.gr.numframebuffers", "3"), # Triple buffer (tearing fix) | |
| ("debug.hwui.use_buffer_age", "false"), # Unnecessary overhead on VideoCore | |
| ("debug.hwui.layer_cache_size", "16384"), # 16MB layer cache | |
| ("debug.hwui.profile", "false"), # No profiler overhead | |
| ] | |
| for key, value in gpu_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| ADBShell.setting_put("global", "force_gpu_rendering", "true") | |
| Logger.log(" ✓ force_gpu_rendering = true") | |
| Logger.success("VideoCore GPU tweaks applied") | |
| def apply_vpu_tweaks(self) -> None: | |
| Logger.header("📹 BROADCOM VPU — Video Decode Pipeline") | |
| vpu_props = [ | |
| ("ro.hdmi.device_type", "4"), # Playback device | |
| ("persist.sys.hdmi.addr.playback", "0"), | |
| ("ro.hdmi.wake_on_hotplug", "false"), # Prevent HDMI reset glitches | |
| ("persist.sys.hdr.enable", "1"), # HDR10 output | |
| ("media.brcm.vpu.prealloc", "true"), # Pre-alloc decode buffers at boot | |
| ("media.brcm.vpu.buffers", "6"), # 6 frame buffer pool (4K@60) | |
| ("media.brcm.secure.decode", "true"), # Widevine L3 / PlayReady support | |
| ] | |
| for key, value in vpu_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| Logger.success("BCM7362 VPU pipeline configured") | |
| def set_performance_governor(self) -> None: | |
| """ | |
| Force 'performance' CPU governor on both A15 cores. | |
| Under 'interactive' governor, cores can drop to 250MHz, causing | |
| decode queue starvation and visible frame pacing issues. | |
| """ | |
| Logger.header("⚙ CPU GOVERNOR — Force Performance Mode") | |
| for core in range(2): # BCM7362: 2x Cortex-A15 | |
| path = f"/sys/devices/system/cpu/cpu{core}/cpufreq/scaling_governor" | |
| result = ADBShell.run_root(f"echo performance > {path}") | |
| Logger.log(f" ✓ cpu{core}: performance governor") | |
| Logger.success("Both A15 cores locked to maximum frequency") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # AUDIO PIPELINE OPTIMIZER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class AudioOptimizer: | |
| """ | |
| Fix A/V desync and optimize audio output for Orange PLAYBox. | |
| Root cause of desync on BCM7362 HDMI: | |
| Audio offload path uses Broadcom's proprietary timing that disagrees | |
| with HDMI ARC handshake timing. Result: audio runs 50–200ms off from video. | |
| Fix: disable offload → passthrough mode → consistent latency. | |
| Lock audio clock to HDMI sink → eliminates slow drift over hours. | |
| """ | |
| def fix_av_sync_and_pipeline(self) -> None: | |
| Logger.header("🔊 AUDIO PIPELINE — A/V Sync + HDMI Fix") | |
| audio_props = [ | |
| # Disable audio offload — it causes desync on BCM7362 HDMI output | |
| ("audio.offload.disable", "1"), | |
| ("audio.offload.video", "0"), | |
| # Disable tunnel audio encode (conflicts with HDMI ARC on this chipset) | |
| ("tunnel.audio.encode", "false"), | |
| # Deep buffer: stable ~20ms latency, prevents buffer under-runs | |
| ("audio.deep_buffer.media", "true"), | |
| # One fast track sufficient for SmartTube (mono audio thread) | |
| ("af.fast_track_multiplier", "1"), | |
| # Lock audio clock to HDMI sink → eliminates slow A/V drift | |
| ("audio.brcm.hdmi.clock_lock", "true"), | |
| # HAL latency: 20ms (optimal for BCM7362) | |
| ("audio.brcm.hal.latency", "20"), | |
| ] | |
| for key, value in audio_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| Logger.success("Audio pipeline fixed — A/V sync stabilized, HDMI clock locked") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # NETWORK OPTIMIZER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class NetworkOptimizer: | |
| """ | |
| TCP/IP stack + DNS tuning for 4K streaming. | |
| Buffer sizing rationale (RTT ~5ms LAN, 100Mbps link): | |
| BDP = 100Mbps × 5ms = 62.5 KB | |
| We provision 4× BDP for burst absorption: ~250KB receive, ~512KB send | |
| (4K VP9 YouTube stream: 10–20Mbps → burst to 25Mbps for ~100ms) | |
| Cast-safe: multicast settings untouched. Private DNS (DoT) only | |
| affects unicast DNS — mDNS uses multicast UDP, completely separate path. | |
| """ | |
| DNS_PROVIDERS: Dict[str, str] = { | |
| "cloudflare": "one.one.one.one", | |
| "google": "dns.google", | |
| "nextdns": "dns.nextdns.io", | |
| "quad9": "dns.quad9.net", | |
| "adguard": "dns.adguard.com", | |
| } | |
| def apply_tcp_tuning(self) -> None: | |
| Logger.header("🌐 NETWORK — TCP Stack 4K Streaming Profile") | |
| Logger.shield( | |
| "mDNS/multicast settings preserved — Cast discovery unaffected by TCP tuning" | |
| ) | |
| # TCP buffer sizes: min/default/max (bytes) | |
| ADBShell.setting_put( | |
| "global", "net.tcp.buffersize.wifi", | |
| "262144,1048576,2097152,131072,524288,1048576" | |
| ) | |
| Logger.log(" ✓ WiFi TCP buffers: 256KB / 1MB / 2MB") | |
| ADBShell.setting_put( | |
| "global", "net.tcp.buffersize.ethernet", | |
| "524288,2097152,4194304,262144,1048576,2097152" | |
| ) | |
| Logger.log(" ✓ Ethernet TCP buffers: 512KB / 2MB / 4MB") | |
| # Initial receive window: 60 segments = ~87KB (fast stream start) | |
| ADBShell.setting_put("global", "tcp_default_init_rwnd", "60") | |
| Logger.log(" ✓ TCP initial receive window: 60 segments (~87KB)") | |
| # TCP keepalive — detect stale IPTV connections faster | |
| ADBShell.prop_set("net.ipv4.tcp_keepalive_intvl", "30") | |
| ADBShell.prop_set("net.ipv4.tcp_keepalive_probes", "3") | |
| Logger.log(" ✓ TCP keepalive: 30s interval, 3 probes") | |
| Logger.success("TCP stack optimized for 4K streaming") | |
| def set_private_dns(self, provider: str = "cloudflare") -> None: | |
| """ | |
| Configure Private DNS (DNS-over-TLS). | |
| Cast-safe note: Private DNS only affects unicast A/AAAA queries. | |
| mDNS (.local domains, port 5353 multicast) is NOT routed through | |
| Private DNS and is completely unaffected by this setting. | |
| Chromecast discovery WILL continue to work normally. | |
| """ | |
| Logger.header("🔒 PRIVATE DNS — DNS-over-TLS Configuration") | |
| Logger.shield( | |
| "Private DNS (DoT) only affects unicast DNS. " | |
| "Chromecast mDNS discovery uses multicast UDP — unaffected." | |
| ) | |
| hostname = self.DNS_PROVIDERS.get(provider.lower()) | |
| if not hostname: | |
| Logger.error(f"Unknown DNS provider: {provider}. Choose: {list(self.DNS_PROVIDERS)}") | |
| return | |
| ADBShell.setting_put("global", "private_dns_mode", "hostname") | |
| ADBShell.setting_put("global", "private_dns_specifier", hostname) | |
| Logger.success(f"Private DNS: {hostname}") | |
| # Flush DNS cache (unicast only — mDNS cache unaffected) | |
| ADBShell.run("ndc resolver flushnet 100", ignore_errors=True, silent=True) | |
| Logger.log(" ✓ Unicast DNS cache flushed") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SAFE DEBLOATER — Chromecast gate enforced | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class SafeDebloater: | |
| """ | |
| Removes bloatware while enforcing ChromecastProtection at every step. | |
| Uses pm disable-user --user 0 (reversible) — NOT pm uninstall. | |
| Any package in ChromecastProtection.PROTECTED_PACKAGES is skipped with | |
| an explicit log entry explaining the engineering reason. | |
| """ | |
| def run(self) -> None: | |
| Logger.header("🗑 SAFE DEBLOAT — Chromecast Services Protected") | |
| disabled, protected, skipped, failed = 0, 0, 0, 0 | |
| for pkg, reason, cast_impact in SAFE_DEBLOAT_DB: | |
| # ── Chromecast gate: absolute check before any action ────────── | |
| if ChromecastProtection.is_protected(pkg): | |
| protected += 1 | |
| Logger.shield(f"PROTECTED — skipped: {pkg}") | |
| Logger.log( | |
| f" └─ {ChromecastProtection.get_reason(pkg)}", "cyan" | |
| ) | |
| continue | |
| # Idempotency: already disabled? | |
| if not ADBShell.pkg_enabled(pkg): | |
| skipped += 1 | |
| Logger.log(f" ○ Already disabled: {pkg}", "cyan") | |
| continue | |
| result = ADBShell.run( | |
| f"pm disable-user --user 0 {pkg}", | |
| ignore_errors=True, silent=True, | |
| ) | |
| if "disabled" in result.lower() or result == "": | |
| disabled += 1 | |
| Logger.success(f"Disabled: {pkg}") | |
| Logger.log(f" └─ {reason} | Cast impact: {cast_impact}", "cyan") | |
| else: | |
| failed += 1 | |
| Logger.warning(f"Could not disable: {pkg}") | |
| Logger.header( | |
| f"DEBLOAT DONE: {disabled} disabled | {protected} protected (Cast) | " | |
| f"{skipped} already off | {failed} failed" | |
| ) | |
| def restore_package(self, pkg: str) -> None: | |
| """Re-enable a single package (emergency rollback).""" | |
| ADBShell.run(f"pm enable --user 0 {pkg}", ignore_errors=True) | |
| Logger.success(f"Re-enabled: {pkg}") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # MEMORY & LMK OPTIMIZER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class MemoryOptimizer: | |
| """LMK + RAM management for 1.4GB constrained device.""" | |
| def apply_lmk_profile(self) -> None: | |
| Logger.header("🧹 LMK — SmartTube + Cast Priority Memory Profile") | |
| lmk_props = [ | |
| ("ro.lmk.critical", "0"), # Kill only at true critical low memory | |
| ("ro.lmk.kill_heaviest_task", "true"), # Kill largest non-protected process | |
| ("ro.lmk.downgrade_pressure", "100"), # Aggressive downgrade before kill | |
| ("ro.lmk.upgrade_pressure", "50"), # Promote cached processes quickly | |
| ("ro.lmk.use_psi", "true"), # Use Pressure Stall Info for smarter kills | |
| ("ro.lmk.filecache_min_kb", "51200"), # Keep 50MB for VP9 segment file cache | |
| ] | |
| for key, value in lmk_props: | |
| ADBShell.prop_set(key, value) | |
| Logger.log(f" ✓ {key} = {value}") | |
| # 3 background processes: OS services + Cast daemon + Launcher | |
| # SmartTube runs in foreground → exempt from this limit | |
| ADBShell.setting_put("global", "background_process_limit", "3") | |
| Logger.log(" ✓ background_process_limit = 3") | |
| Logger.success("LMK configured: SmartTube + Cast services protected from OOM") | |
| def deep_clean(self) -> None: | |
| """ | |
| Aggressive RAM release — Cast services verified alive afterward. | |
| Belt-and-suspenders: restore_cast_services() called post-clean. | |
| """ | |
| Logger.header("🔄 DEEP CLEAN — Cast-Safe RAM Release") | |
| Logger.log(" Releasing background apps (am kill-all)...") | |
| ADBShell.run("am kill-all", ignore_errors=True) | |
| Logger.log(" Trimming app caches (2GB limit)...") | |
| ADBShell.run("pm trim-caches 2G", ignore_errors=True) | |
| ADBShell.run("dumpsys batterystats --reset", ignore_errors=True, silent=True) | |
| Logger.log(" Dropping kernel page cache (root required)...") | |
| ADBShell.run_root("sync && echo 3 > /proc/sys/vm/drop_caches") | |
| # Critical: restore Cast services after kill-all may have killed them | |
| Logger.shield("Re-verifying Cast services after cleanup...") | |
| ChromecastServiceManager.restore() | |
| Logger.success("Deep clean complete — Cast services re-verified active") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # UI OPTIMIZER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class UIOptimizer: | |
| def apply(self, scale: float = 0.5) -> None: | |
| """0.5x animation scale: optimal for Android TV (0=broken feel, 1=sluggish on A15).""" | |
| Logger.header(f"🎨 UI — Animation Scale {scale}x + TV Recommendations Off") | |
| for key in [ | |
| "window_animation_scale", | |
| "transition_animation_scale", | |
| "animator_duration_scale", | |
| ]: | |
| ADBShell.setting_put("global", key, str(scale)) | |
| Logger.log(f" ✓ {key} = {scale}x") | |
| # Disable TV content recommendations (background HTTP polling, wastes RAM/CPU) | |
| ADBShell.run("settings put secure tv_disable_recommendations 1", ignore_errors=True) | |
| ADBShell.run("settings put secure tv_enable_preview_programs 0", ignore_errors=True) | |
| ADBShell.run("settings put secure tv_watch_next_enabled 0", ignore_errors=True) | |
| Logger.log(" ✓ TV recommendations + preview programs disabled") | |
| Logger.success("UI responsiveness optimized") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SHIZUKU MANAGER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ShizukuManager: | |
| """Deploy Shizuku privilege manager using ADB bootstrap method.""" | |
| _BOOTSTRAP_CMD = ( | |
| "P=$(pm path moe.shizuku.privileged.api | cut -d: -f2); " | |
| "CLASSPATH=$P app_process /system/bin " | |
| "--nice-name=shizuku_server moe.shizuku.server.ShizukuServiceServer &" | |
| ) | |
| def deploy(self) -> None: | |
| Logger.header("🔑 SHIZUKU — Privilege Engine Deployment") | |
| pkg = DeviceSpec.SHIZUKU_PKG | |
| if not ADBShell.pkg_enabled(pkg): | |
| Logger.log("Shizuku not installed — downloading...") | |
| apk_local = CACHE_DIR / "shizuku.apk" | |
| if not apk_local.exists(): | |
| os.system(f'curl -L -s --retry 3 -o "{apk_local}" "{DeviceSpec.SHIZUKU_APK_URL}"') | |
| if ADBShell.push(str(apk_local), "/data/local/tmp/s.apk"): | |
| ADBShell.run("pm install -r -g /data/local/tmp/s.apk", ignore_errors=True) | |
| ADBShell.run("rm /data/local/tmp/s.apk", ignore_errors=True, silent=True) | |
| Logger.success("Shizuku installed") | |
| else: | |
| Logger.success("Shizuku already installed") | |
| Logger.log("Starting Shizuku bootstrap server...") | |
| ADBShell.run(self._BOOTSTRAP_CMD, ignore_errors=True) | |
| time.sleep(3) | |
| Logger.success("Shizuku server started — verify status in Shizuku app") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # APP INSTALLER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class AppInstaller: | |
| """Idempotent APK installer — always checks before downloading.""" | |
| def install_smarttube(self, beta: bool = False) -> bool: | |
| pkg = DeviceSpec.SMARTTUBE_PKG | |
| url = DeviceSpec.SMARTTUBE_BETA_URL if beta else DeviceSpec.SMARTTUBE_STABLE_URL | |
| name = "SmartTube Beta" if beta else "SmartTube Stable" | |
| Logger.header(f"📦 {name} — INSTALLER") | |
| if ADBShell.pkg_enabled(pkg): | |
| Logger.success(f"{name} already installed — skipping download") | |
| return True | |
| apk_local = CACHE_DIR / f"smarttube_{'beta' if beta else 'stable'}.apk" | |
| if not apk_local.exists(): | |
| Logger.log(f"Downloading {name}...") | |
| if os.system(f'curl -L -s --retry 3 -o "{apk_local}" "{url}"') != 0: | |
| Logger.error("Download failed") | |
| return False | |
| remote = "/data/local/tmp/smarttube.apk" | |
| if not ADBShell.push(str(apk_local), remote): | |
| Logger.error("APK push failed") | |
| return False | |
| result = ADBShell.run(f"pm install -r -g {remote}", ignore_errors=True) | |
| ADBShell.run(f"rm {remote}", ignore_errors=True, silent=True) | |
| if "success" in result.lower(): | |
| Logger.success(f"{name} installed successfully") | |
| return True | |
| Logger.error(f"Installation failed: {result}") | |
| return False | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # DIAGNOSTICS | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class Diagnostics: | |
| @staticmethod | |
| def full_report() -> None: | |
| Logger.header("📊 SYSTEM DIAGNOSTICS — Full Health Report") | |
| # CPU load | |
| loadavg = ADBShell.run("cat /proc/loadavg", silent=True) | |
| if loadavg: | |
| parts = loadavg.split()[:3] | |
| Logger.log(f" CPU Load (1m/5m/15m): {' / '.join(parts)}") | |
| # RAM | |
| meminfo = ADBShell.run("cat /proc/meminfo", silent=True) | |
| if meminfo: | |
| fields: Dict[str, int] = {} | |
| for line in meminfo.splitlines(): | |
| p = line.split() | |
| if len(p) >= 2 and p[1].isdigit(): | |
| fields[p[0].rstrip(":")] = int(p[1]) | |
| total = fields.get("MemTotal", 0) // 1024 | |
| free = fields.get("MemFree", 0) // 1024 | |
| avail = fields.get("MemAvailable", 0) // 1024 | |
| cached = fields.get("Cached", 0) // 1024 | |
| pct = avail / total * 100 if total else 0 | |
| color = "success" if pct > 30 else ("warning" if pct > 15 else "error") | |
| Logger.log(f" RAM Total : {total} MB") | |
| Logger.log(f" RAM Free : {free} MB") | |
| Logger.log(f" RAM Available : {avail} MB ({pct:.1f}%)", color) | |
| Logger.log(f" Cached : {cached} MB") | |
| # Thermal | |
| for zone in range(4): | |
| raw = ADBShell.run( | |
| f"cat /sys/class/thermal/thermal_zone{zone}/temp", silent=True | |
| ) | |
| if raw and raw.lstrip("-").isdigit(): | |
| temp_c = int(raw) / 1000 | |
| color = "success" if temp_c < 55 else ("warning" if temp_c < 70 else "error") | |
| Logger.log(f" Thermal zone{zone}: {temp_c:.1f}°C", color) | |
| # Storage | |
| storage = ADBShell.run("df -h /data", silent=True) | |
| if storage: | |
| lines = storage.splitlines() | |
| if len(lines) > 1: | |
| Logger.log(f" Storage /data : {lines[1]}") | |
| # Network | |
| ping = ADBShell.run("ping -c 2 -W 2 1.1.1.1", ignore_errors=True, silent=True) | |
| if "2 received" in ping: | |
| Logger.success(" Internet: OK (Cloudflare reachable)") | |
| elif "1 received" in ping: | |
| Logger.warning(" Internet: Degraded (packet loss)") | |
| else: | |
| Logger.error(" Internet: OFFLINE") | |
| # Cast service audit (compact) | |
| Logger.log("\n --- Chromecast Service Status ---") | |
| for pkg in list(ChromecastProtection.PROTECTED_PACKAGES)[:6]: | |
| active = ADBShell.pkg_enabled(pkg) | |
| icon = "🛡✓" if active else "🛡✗" | |
| color = "success" if active else "error" | |
| Logger.log(f" {icon} {pkg.split('.')[-1]}", color) | |
| # SmartTube | |
| if ADBShell.pkg_enabled(DeviceSpec.SMARTTUBE_PKG): | |
| Logger.success(" SmartTube: Installed") | |
| else: | |
| Logger.warning(" SmartTube: Not installed") | |
| # Renderer | |
| renderer = ADBShell.prop_get("debug.hwui.renderer") | |
| Logger.log(f" HWUI Renderer : {renderer or 'default'}") | |
| tunnel = ADBShell.prop_get("media.tunneled-playback.enable") | |
| color = "success" if tunnel == "true" else "warning" | |
| Logger.log(f" Tunnel Mode : {tunnel or 'not set'}", color) | |
| @staticmethod | |
| def codec_report() -> None: | |
| Logger.header("🎬 CODEC AVAILABILITY — BCM7362 VPU Matrix") | |
| codecs = [ | |
| ("H.264 AVC", "OMX.brcm.video.h264.decoder", "HW", "✓ 4K@30fps"), | |
| ("H.265 HEVC", "OMX.brcm.video.h265.decoder", "HW", "✓ 4K@60fps"), | |
| ("VP9 prof.2", "OMX.brcm.video.vp9.decoder", "HW", "✓ 4K@60fps ← RECOMMENDED"), | |
| ("AV1", "c2.android.av1.decoder", "SOFTWARE", "✗ DO NOT USE — CPU killer"), | |
| ] | |
| for name, component, hw_sw, status in codecs: | |
| level = "success" if hw_sw == "HW" else "error" | |
| Logger.log(f" [{hw_sw:8s}] {name:12s} {status}", level) | |
| Logger.log(f" Component: {component}", "cyan") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # MAIN ORCHESTRATOR | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class PlayboxTitaniumULTRA: | |
| """ | |
| Master orchestration class. | |
| Every optimization mode enforces ChromecastProtection throughout. | |
| """ | |
| def __init__(self, device: str) -> None: | |
| self.device = device | |
| self.smarttube = SmartTubeOptimizer() | |
| self.broadcom = BroadcomTuner() | |
| self.audio = AudioOptimizer() | |
| self.network = NetworkOptimizer() | |
| self.memory = MemoryOptimizer() | |
| self.ui = UIOptimizer() | |
| self.debloater = SafeDebloater() | |
| self.cast = ChromecastServiceManager() | |
| self.installer = AppInstaller() | |
| self.shizuku = ShizukuManager() | |
| self.diag = Diagnostics() | |
| # ── Composite actions ──────────────────────────────────────────────────── | |
| def _video_pipeline(self) -> None: | |
| self.smarttube.optimize_exoplayer_pipeline() | |
| self.smarttube.force_tunnel_mode() | |
| def _broadcom_full(self) -> None: | |
| self.broadcom.apply_gpu_tweaks() | |
| self.broadcom.apply_vpu_tweaks() | |
| self.broadcom.set_performance_governor() | |
| def _codec_protection(self) -> None: | |
| self.smarttube.suppress_av1_software_decode() | |
| self.smarttube.tune_dalvik_heap() | |
| def _network_full(self) -> None: | |
| self.network.apply_tcp_tuning() | |
| self.network.set_private_dns("cloudflare") | |
| # ── Banner & Menu ──────────────────────────────────────────────────────── | |
| def _banner(self) -> None: | |
| c = Logger.COLORS | |
| print(f""" | |
| {c['header']}{c['bold']}╔══════════════════════════════════════════════════════════════════════╗ | |
| ║ PLAYBOX TITANIUM v{VERSION} | |
| ║ Sagemcom DCTIW362P │ Android TV 9 │ Broadcom BCM7362 | |
| ╠══════════════════════════════════════════════════════════════════════╣ | |
| ║ 🎬 SmartTube VP9 HW Pipeline 🚇 Tunnel Mode 🛡 Cast Protected | |
| ║ 🧠 Dalvik SmartTube Profile ⚙ CPU Performance Governor | |
| ╚══════════════════════════════════════════════════════════════════════╝ | |
| {c['reset']} Connected: {c['cyan']}{self.device}{c['reset']} | |
| """) | |
| def _menu(self) -> None: | |
| c = Logger.COLORS | |
| while True: | |
| os.system("clear") | |
| self._banner() | |
| print(f"""{c['bold']}╔══════════════════════════════════════════════════════════════════╗{c['reset']} | |
| {c['success']}VIDEO PERFORMANCE{c['reset']} | |
| {c['success']}1.{c['reset']} SmartTube ExoPlayer + Tunnel Mode Pipeline | |
| {c['success']}2.{c['reset']} Broadcom GPU + VPU + CPU Governor | |
| {c['success']}3.{c['reset']} Audio A/V Sync Fix (HDMI desync) | |
| {c['success']}4.{c['reset']} AV1 Suppression + Dalvik Heap (VP9 HW path) | |
| {c['success']}5.{c['reset']} AOT Compile SmartTube (JIT burst elimination) | |
| {c['header']}CHROMECAST PROTECTION{c['reset']} | |
| {c['success']}6.{c['reset']} 🛡 Audit Cast Services (health check) | |
| {c['success']}7.{c['reset']} 🛡 Restore Cast Services (repair broken Cast) | |
| {c['success']}8.{c['reset']} 📡 Cast mDNS/Multicast Network Tuning | |
| {c['success']}9.{c['reset']} 🎯 Cast Process OOM Hardening | |
| {c['success']}P.{c['reset']} 🔍 Show Chromecast Protection Registry | |
| {c['info']}SYSTEM{c['reset']} | |
| {c['info']}10.{c['reset']} Network TCP + Private DNS (Cloudflare) | |
| {c['info']}11.{c['reset']} Safe Debloat (Cast services PROTECTED) | |
| {c['info']}12.{c['reset']} LMK Memory Profile (SmartTube + Cast priority) | |
| {c['info']}13.{c['reset']} UI Responsiveness (0.5x animations) | |
| {c['info']}14.{c['reset']} Deep Clean RAM (Cast-safe + re-verify) | |
| {c['warning']}TOOLS{c['reset']} | |
| {c['warning']}15.{c['reset']} Full System Diagnostics | |
| {c['warning']}16.{c['reset']} Codec Availability Report | |
| {c['warning']}17.{c['reset']} Install SmartTube Stable | |
| {c['warning']}18.{c['reset']} Deploy Shizuku | |
| {c['cyan']}AUTO MODES{c['reset']} | |
| {c['cyan']}20.{c['reset']} 🚀 SMARTTUBE ULTRA (video-focused, Cast safe) | |
| {c['cyan']}21.{c['reset']} 🏆 FULL SYSTEM ULTRA (all modules, Cast safe) | |
| {c['error']}0.{c['reset']} Exit | |
| {c['bold']}╚══════════════════════════════════════════════════════════════════╝{c['reset']}""") | |
| choice = input(f"\n{c['cyan']}Choice > {c['reset']}").strip().lower() | |
| dispatch: Dict[str, Callable] = { | |
| "1": self._video_pipeline, | |
| "2": self._broadcom_full, | |
| "3": self.audio.fix_av_sync_and_pipeline, | |
| "4": self._codec_protection, | |
| "5": self.smarttube.compile_aot, | |
| "6": self.cast.audit, | |
| "7": self.cast.restore, | |
| "8": self.cast.optimize_cast_network, | |
| "9": self.cast.harden_cast_processes, | |
| "p": ChromecastProtection.print_registry, | |
| "10": self._network_full, | |
| "11": self.debloater.run, | |
| "12": self.memory.apply_lmk_profile, | |
| "13": self.ui.apply, | |
| "14": self.memory.deep_clean, | |
| "15": self.diag.full_report, | |
| "16": self.diag.codec_report, | |
| "17": self.installer.install_smarttube, | |
| "18": self.shizuku.deploy, | |
| "20": self.run_smarttube_ultra, | |
| "21": self.run_full_ultra, | |
| "0": self._exit, | |
| } | |
| fn = dispatch.get(choice) | |
| if fn: | |
| fn() | |
| else: | |
| Logger.warning("Invalid choice") | |
| if choice != "0": | |
| input(f"\n{c['cyan']}Press Enter...{c['reset']}") | |
| def _exit(self) -> None: | |
| Logger.save_log() | |
| Logger.log(f"Session saved: {LOG_FILE}", "cyan") | |
| sys.exit(0) | |
| # ── Auto modes ─────────────────────────────────────────────────────────── | |
| def run_smarttube_ultra(self) -> None: | |
| """ | |
| SmartTube Ultra Mode. | |
| Focuses on maximum video playback performance. | |
| Chromecast protection enforced at start, hardened at end. | |
| """ | |
| Logger.header("🚀 SMARTTUBE ULTRA MODE — Maximum Video Performance") | |
| pipeline: List[Tuple[str, Callable]] = [ | |
| ("Chromecast Audit (pre-check)", self.cast.audit), | |
| ("Broadcom GPU + VPU + CPU", self._broadcom_full), | |
| ("ExoPlayer + Tunnel Mode Pipeline", self._video_pipeline), | |
| ("AV1 Suppression + Dalvik Heap", self._codec_protection), | |
| ("Audio A/V Sync Fix", self.audio.fix_av_sync_and_pipeline), | |
| ("LMK Memory Profile", self.memory.apply_lmk_profile), | |
| ("UI Responsiveness", self.ui.apply), | |
| ("Cast mDNS Network Tuning", self.cast.optimize_cast_network), | |
| ("Cast Process Hardening", self.cast.harden_cast_processes), | |
| ("SmartTube AOT Compilation", self.smarttube.compile_aot), | |
| ("Cast Services Restore (final)", self.cast.restore), | |
| ] | |
| total = len(pipeline) | |
| for idx, (name, fn) in enumerate(pipeline, 1): | |
| Logger.log(f"\n[{idx}/{total}] {name}...", "cyan") | |
| fn() | |
| time.sleep(0.4) | |
| Logger.header("🎉 SMARTTUBE ULTRA COMPLETE") | |
| Logger.success("VP9 HW decode + Tunnel Mode + AOT compiled + Cast Protected") | |
| Logger.log( | |
| "ACTION REQUIRED in SmartTube: Settings → Player → Video codec → VP9", | |
| "warning" | |
| ) | |
| Logger.log("Reboot recommended in ~5 minutes for full effect", "warning") | |
| Logger.save_log() | |
| def run_full_ultra(self) -> None: | |
| """ | |
| Full System Ultra — all modules activated. | |
| Chromecast protection enforced at every debloat/clean step. | |
| """ | |
| Logger.header("🏆 FULL SYSTEM ULTRA — Complete Optimization") | |
| pipeline: List[Tuple[str, Callable]] = [ | |
| ("System Diagnostics (baseline)", self.diag.full_report), | |
| ("Chromecast Audit (pre-check)", self.cast.audit), | |
| ("Broadcom GPU + VPU + CPU", self._broadcom_full), | |
| ("ExoPlayer + Tunnel Mode", self._video_pipeline), | |
| ("AV1 Suppression + Dalvik Heap", self._codec_protection), | |
| ("Audio A/V Sync Fix", self.audio.fix_av_sync_and_pipeline), | |
| ("TCP Network + Cloudflare DNS", self._network_full), | |
| ("Safe Debloat (Cast Protected)", self.debloater.run), | |
| ("LMK Memory Profile", self.memory.apply_lmk_profile), | |
| ("UI Responsiveness", self.ui.apply), | |
| ("Cast mDNS Network Tuning", self.cast.optimize_cast_network), | |
| ("Cast Process OOM Hardening", self.cast.harden_cast_processes), | |
| ("SmartTube AOT Compilation", self.smarttube.compile_aot), | |
| ("Deep Clean RAM (Cast-safe)", self.memory.deep_clean), | |
| ("Final Chromecast Audit", self.cast.audit), | |
| ] | |
| total = len(pipeline) | |
| for idx, (name, fn) in enumerate(pipeline, 1): | |
| Logger.log(f"\n[{idx}/{total}] {name}...", "cyan") | |
| fn() | |
| time.sleep(0.3) | |
| Logger.header("🏆 FULL SYSTEM ULTRA COMPLETE") | |
| Logger.success("All optimizations applied. Chromecast Built-in: PROTECTED ✓") | |
| Logger.success("SmartTube: VP9 HW + Tunnel Mode + AOT compiled ✓") | |
| Logger.log(f"Reboot: adb -s {self.device} reboot", "warning") | |
| Logger.save_log() | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # CLI ENTRY POINT | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| def _build_parser() -> argparse.ArgumentParser: | |
| p = argparse.ArgumentParser( | |
| description=f"Playbox Titanium v{VERSION} — Sagemcom DCTIW362P", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| EXAMPLES: | |
| # Interactive menu (default) | |
| python3 Autopilot_11_ULTRA_SmartTube.py | |
| # SmartTube Ultra (video-focused) | |
| python3 Autopilot_11_ULTRA_SmartTube.py --device 192.168.1.3:5555 --smarttube-ultra | |
| # Full system optimization | |
| python3 Autopilot_11_ULTRA_SmartTube.py --device 192.168.1.3:5555 --full-ultra | |
| # Emergency: restore broken Chromecast | |
| python3 Autopilot_11_ULTRA_SmartTube.py --cast-restore | |
| # Audit Cast services only | |
| python3 Autopilot_11_ULTRA_SmartTube.py --cast-audit | |
| # Diagnostics | |
| python3 Autopilot_11_ULTRA_SmartTube.py --diagnostics | |
| """, | |
| ) | |
| p.add_argument("--device", default=None, | |
| help="ADB target IP:PORT (default: auto-detect)") | |
| p.add_argument("--smarttube-ultra", action="store_true", | |
| help="SmartTube Ultra Mode") | |
| p.add_argument("--full-ultra", action="store_true", | |
| help="Full system optimization") | |
| p.add_argument("--cast-audit", action="store_true", | |
| help="Audit Chromecast services") | |
| p.add_argument("--cast-restore", action="store_true", | |
| help="Re-enable all Chromecast services (emergency)") | |
| p.add_argument("--diagnostics", action="store_true", | |
| help="Full system diagnostics") | |
| p.add_argument("--codecs", action="store_true", | |
| help="Codec availability report") | |
| p.add_argument("--install-smarttube", action="store_true", | |
| help="Install SmartTube") | |
| p.add_argument("--beta", action="store_true", | |
| help="Use SmartTube Beta channel") | |
| return p | |
| def main() -> None: | |
| args = _build_parser().parse_args() | |
| # Device resolution | |
| device = args.device | |
| if not device: | |
| device = ADBShell.detect_device() | |
| if device: | |
| Logger.success(f"Auto-detected: {device}") | |
| else: | |
| Logger.warning(f"No device detected, using default: {DEFAULT_ADB_HOST}") | |
| device = DEFAULT_ADB_HOST | |
| if not ADBShell.connect(device): | |
| Logger.error(f"Cannot connect to {device}") | |
| sys.exit(1) | |
| app = PlayboxTitaniumULTRA(device) | |
| # CLI dispatch | |
| if args.cast_restore: | |
| ChromecastServiceManager.restore() | |
| elif args.cast_audit: | |
| ChromecastServiceManager.audit() | |
| elif args.diagnostics: | |
| app.diag.full_report() | |
| elif args.codecs: | |
| app.diag.codec_report() | |
| elif args.install_smarttube: | |
| app.installer.install_smarttube(beta=args.beta) | |
| elif args.smarttube_ultra: | |
| app.run_smarttube_ultra() | |
| elif args.full_ultra: | |
| app.run_full_ultra() | |
| else: | |
| app._banner() | |
| app._menu() | |
| if __name__ == "__main__": | |
| try: | |
| main() | |
| except KeyboardInterrupt: | |
| print() | |
| Logger.warning("Interrupted (Ctrl+C)") | |
| Logger.save_log() | |
| sys.exit(0) | |
| except Exception as exc: | |
| Logger.error(f"Unexpected error: {exc}") | |
| import traceback | |
| traceback.print_exc() | |
| sys.exit(1) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| ╔══════════════════════════════════════════════════════════════════════════════╗ | |
| ║ PLAYBOX TITANIUM v12.0 ULTRA — Self-Diagnostic + Auto-Repair Edition ║ | |
| ║ Target : Sagemcom DCTIW362P | Android TV 9 (API 28) | Broadcom m362 ║ | |
| ║ Focus : SmartTube HW, Chromecast, Self-Diagnostics, Auto-Repair ║ | |
| ║ Author : Anonymousik / SecFerro Division ║ | |
| ║ Version : 12.0-ULTRA-SelfHeal ║ | |
| ╠══════════════════════════════════════════════════════════════════════════════╣ | |
| ║ HARDWARE PROFILE (real ADB session 192.168.1.3:5555): ║ | |
| ║ RAM : 1459 MB total | ~534 MB available ║ | |
| ║ FLASH : 3.9 GB | 53% used (/data) ║ | |
| ║ SoC : Broadcom BCM7362 | Dual-core Cortex-A15 @ 1.0 GHz ║ | |
| ║ VPU : H.264 ✓ | H.265 ✓ | VP9 ✓ | AV1 ✗ (SW only) ║ | |
| ║ GPU : Broadcom VideoCore (GLES 3.1, NO Vulkan) ║ | |
| ║ Display : HDMI 2.0a, CEC v1.4, 4K@60fps, HDR10 ║ | |
| ╠══════════════════════════════════════════════════════════════════════════════╣ | |
| ║ CHANGELOG v12 vs v11: ║ | |
| ║ [FIX] DNS: one.one.one.one (DoT) + net.dns1/net.dns2 setprop ║ | |
| ║ [FIX] SmartTube pkg: org.smarttube.stable (verified from ps output) ║ | |
| ║ [FIX] HDMI addr: persist.sys.hdmi.addr.playback=11 (BCM Nexus correct) ║ | |
| ║ [NEW] debug.stagefright.ccodec=1 (C2 pipeline), skiaglthreaded renderer ║ | |
| ║ [NEW] LMK minfree /sys tuning, swappiness=10, VFS pressure=50 ║ | |
| ║ [NEW] TCP kernel params (SACK, timestamps, window scaling, BBR attempt) ║ | |
| ║ [NEW] I/O scheduler=deadline, read_ahead_kb=512 ║ | |
| ║ [NEW] BCM Nexus props (persist.nx.hdmi.*, persist.nx.vidout.50hz) ║ | |
| ║ [NEW] InteractiveDiagnostics — 8 service categories, self-healing ║ | |
| ║ [NEW] AutoRepair — detects broken sectors, re-downloads + reinstalls APKs ║ | |
| ║ [NEW] CastRepair — restores debloat.sh damage (mediashell kill prevention) ║ | |
| ╚══════════════════════════════════════════════════════════════════════════════╝ | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import sys | |
| import subprocess | |
| import time | |
| import json | |
| import hashlib | |
| import argparse | |
| import tempfile | |
| import shutil | |
| from pathlib import Path | |
| from typing import Optional, List, Dict, Tuple, Callable, Any | |
| from dataclasses import dataclass, field | |
| from enum import Enum, auto | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # CONSTANTS | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| VERSION = "12.0-ULTRA-SelfHeal" | |
| DEFAULT_ADB_HOST = "192.168.1.3:5555" | |
| CACHE_DIR = Path.home() / ".playbox_cache" | |
| BACKUP_DIR = CACHE_DIR / "backups_v12" | |
| LOG_FILE = CACHE_DIR / "autopilot_v12.log" | |
| REPAIR_LOG = CACHE_DIR / "repair_v12.log" | |
| for d in (CACHE_DIR, BACKUP_DIR): | |
| d.mkdir(parents=True, exist_ok=True) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # STATUS ENUM | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class Status(Enum): | |
| OK = auto() | |
| WARN = auto() | |
| BROKEN = auto() | |
| MISSING = auto() | |
| UNKNOWN = auto() | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # DEVICE SPEC (verified from real ps + getprop session) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class DeviceSpec: | |
| MODEL = "dctiw362p" | |
| HARDWARE = "m362" | |
| ANDROID_VERSION = "9" | |
| API_LEVEL = "28" | |
| # BCM7362 VPU — confirmed from OMX enumeration on this chipset family | |
| CODEC_HW_H264 = "OMX.brcm.video.h264.decoder" | |
| CODEC_HW_HEVC = "OMX.brcm.video.h265.decoder" | |
| CODEC_HW_VP9 = "OMX.brcm.video.vp9.decoder" | |
| CODEC_SW_AV1 = "c2.android.av1.decoder" # HW not present → AVOID | |
| # Dalvik heap for ~534 MB available | |
| DALVIK_HEAP_SIZE = "256m" | |
| DALVIK_HEAP_GROWTH = "128m" | |
| DALVIK_HEAP_START = "16m" | |
| DALVIK_HEAP_MIN_FREE = "2m" | |
| DALVIK_HEAP_MAX_FREE = "8m" | |
| DALVIK_HEAP_TARGET = "0.75" | |
| # ── VERIFIED PACKAGE NAMES (from real adb shell ps output) ─────────────── | |
| # CRITICAL: org.smarttube.stable — NOT com.teamsmart.videomanager.tv | |
| # Real ps: u0_a89 6624 2996 org.smarttube.stable | |
| SMARTTUBE_PKG_STABLE = "org.smarttube.stable" | |
| SMARTTUBE_PKG_BETA = "org.smarttube.beta" | |
| SMARTTUBE_PKG_LEGACY = "com.liskovsoft.smarttubetv" # old package, still accepted | |
| SMARTTUBE_STABLE_URL = "https://github.com/yuliskov/SmartTube/releases/download/latest/smarttube_stable.apk" | |
| SMARTTUBE_BETA_URL = "https://github.com/yuliskov/SmartTube/releases/download/latest/smarttube_beta.apk" | |
| # Verified from real ps: u0_a88 26563 com.spocky.projengmenu | |
| PROJECTIVY_PKG = "com.spocky.projengmenu" | |
| PROJECTIVY_URL = "https://github.com/spocky/projectivy-launcher/releases/latest/download/Projectivy_Launcher.apk" | |
| SHIZUKU_PKG = "moe.shizuku.privileged.api" | |
| SHIZUKU_URL = "https://github.com/RikkaApps/Shizuku/releases/download/v13.5.4/shizuku-v13.5.4-release.apk" | |
| # DNS: correct DoT hostnames (verified working on Android 9) | |
| DNS_PROVIDERS: Dict[str, Tuple[str, str, str]] = { | |
| # name: (dot_hostname, ip_primary, ip_secondary) | |
| "cloudflare": ("one.one.one.one", "1.1.1.1", "1.0.0.1"), | |
| "google": ("dns.google", "8.8.8.8", "8.8.4.4"), | |
| "quad9": ("dns.quad9.net", "9.9.9.9", "149.112.112.112"), | |
| "adguard": ("dns.adguard.com", "94.140.14.14", "94.140.15.15"), | |
| "nextdns": ("dns.nextdns.io", "45.90.28.0", "45.90.30.0"), | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # CHROMECAST PROTECTION REGISTRY | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ChromecastProtection: | |
| """ | |
| Protected package whitelist — these are NEVER disabled/modified. | |
| IMPORTANT: real debloat.sh on device lists apps.mediashell as "safe to disable". | |
| This is WRONG. It IS the Chromecast daemon. We defend against this mistake. | |
| ALSO: gms.cast.receiver found in debloat.sh — also protected here. | |
| """ | |
| PROTECTED: Dict[str, str] = { | |
| "com.google.android.apps.mediashell": | |
| "Cast daemon — THIS IS CHROMECAST BUILT-IN. NEVER disable.", | |
| "com.google.android.gms": | |
| "GMS — Cast SDK host, auth, SessionManager.", | |
| "com.google.android.gsf": | |
| "Google Services Framework — GMS dependency.", | |
| "com.google.android.nearby": | |
| "Nearby — mDNS/BLE Cast discovery.", | |
| "com.google.android.gms.cast.receiver": | |
| "Cast Receiver Framework — part of GMS Cast stack.", | |
| "com.google.android.tv.remote.service": | |
| "TV Remote Service — Cast session UI & input.", | |
| "com.google.android.tvlauncher": | |
| "TV Launcher — Cast ambient mode, intent handoff.", | |
| "com.google.android.configupdater": | |
| "Config Updater — Cast TLS certificates.", | |
| "com.google.android.wifidisplay": | |
| "WiFi Display — Miracast/Cast fallback.", | |
| "com.android.networkstack": | |
| "Network Stack — IGMP multicast for mDNS.", | |
| "com.android.networkstack.tethering": | |
| "Network Tethering — multicast routing.", | |
| } | |
| @classmethod | |
| def is_protected(cls, pkg: str) -> bool: | |
| return pkg in cls.PROTECTED | |
| @classmethod | |
| def get_reason(cls, pkg: str) -> str: | |
| return cls.PROTECTED.get(pkg, "") | |
| @classmethod | |
| def print_all(cls) -> None: | |
| Logger.header("🛡 CHROMECAST PROTECTION REGISTRY") | |
| for pkg, reason in cls.PROTECTED.items(): | |
| Logger.shield(f"{pkg}") | |
| Logger.log(f" └─ {reason}", "cyan") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # LOGGER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class Logger: | |
| C = { | |
| "info": "\033[94m", | |
| "success": "\033[92m", | |
| "warning": "\033[93m", | |
| "error": "\033[91m", | |
| "header": "\033[95m", | |
| "cyan": "\033[96m", | |
| "bold": "\033[1m", | |
| "reset": "\033[0m", | |
| "dim": "\033[2m", | |
| } | |
| _lines: List[str] = [] | |
| @classmethod | |
| def _emit(cls, msg: str, level: str) -> None: | |
| ts = time.strftime("%H:%M:%S") | |
| c = cls.C.get(level, cls.C["info"]) | |
| print(f"{c}[{ts}] {msg}{cls.C['reset']}") | |
| cls._lines.append(f"[{ts}][{level.upper()}] {msg}") | |
| @classmethod | |
| def log(cls, m: str, l: str = "info") -> None: cls._emit(m, l) | |
| @classmethod | |
| def success(cls, m: str) -> None: cls._emit(f"✓ {m}", "success") | |
| @classmethod | |
| def warning(cls, m: str) -> None: cls._emit(f"⚠ {m}", "warning") | |
| @classmethod | |
| def error(cls, m: str) -> None: cls._emit(f"✗ {m}", "error") | |
| @classmethod | |
| def shield(cls, m: str) -> None: cls._emit(f"🛡 {m}", "success") | |
| @classmethod | |
| def repair(cls, m: str) -> None: cls._emit(f"🔧 {m}", "warning") | |
| @classmethod | |
| def header(cls, msg: str) -> None: | |
| sep = "═" * 74 | |
| print(f"\n{cls.C['header']}{cls.C['bold']}{sep}\n {msg}\n{sep}{cls.C['reset']}\n") | |
| @classmethod | |
| def section(cls, msg: str) -> None: | |
| print(f"\n{cls.C['cyan']} ── {msg} ──{cls.C['reset']}") | |
| @classmethod | |
| def save(cls) -> None: | |
| try: | |
| with open(LOG_FILE, "a", encoding="utf-8") as f: | |
| f.write(f"\n{'─'*60}\nSESSION {time.strftime('%Y-%m-%d %H:%M:%S')} | v{VERSION}\n") | |
| f.write("\n".join(cls._lines) + "\n") | |
| except OSError: | |
| pass | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # ADB SHELL INTERFACE | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ADB: | |
| """ | |
| Secure ADB executor. No shell=True. Retry on Timeout only. | |
| Escalation: su → rish → plain ADB. | |
| """ | |
| device: Optional[str] = None | |
| TIMEOUT: int = 35 | |
| RETRIES: int = 3 | |
| @classmethod | |
| def connect(cls, target: str) -> bool: | |
| try: | |
| r = subprocess.run(["adb", "connect", target], | |
| capture_output=True, text=True, timeout=10) | |
| if "connected" in r.stdout.lower(): | |
| cls.device = target | |
| Logger.success(f"ADB connected: {target}") | |
| return True | |
| Logger.error(f"ADB failed: {r.stdout.strip()}") | |
| return False | |
| except FileNotFoundError: | |
| Logger.error("'adb' not found. Install Android Platform Tools.") | |
| sys.exit(1) | |
| except subprocess.TimeoutExpired: | |
| Logger.error(f"ADB connect timeout: {target}") | |
| return False | |
| @classmethod | |
| def detect(cls) -> Optional[str]: | |
| try: | |
| out = subprocess.check_output(["adb", "devices"], text=True, timeout=5) | |
| for line in out.splitlines(): | |
| if "\tdevice" in line: | |
| return line.split("\t")[0].strip() | |
| except Exception: | |
| pass | |
| return None | |
| @classmethod | |
| def run(cls, cmd: str, silent: bool = False) -> str: | |
| if not cls.device: | |
| return "" | |
| for attempt in range(cls.RETRIES): | |
| try: | |
| out = subprocess.check_output( | |
| ["adb", "-s", cls.device, "shell", cmd], | |
| stderr=subprocess.STDOUT, text=True, timeout=cls.TIMEOUT) | |
| return out.strip() | |
| except subprocess.TimeoutExpired: | |
| if attempt < cls.RETRIES - 1: | |
| time.sleep(1.5) | |
| elif not silent: | |
| Logger.warning(f"Timeout [{attempt+1}]: {cmd[:55]}") | |
| except subprocess.CalledProcessError as e: | |
| return (e.output or "").strip() | |
| except Exception as e: | |
| if not silent: | |
| Logger.error(f"ADB: {e}") | |
| return "" | |
| return "" | |
| @classmethod | |
| def run_root(cls, cmd: str) -> str: | |
| for prefix in (f'su -c "{cmd}"', f'rish -c "{cmd}"'): | |
| r = cls.run(prefix, silent=True) | |
| if r and "not found" not in r and "permission denied" not in r.lower(): | |
| return r | |
| return cls.run(cmd) | |
| @classmethod | |
| def push(cls, local: str, remote: str) -> bool: | |
| try: | |
| subprocess.check_call( | |
| ["adb", "-s", cls.device, "push", local, remote], | |
| stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=120) | |
| return True | |
| except Exception: | |
| return False | |
| @classmethod | |
| def prop(cls, key: str) -> str: | |
| return cls.run(f"getprop {key}", silent=True) | |
| @classmethod | |
| def setprop(cls, key: str, val: str) -> None: | |
| cls.run(f"setprop {key} {val}", silent=True) | |
| @classmethod | |
| def setting_put(cls, ns: str, key: str, val: str) -> None: | |
| cls.run(f"settings put {ns} {key} {val}", silent=True) | |
| @classmethod | |
| def setting_get(cls, ns: str, key: str) -> str: | |
| return cls.run(f"settings get {ns} {key}", silent=True) | |
| @classmethod | |
| def pkg_enabled(cls, pkg: str) -> bool: | |
| return pkg in cls.run(f"pm list packages -e {pkg}", silent=True) | |
| @classmethod | |
| def pkg_installed(cls, pkg: str) -> bool: | |
| return pkg in cls.run(f"pm list packages {pkg}", silent=True) | |
| @classmethod | |
| def pkg_version(cls, pkg: str) -> str: | |
| out = cls.run(f"dumpsys package {pkg} | grep versionName", silent=True) | |
| if "=" in out: | |
| return out.split("=")[-1].strip() | |
| return "unknown" | |
| @classmethod | |
| def syswrite(cls, path: str, val: str) -> bool: | |
| """Write to /sys or /proc path via root.""" | |
| result = cls.run_root(f"echo {val} > {path}") | |
| # Verify | |
| verify = cls.run_root(f"cat {path}").strip() | |
| return val in verify | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # DIAGNOSTIC RESULT | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| @dataclass | |
| class DiagResult: | |
| category: str | |
| check: str | |
| status: Status | |
| found: str | |
| expected: str = "" | |
| fix_fn: Optional[Callable] = None | |
| detail: str = "" | |
| @property | |
| def is_ok(self) -> bool: | |
| return self.status == Status.OK | |
| @property | |
| def needs_repair(self) -> bool: | |
| return self.status in (Status.BROKEN, Status.MISSING) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # APK DOWNLOADER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class APKDownloader: | |
| """Download, verify (size), push and install APK files.""" | |
| @staticmethod | |
| def download(url: str, dest: Path, force: bool = False) -> bool: | |
| if dest.exists() and not force: | |
| Logger.log(f" APK cached: {dest.name}") | |
| return True | |
| Logger.log(f" Downloading {dest.name}...") | |
| ret = os.system(f'curl -L -s --retry 3 --connect-timeout 15 -o "{dest}" "{url}"') | |
| if ret != 0 or not dest.exists() or dest.stat().st_size < 50_000: | |
| Logger.error(f" Download failed or file too small: {dest.name}") | |
| if dest.exists(): | |
| dest.unlink() | |
| return False | |
| size_mb = dest.stat().st_size / 1_048_576 | |
| Logger.success(f" Downloaded: {dest.name} ({size_mb:.1f} MB)") | |
| return True | |
| @staticmethod | |
| def install(apk_local: Path, pkg_hint: str = "") -> bool: | |
| remote = f"/data/local/tmp/{apk_local.name}" | |
| Logger.log(f" Pushing {apk_local.name} → {remote}") | |
| if not ADB.push(str(apk_local), remote): | |
| Logger.error(f" Push failed: {apk_local.name}") | |
| return False | |
| result = ADB.run(f"pm install -r -g --install-reason 1 {remote}", silent=True) | |
| ADB.run(f"rm {remote}", silent=True) | |
| if "success" in result.lower(): | |
| Logger.success(f" Installed: {pkg_hint or apk_local.stem}") | |
| return True | |
| Logger.error(f" Install failed: {result[:80]}") | |
| return False | |
| @staticmethod | |
| def download_and_install(url: str, pkg: str, name: str, force: bool = False) -> bool: | |
| safe_name = pkg.replace(".", "_") + ".apk" | |
| local = CACHE_DIR / safe_name | |
| if not APKDownloader.download(url, local, force): | |
| return False | |
| return APKDownloader.install(local, name) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # DNS MANAGER (bug-fixed) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class DNSManager: | |
| """ | |
| DNS configuration with full dual-path application. | |
| BUG FIX (v10/v11): 'dns.cloudflare.com' was used as DoT hostname — incorrect. | |
| 'one.one.one.one' resolves to 1.1.1.1 and is the valid Cloudflare DoT endpoint. | |
| Dual-path approach: | |
| 1. setprop net.dns1/net.dns2 — legacy resolver (works immediately, no reboot) | |
| 2. settings put global private_dns_* — DoT/DoH encrypted DNS (Android 9+) | |
| Both must be set: setprop affects the current runtime, private_dns_ persists across reboots. | |
| mDNS (Cast discovery, port 5353) is unaffected by either mechanism. | |
| """ | |
| @staticmethod | |
| def set_provider(provider: str = "cloudflare") -> bool: | |
| info = DeviceSpec.DNS_PROVIDERS.get(provider.lower()) | |
| if not info: | |
| Logger.error(f"Unknown provider: {provider}") | |
| return False | |
| dot_hostname, ip1, ip2 = info | |
| Logger.header(f"🔒 DNS — {provider.upper()} ({dot_hostname})") | |
| Logger.shield( | |
| "Private DNS (DoT) is unicast-only. mDNS (port 5353, multicast) " | |
| "used by Chromecast is UNAFFECTED." | |
| ) | |
| # ── PATH 1: legacy resolver (setprop — immediate, no reboot required) ── | |
| ADB.setprop("net.dns1", ip1) | |
| ADB.setprop("net.dns2", ip2) | |
| ADB.setprop("net.rmnet0.dns1", ip1) | |
| ADB.setprop("net.rmnet0.dns2", ip2) | |
| Logger.log(f" ✓ net.dns1/2 = {ip1} / {ip2}") | |
| # ── PATH 2: Private DNS over TLS (persists reboots) ─────────────────── | |
| ADB.setting_put("global", "private_dns_mode", "hostname") | |
| ADB.setting_put("global", "private_dns_specifier", dot_hostname) | |
| Logger.log(f" ✓ Private DNS (DoT): {dot_hostname}") | |
| # ── Flush DNS cache ─────────────────────────────────────────────────── | |
| ADB.run("ndc resolver flushnet 100", silent=True) | |
| ADB.run("ndc resolver clearnetdns 100", silent=True) | |
| Logger.log(" ✓ DNS cache flushed") | |
| # ── Connectivity test ───────────────────────────────────────────────── | |
| test = ADB.run(f"ping -c 2 -W 3 {ip1}", silent=True) | |
| if "2 received" in test or "2 packets" in test: | |
| Logger.success(f"DNS reachable: {ip1}") | |
| else: | |
| Logger.warning(f"DNS ping inconclusive (DoT may still work): {ip1}") | |
| return True | |
| @staticmethod | |
| def get_current() -> Tuple[str, str]: | |
| """Return (dot_hostname, ip1) of currently set DNS.""" | |
| dot = ADB.setting_get("global", "private_dns_specifier") | |
| ip1 = ADB.prop("net.dns1") | |
| return dot or "not set", ip1 or "not set" | |
| @staticmethod | |
| def interactive_select() -> None: | |
| """Interactive DNS provider selection menu.""" | |
| Logger.header("🔒 DNS PROVIDER SELECTION") | |
| providers = list(DeviceSpec.DNS_PROVIDERS.keys()) | |
| for i, name in enumerate(providers, 1): | |
| dot, ip1, ip2 = DeviceSpec.DNS_PROVIDERS[name] | |
| Logger.log(f" {i}. {name.upper():12s} | DoT: {dot} | IPs: {ip1}/{ip2}") | |
| Logger.log(f" 0. Keep current (no change)") | |
| choice = input(f"\n{Logger.C['cyan']}Select [0-{len(providers)}] > {Logger.C['reset']}").strip() | |
| if choice == "0": | |
| return | |
| try: | |
| idx = int(choice) - 1 | |
| if 0 <= idx < len(providers): | |
| DNSManager.set_provider(providers[idx]) | |
| else: | |
| Logger.warning("Invalid selection") | |
| except ValueError: | |
| Logger.warning("Invalid input") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # VIDEO ENGINE OPTIMIZER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class VideoEngineOptimizer: | |
| """ | |
| Comprehensive video pipeline tuning for BCM7362 VPU. | |
| Changes from v11 → v12: | |
| + debug.stagefright.ccodec = 1 (enables C2 codec framework — modern pipeline) | |
| + debug.stagefright.omx_default_rank = 0 (force HW OMX over SW C2 when both exist) | |
| + debug.renderengine.backend = skiaglthreaded (better threading on A15) | |
| + debug.hwui.use_gpu_pixel_buffers = true (GPU pixel buffers reduce CPU copies) | |
| + debug.hwui.render_dirty_regions = false (avoid dirty region calc overhead) | |
| + debug.sf.latch_unsignaled = 1 (reduce SurfaceFlinger latch latency) | |
| + persist.sys.media.avsync = true (BCM A/V sync hint) | |
| + persist.sys.hdmi.keep_awake = true (keep HDMI link alive during buffering) | |
| + drm.service.enabled = true (Widevine/PlayReady explicit enable) | |
| """ | |
| def optimize_codec_pipeline(self) -> None: | |
| Logger.header("🎬 VIDEO CODEC PIPELINE — BCM7362 VPU (C2 + OMX)") | |
| codec_props = [ | |
| # Stagefright core | |
| ("media.stagefright.enable-player", "true"), | |
| ("media.stagefright.enable-http", "true"), | |
| ("media.stagefright.enable-aac", "true"), | |
| ("media.stagefright.enable-scan", "true"), | |
| ("media.stagefright.enable-meta", "true"), | |
| # HW priority | |
| ("media.acodec.preferhw", "true"), | |
| ("media.vcodec.preferhw", "true"), | |
| ("media.codec.sw.fallback", "false"), | |
| ("media.codec.priority", "1"), | |
| # C2 / OMX rank (new in v12) | |
| # ccodec=1 enables C2 framework, omx_default_rank=0 keeps BCM OMX as primary | |
| ("debug.stagefright.ccodec", "1"), | |
| ("debug.stagefright.omx_default_rank", "0"), | |
| # DRM | |
| ("drm.service.enabled", "true"), | |
| # A/V sync hint (BCM-specific) | |
| ("persist.sys.media.avsync", "true"), | |
| ("persist.sys.hdmi.keep_awake", "true"), | |
| ] | |
| for key, val in codec_props: | |
| ADB.setprop(key, val) | |
| Logger.log(f" ✓ {key} = {val}") | |
| # Media cache: 64KB/128KB/30s format (correct for stagefright) | |
| ADB.setprop("media.stagefright.cache-params", "65536/131072/30") | |
| Logger.log(" ✓ cache-params: 64KB/128KB/30s") | |
| # Extended HLS buffer | |
| abr_props = [ | |
| ("media.httplive.max-bitrate", "25000000"), | |
| ("media.httplive.initial-bitrate", "8000000"), | |
| ("media.httplive.max-live-offset", "60"), | |
| ("media.httplive.bw-update-interval", "1000"), | |
| ] | |
| for key, val in abr_props: | |
| ADB.setprop(key, val) | |
| Logger.log(f" ✓ {key} = {val}") | |
| Logger.success("Codec pipeline configured — BCM7362 VPU HW path active") | |
| def optimize_rendering(self) -> None: | |
| Logger.header("🎮 RENDERING ENGINE — VideoCore + Skia (v12 tuning)") | |
| render_props = [ | |
| # Renderer: opengl only — no Vulkan on VideoCore | |
| ("debug.hwui.renderer", "skiagl"), | |
| # Threaded Skia backend — uses both A15 cores for render pipeline | |
| ("debug.renderengine.backend", "skiaglthreaded"), | |
| # HW composition | |
| ("debug.egl.hw", "1"), | |
| ("debug.sf.hw", "1"), | |
| # Triple buffer | |
| ("debug.gr.numframebuffers", "3"), | |
| # GPU pixel buffers (reduces CPU→GPU copy overhead) | |
| ("debug.hwui.use_gpu_pixel_buffers", "true"), | |
| # Disable dirty region calculation (saves CPU per frame) | |
| ("debug.hwui.render_dirty_regions", "false"), | |
| # Latch unsignaled: reduce SurfaceFlinger → compositor latency | |
| ("debug.sf.latch_unsignaled", "1"), | |
| # Disable SF back-pressure on VPU tunnel output | |
| ("debug.sf.disable_backpressure", "1"), | |
| # Buffer age: not useful on VideoCore | |
| ("debug.hwui.use_buffer_age", "false"), | |
| # Force GPU for UI window compositing | |
| ("persist.sys.ui.hw", "true"), | |
| ] | |
| for key, val in render_props: | |
| ADB.setprop(key, val) | |
| Logger.log(f" ✓ {key} = {val}") | |
| ADB.setting_put("global", "force_gpu_rendering", "true") | |
| Logger.log(" ✓ force_gpu_rendering = true") | |
| Logger.success("Rendering engine: skiaglthreaded + VideoCore GPU optimized") | |
| def force_tunnel_mode(self) -> None: | |
| Logger.header("🚇 TUNNEL MODE — VPU → Display Controller Direct Path") | |
| tunnel_props = [ | |
| ("media.tunneled-playback.enable", "true"), | |
| ("media.brcm.tunnel.sessions", "1"), | |
| ("media.brcm.hdmi.tunnel", "true"), | |
| # Lock A/V to HDMI sink clock — eliminates drift | |
| ("media.brcm.tunnel.clock", "hdmi"), | |
| ] | |
| for key, val in tunnel_props: | |
| ADB.setprop(key, val) | |
| Logger.log(f" ✓ {key} = {val}") | |
| Logger.success("Tunnel Mode: VPU→Display direct path (bypass SurfaceFlinger)") | |
| def suppress_av1(self) -> None: | |
| Logger.header("🚫 AV1 SUPPRESSION — Force VP9/H.264 HW Path") | |
| Logger.warning("BCM7362: NO AV1 HW decoder — SW decode causes 100% CPU") | |
| for key, val in [ | |
| ("debug.stagefright.c2.av1", "0"), | |
| ("media.av1.sw.decode.disable", "true"), | |
| ("media.codec.av1.disable", "true"), | |
| ]: | |
| ADB.setprop(key, val) | |
| Logger.log(f" ✓ {key} = {val}") | |
| Logger.success("AV1 SW decoder blocked — ExoPlayer will negotiate VP9 HW") | |
| c = Logger.C | |
| print(f""" | |
| {c['cyan']}┌──────────────────────────────────────────────────────────┐ | |
| │ SmartTube Settings (manual, one-time after launch): │ | |
| │ Settings → Player → Video codec → VP9 │ | |
| │ Settings → Player → Resolution → 2160p (4K) │ | |
| │ Settings → Player → Use tunnel mode → ON │ | |
| └──────────────────────────────────────────────────────────┘{c['reset']} | |
| """) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # MEMORY + LMK + VM OPTIMIZER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class MemoryOptimizer: | |
| """ | |
| Full memory stack tuning. | |
| v12 additions: | |
| + /sys/module/lowmemorykiller/parameters/minfree (kernel LMK levels) | |
| + /proc/sys/vm/swappiness = 10 (prefer RAM, no thrashing on eMMC) | |
| + /proc/sys/vm/vfs_cache_pressure = 50 (keep dentry/inode cache longer) | |
| + dalvik.vm.heapstartsize = 16m (faster cold start) | |
| + dalvik.vm.usejitprofiles = true | |
| """ | |
| def apply_dalvik_heap(self) -> None: | |
| Logger.header("🧠 DALVIK/ART HEAP — SmartTube 4K Streaming Profile") | |
| heap_props = [ | |
| ("dalvik.vm.heapsize", DeviceSpec.DALVIK_HEAP_SIZE), | |
| ("dalvik.vm.heapgrowthlimit", DeviceSpec.DALVIK_HEAP_GROWTH), | |
| ("dalvik.vm.heapstartsize", DeviceSpec.DALVIK_HEAP_START), | |
| ("dalvik.vm.heapminfree", DeviceSpec.DALVIK_HEAP_MIN_FREE), | |
| ("dalvik.vm.heapmaxfree", DeviceSpec.DALVIK_HEAP_MAX_FREE), | |
| ("dalvik.vm.heaptargetutilization", DeviceSpec.DALVIK_HEAP_TARGET), | |
| ("dalvik.vm.usejit", "true"), | |
| ("dalvik.vm.usejitprofiles", "true"), # new v12 | |
| ("dalvik.vm.dex2oat-filter", "speed-profile"), | |
| ("dalvik.vm.gctype", "CMS"), | |
| ("persist.sys.dalvik.vm.lib.2", "libart.so"), | |
| ] | |
| for key, val in heap_props: | |
| ADB.setprop(key, val) | |
| Logger.log(f" ✓ {key} = {val}") | |
| Logger.success("Dalvik heap configured for SmartTube 4K workload") | |
| def apply_lmk_profile(self) -> None: | |
| Logger.header("🧹 LMK — SmartTube + Cast Priority Profile") | |
| # Android props | |
| lmk_props = [ | |
| ("ro.lmk.critical", "0"), | |
| ("ro.lmk.kill_heaviest_task", "true"), | |
| ("ro.lmk.downgrade_pressure", "100"), | |
| ("ro.lmk.upgrade_pressure", "50"), | |
| ("ro.lmk.use_psi", "true"), | |
| ("ro.lmk.filecache_min_kb", "51200"), | |
| ] | |
| for key, val in lmk_props: | |
| ADB.setprop(key, val) | |
| Logger.log(f" ✓ {key} = {val}") | |
| # Kernel LMK minfree (pages, 4KB each) | |
| # Levels: 48MB,64MB,80MB,96MB,112MB,128MB | |
| minfree = "12288,16384,20480,24576,28672,32768" | |
| wrote = ADB.syswrite("/sys/module/lowmemorykiller/parameters/minfree", minfree) | |
| if wrote: | |
| Logger.success(f"Kernel LMK minfree: {minfree}") | |
| else: | |
| Logger.warning("LMK minfree: /sys not writable (user build) — props applied only") | |
| ADB.setting_put("global", "background_process_limit", "3") | |
| Logger.log(" ✓ background_process_limit = 3") | |
| Logger.success("LMK profile active: SmartTube + Cast protected from OOM") | |
| def apply_vm_tuning(self) -> None: | |
| Logger.header("⚡ VM TUNING — Swappiness + VFS Cache Pressure") | |
| vm_params = [ | |
| ("/proc/sys/vm/swappiness", "10"), # prefer RAM, minimal swap | |
| ("/proc/sys/vm/vfs_cache_pressure", "50"), # keep dentry/inode longer | |
| ("/proc/sys/vm/dirty_ratio", "10"), # writeback threshold | |
| ("/proc/sys/vm/dirty_background_ratio", "5"), | |
| ] | |
| for path, val in vm_params: | |
| wrote = ADB.syswrite(path, val) | |
| icon = "✓" if wrote else "⚠" | |
| Logger.log(f" {icon} {path.split('/')[-1]} = {val}") | |
| Logger.success("VM params applied") | |
| def deep_clean(self) -> None: | |
| Logger.header("🔄 DEEP CLEAN — Cast-Safe RAM Release") | |
| Logger.log(" am kill-all (releases background apps)...") | |
| ADB.run("am kill-all", silent=True) | |
| Logger.log(" pm trim-caches 2G...") | |
| ADB.run("pm trim-caches 2G", silent=True) | |
| ADB.run("dumpsys batterystats --reset", silent=True) | |
| Logger.log(" drop_caches (requires root)...") | |
| ADB.run_root("sync && echo 3 > /proc/sys/vm/drop_caches") | |
| Logger.shield("Re-verifying Cast services post-clean...") | |
| ChromecastServiceManager.restore() | |
| Logger.success("Deep clean complete — Cast services verified") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # NETWORK OPTIMIZER (v12 — full TCP kernel stack) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class NetworkOptimizer: | |
| """ | |
| Full TCP/IP stack tuning. | |
| v12 additions: | |
| + /proc/sys/net/ipv4/tcp_window_scaling, tcp_timestamps, tcp_sack (kernel) | |
| + BBR congestion control attempt (may not be available on Android 9 STB) | |
| + /proc/sys/net/core/rmem_max = 16MB | |
| + wifi.supplicant_scan_interval = 300 (reduce AP scan interference) | |
| + persist.debug.wfd.enable = 1 (WiFi Direct for Cast fallback) | |
| """ | |
| def apply_tcp(self) -> None: | |
| Logger.header("🌐 NETWORK — TCP/IP Stack + Kernel Params (v12)") | |
| Logger.shield( | |
| "mDNS multicast (Cast discovery port 5353) unaffected by TCP tuning." | |
| ) | |
| # Android-level buffers | |
| ADB.setting_put("global", "net.tcp.buffersize.wifi", | |
| "262144,1048576,2097152,131072,524288,1048576") | |
| Logger.log(" ✓ WiFi TCP buffers: 256KB / 1MB / 2MB") | |
| ADB.setting_put("global", "net.tcp.buffersize.ethernet", | |
| "524288,2097152,4194304,262144,1048576,2097152") | |
| Logger.log(" ✓ Ethernet TCP buffers: 512KB / 2MB / 4MB") | |
| ADB.setting_put("global", "tcp_default_init_rwnd", "120") | |
| Logger.log(" ✓ TCP init rwnd = 120 segments") | |
| # Kernel-level TCP params (via root) | |
| kernel_tcp = [ | |
| ("/proc/sys/net/ipv4/tcp_window_scaling", "1"), | |
| ("/proc/sys/net/ipv4/tcp_timestamps", "1"), | |
| ("/proc/sys/net/ipv4/tcp_sack", "1"), | |
| ("/proc/sys/net/ipv4/tcp_keepalive_intvl", "30"), | |
| ("/proc/sys/net/ipv4/tcp_keepalive_probes", "3"), | |
| ("/proc/sys/net/ipv4/tcp_no_metrics_save", "1"), | |
| ("/proc/sys/net/ipv4/tcp_fastopen", "3"), | |
| ] | |
| for path, val in kernel_tcp: | |
| wrote = ADB.syswrite(path, val) | |
| icon = "✓" if wrote else "⚠" | |
| Logger.log(f" {icon} {path.split('/')[-1]} = {val}") | |
| # Core net buffers (16MB) | |
| for path in ("/proc/sys/net/core/rmem_max", "/proc/sys/net/core/wmem_max"): | |
| ADB.syswrite(path, "16777216") | |
| Logger.log(" ✓ Net core rmem/wmem_max = 16MB") | |
| # BBR congestion control (best-effort — may not be available) | |
| bbr_path = "/proc/sys/net/ipv4/tcp_congestion_control" | |
| bbr_result = ADB.run_root(f"echo bbr > {bbr_path}") | |
| verify = ADB.run_root(f"cat {bbr_path}").strip() | |
| if "bbr" in verify: | |
| Logger.success(" TCP congestion control: BBR") | |
| else: | |
| Logger.log(f" ⚠ BBR unavailable on this kernel ({verify}) — using CUBIC") | |
| # WiFi stability | |
| ADB.setprop("wifi.supplicant_scan_interval", "300") | |
| Logger.log(" ✓ wifi.supplicant_scan_interval = 300 (reduce interference)") | |
| ADB.setting_put("global", "wifi_sleep_policy", "2") | |
| Logger.log(" ✓ wifi_sleep_policy = 2 (stay connected, Cast-safe)") | |
| # WiFi Direct (Cast fallback transport) | |
| ADB.setprop("persist.debug.wfd.enable", "1") | |
| Logger.log(" ✓ WiFi Direct enabled (Cast fallback transport)") | |
| Logger.success("TCP/IP stack optimized for 4K streaming") | |
| def apply_dns(self, provider: str = "cloudflare") -> None: | |
| DNSManager.set_provider(provider) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # HDMI + CEC OPTIMIZER (v12 — BCM Nexus props added) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class HDMIOptimizer: | |
| """ | |
| v12 additions vs v11: | |
| + persist.sys.cec.status = true (BCM CEC HAL enable) | |
| + persist.sys.hdmi.tx_standby_cec = 1 (CEC standby command) | |
| + persist.sys.hdmi.tx_view_on_cec = 1 (CEC power-on command) | |
| + persist.nx.hdmi.tx_standby_cec = 1 (BCM Nexus CEC) | |
| + persist.nx.hdmi.tx_view_on_cec = 1 (BCM Nexus CEC) | |
| + persist.sys.hdmi.addr.playback = 11 (CORRECTED: BCM says 11, not 0) | |
| + persist.nx.vidout.50hz awareness (PAL region) | |
| """ | |
| def apply(self) -> None: | |
| Logger.header("📺 HDMI + CEC — BCM7362 Nexus Full Configuration") | |
| current_cec = ADB.prop("persist.sys.cec.status") | |
| Logger.log(f" Current CEC status: {current_cec}") | |
| hdmi_props = [ | |
| # Device type: 4 = playback device (set-top box) | |
| ("ro.hdmi.device_type", "4"), | |
| # CORRECTED from v11: BCM Nexus addr.playback = 11 (from pbap9.sh verified) | |
| ("persist.sys.hdmi.addr.playback", "11"), | |
| # CEC enable | |
| ("persist.sys.cec.status", "true"), | |
| ("persist.sys.hdmi.tx_standby_cec", "1"), | |
| ("persist.sys.hdmi.tx_view_on_cec", "1"), | |
| ("persist.sys.hdmi.cec_enabled", "1"), | |
| # BCM Nexus CEC (persist.nx.* namespace = BCM hardware layer) | |
| ("persist.nx.hdmi.tx_standby_cec", "1"), | |
| ("persist.nx.hdmi.tx_view_on_cec", "1"), | |
| # Keep HDMI link alive during buffering | |
| ("persist.sys.hdmi.keep_awake", "true"), | |
| # HDR10 | |
| ("persist.sys.hdr.enable", "1"), | |
| # No spurious HDMI resets on hotplug | |
| ("ro.hdmi.wake_on_hotplug", "false"), | |
| # ARC / passthrough | |
| ("settings put global", "hdmi_cec_enabled", "1"), | |
| ] | |
| for entry in hdmi_props: | |
| if len(entry) == 2: | |
| ADB.setprop(entry[0], entry[1]) | |
| Logger.log(f" ✓ {entry[0]} = {entry[1]}") | |
| else: | |
| # settings put | |
| ADB.run(f"settings put {entry[0].split()[2]} {entry[1]} {entry[2]}", silent=True) | |
| Logger.log(f" ✓ {entry[1]} = {entry[2]}") | |
| # PAL 50Hz check | |
| hz50 = ADB.prop("persist.nx.vidout.50hz") | |
| if hz50 == "0": | |
| Logger.log(" ℹ 50Hz disabled — enable if PAL content stutters") | |
| else: | |
| Logger.log(f" ✓ 50Hz mode: {hz50}") | |
| Logger.success("HDMI + CEC fully configured with BCM Nexus props") | |
| def fix_audio_desync(self) -> None: | |
| Logger.header("🔊 AUDIO — A/V Sync Fix (HDMI ARC desync elimination)") | |
| audio_props = [ | |
| ("audio.offload.disable", "1"), # offload causes desync on BCM7362 HDMI | |
| ("audio.offload.video", "0"), | |
| ("tunnel.audio.encode", "false"), | |
| ("audio.deep_buffer.media", "true"), # stable latency baseline | |
| ("af.fast_track_multiplier", "1"), | |
| ("audio.brcm.hdmi.clock_lock", "true"), # lock to HDMI sink clock | |
| ("audio.brcm.hal.latency", "20"), | |
| ] | |
| for key, val in audio_props: | |
| ADB.setprop(key, val) | |
| Logger.log(f" ✓ {key} = {val}") | |
| Logger.success("Audio A/V sync fixed — HDMI clock locked") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SYSTEM RESPONSIVENESS (v12 — I/O scheduler, read-ahead) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ResponsivenessOptimizer: | |
| """ | |
| v12 additions: | |
| + I/O scheduler = deadline (low-latency for eMMC sequential reads) | |
| + read_ahead_kb = 512 (prefetch for VP9 segment files) | |
| + CPU governor = performance on both A15 cores | |
| """ | |
| def apply(self, anim_scale: float = 0.5) -> None: | |
| Logger.header(f"🎨 SYSTEM RESPONSIVENESS — Animations + I/O + CPU") | |
| # Animations | |
| for key in ["window_animation_scale", "transition_animation_scale", "animator_duration_scale"]: | |
| ADB.setting_put("global", key, str(anim_scale)) | |
| Logger.log(f" ✓ {key} = {anim_scale}x") | |
| # TV recommendations off | |
| for cmd in [ | |
| "settings put secure tv_disable_recommendations 1", | |
| "settings put secure tv_enable_preview_programs 0", | |
| "settings put secure tv_watch_next_enabled 0", | |
| ]: | |
| ADB.run(cmd, silent=True) | |
| Logger.log(" ✓ TV recommendations disabled") | |
| # Logger reduction (reduce logd overhead) | |
| ADB.setprop("persist.logd.size", "32768") | |
| ADB.setprop("log.tag.stats_log", "OFF") | |
| ADB.setprop("log.tag.statsd", "OFF") | |
| Logger.log(" ✓ Logging reduced to 32KB") | |
| # I/O scheduler: deadline (low latency for streaming segment reads) | |
| io_result = ADB.run_root( | |
| "for d in /sys/block/*/queue/scheduler; do " | |
| "echo deadline > $d 2>/dev/null; done" | |
| ) | |
| Logger.log(" ✓ I/O scheduler = deadline (all block devices)") | |
| # read-ahead: 512KB (VP9 segment prefetch) | |
| ADB.run_root( | |
| "for d in /sys/block/*/queue/read_ahead_kb; do " | |
| "echo 512 > $d 2>/dev/null; done" | |
| ) | |
| Logger.log(" ✓ read_ahead_kb = 512") | |
| # CPU governor: performance on both A15 cores | |
| for cpu in range(2): | |
| path = f"/sys/devices/system/cpu/cpu{cpu}/cpufreq/scaling_governor" | |
| ADB.run_root(f"echo performance > {path}") | |
| Logger.log(f" ✓ cpu{cpu}: performance governor") | |
| Logger.success("System responsiveness fully optimized") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SAFE DEBLOATER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| SAFE_DEBLOAT_DB: List[Tuple[str, str]] = [ | |
| ("com.google.android.backdrop", "Ambient screensaver — idle GPU waste"), | |
| ("com.google.android.tvrecommendations", "Content recommendations — constant HTTP polling"), | |
| ("com.google.android.katniss", "Google TV voice overlay — high idle CPU"), | |
| ("com.google.android.tungsten.setupwraith", "Setup wizard — post-setup dead weight"), | |
| ("com.google.android.apps.tv.launcherx", "New Google TV launcher (unused on API 28)"), | |
| ("com.google.android.leanbacklauncher", "Legacy Leanback duplicate"), | |
| ("com.google.android.marvin.talkback", "Accessibility TTS — 40MB unused"), | |
| ("com.google.android.onetimeinitializer","One-time init — already completed"), | |
| ("com.google.android.feedback", "Google feedback service — periodic ping"), | |
| ("com.google.android.speech.pumpkin", "Hotword detection — high background CPU"), | |
| ("com.android.printspooler", "Print service — no printers on TV"), | |
| ("com.android.dreams.basic", "Basic screensaver"), | |
| ("com.android.dreams.phototable", "Photo screensaver"), | |
| ("com.android.providers.calendar", "Calendar provider — unused on TV"), | |
| ("com.android.providers.contacts", "Contacts provider — unused on TV"), | |
| ("com.sagemcom.stb.setupwizard", "Sagemcom factory setup wizard"), | |
| ("com.orange.fr.tv.assistance", "Orange assistance — server polling"), | |
| ("com.orange.fr.tv.guide", "Orange TV guide — heavy background sync"), | |
| ("com.amazon.amazonvideo.livingroom", "Amazon Prime — pre-bundled, use standalone"), | |
| ("com.google.android.play.games", "Play Games — unused on TV"), | |
| ("com.google.android.videos", "Play Movies — unused on TV"), | |
| ("com.google.android.partnersetup", "Partner setup — post-setup unused"), | |
| ] | |
| class SafeDebloater: | |
| def run(self) -> None: | |
| Logger.header("🗑 SAFE DEBLOAT — Chromecast Protection Enforced") | |
| disabled, protected, already_off, failed = 0, 0, 0, 0 | |
| for pkg, reason in SAFE_DEBLOAT_DB: | |
| if ChromecastProtection.is_protected(pkg): | |
| protected += 1 | |
| Logger.shield(f"PROTECTED: {pkg}") | |
| Logger.log(f" └─ {ChromecastProtection.get_reason(pkg)}", "cyan") | |
| continue | |
| if not ADB.pkg_enabled(pkg): | |
| already_off += 1 | |
| continue | |
| result = ADB.run(f"pm disable-user --user 0 {pkg}", silent=True) | |
| if "disabled" in result.lower() or result == "": | |
| disabled += 1 | |
| Logger.success(f"Disabled: {pkg}") | |
| Logger.log(f" └─ {reason}", "dim") | |
| else: | |
| failed += 1 | |
| Logger.warning(f"Could not disable: {pkg}") | |
| Logger.header( | |
| f"DEBLOAT: {disabled} disabled | {protected} protected | " | |
| f"{already_off} already off | {failed} failed" | |
| ) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # CHROMECAST SERVICE MANAGER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ChromecastServiceManager: | |
| """ | |
| Audit, restore, harden Cast services. | |
| v12: added detection of debloat.sh damage pattern | |
| (apps.mediashell disabled by erroneous debloat script on device). | |
| """ | |
| @staticmethod | |
| def audit() -> Dict[str, bool]: | |
| Logger.header("🔍 CHROMECAST SERVICE AUDIT") | |
| results: Dict[str, bool] = {} | |
| for pkg, reason in ChromecastProtection.PROTECTED.items(): | |
| ok = ADB.pkg_enabled(pkg) | |
| results[pkg] = ok | |
| icon = "✓" if ok else "✗" | |
| Logger.log(f" {icon} {pkg}", "success" if ok else "error") | |
| Logger.log(f" └─ {reason}", "cyan") | |
| broken = [p for p, e in results.items() if not e] | |
| if broken: | |
| Logger.warning(f"{len(broken)} Cast service(s) DISABLED — Auto-repair available (option 7)") | |
| else: | |
| Logger.success("All Chromecast services healthy ✓") | |
| return results | |
| @staticmethod | |
| def restore() -> None: | |
| Logger.header("🛡 CHROMECAST RESTORATION") | |
| for pkg in ChromecastProtection.PROTECTED: | |
| ADB.run(f"pm enable {pkg}", silent=True) | |
| ADB.run(f"pm enable --user 0 {pkg}", silent=True) | |
| Logger.shield(f"Ensured: {pkg}") | |
| Logger.success("All Cast services re-enabled") | |
| @staticmethod | |
| def optimize_network() -> None: | |
| Logger.header("📡 CAST NETWORK — mDNS/Multicast Reliability") | |
| ADB.setting_put("global", "wifi_sleep_policy", "2") | |
| ADB.setting_put("global", "wifi_power_save", "0") | |
| ADB.setprop("ro.mdns.enable_passive_mode", "false") | |
| ADB.setprop("net.ssdp.ttl", "4") | |
| Logger.success("Cast mDNS/multicast network profile active") | |
| @staticmethod | |
| def harden_oom() -> None: | |
| Logger.header("🎯 CAST OOM HARDENING") | |
| for pkg in [ | |
| "com.google.android.apps.mediashell", | |
| "com.google.android.gms", | |
| "com.google.android.nearby", | |
| ]: | |
| pid = ADB.run(f"pidof {pkg}", silent=True).strip() | |
| if pid and pid.isdigit(): | |
| ADB.run_root(f"echo 100 > /proc/{pid}/oom_score_adj") | |
| Logger.shield(f"OOM adj=100: {pkg} (PID {pid})") | |
| else: | |
| Logger.log(f" {pkg} not running yet — protected at next start", "warning") | |
| Logger.success("Cast processes hardened") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # AOT COMPILER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class AOTCompiler: | |
| APPS: Dict[str, str] = { | |
| DeviceSpec.SMARTTUBE_PKG_STABLE: "SmartTube Stable", | |
| DeviceSpec.PROJECTIVY_PKG: "Projectivy Launcher", | |
| "com.google.android.apps.mediashell": "Cast Receiver", | |
| } | |
| @classmethod | |
| def compile_all(cls) -> None: | |
| Logger.header("⚡ AOT COMPILATION — Eliminate JIT Bursts") | |
| for pkg, name in cls.APPS.items(): | |
| if not ADB.pkg_installed(pkg): | |
| Logger.log(f" ○ {name} not installed — skip", "dim") | |
| continue | |
| Logger.log(f" Compiling {name} (speed-profile)... ~60–90s") | |
| result = ADB.run(f"cmd package compile -m speed-profile -f {pkg}", silent=True) | |
| if "success" in result.lower(): | |
| Logger.success(f" {name}: AOT compiled (speed-profile)") | |
| else: | |
| # fallback | |
| ADB.run(f"cmd package compile -m speed -f {pkg}", silent=True) | |
| Logger.success(f" {name}: AOT compiled (speed)") | |
| # ═════════════════════════════════════════════════════════════════════════════ | |
| # SELF-DIAGNOSTIC ENGINE | |
| # ═════════════════════════════════════════════════════════════════════════════ | |
| class DiagnosticEngine: | |
| """ | |
| Interactive self-diagnostic system that: | |
| 1. Checks 8 service categories comprehensively | |
| 2. Presents results category-by-category with interactive drill-down | |
| 3. Identifies broken/missing/incorrect settings | |
| 4. Offers targeted auto-repair per finding | |
| 5. Downloads and reinstalls APKs when needed | |
| Categories: | |
| A. System Health (RAM, CPU, Thermal, Storage) | |
| B. Cast Services (all 10 protected packages) | |
| C. SmartTube (install, version, codec settings, AOT) | |
| D. Video Pipeline (codec props, tunnel, renderer) | |
| E. Network/DNS (TCP params, DNS correctness) | |
| F. Audio (desync props, HDMI clock) | |
| G. Memory/LMK (LMK props, dalvik heap, kernel VM) | |
| H. HDMI/CEC (CEC props, BCM Nexus, 50Hz) | |
| """ | |
| def __init__(self): | |
| self.results: List[DiagResult] = [] | |
| def _r(self, cat: str, check: str, status: Status, found: str, | |
| expected: str = "", fix_fn: Optional[Callable] = None, | |
| detail: str = "") -> DiagResult: | |
| dr = DiagResult(cat, check, status, found, expected, fix_fn, detail) | |
| self.results.append(dr) | |
| return dr | |
| # ─────────────────── CATEGORY A: SYSTEM HEALTH ────────────────────────── | |
| def _check_system_health(self) -> List[DiagResult]: | |
| results = [] | |
| cat = "SYSTEM" | |
| meminfo = ADB.run("cat /proc/meminfo", silent=True) | |
| fields: Dict[str, int] = {} | |
| for line in meminfo.splitlines(): | |
| p = line.split() | |
| if len(p) >= 2 and p[1].isdigit(): | |
| fields[p[0].rstrip(":")] = int(p[1]) | |
| avail_mb = fields.get("MemAvailable", 0) // 1024 | |
| total_mb = fields.get("MemTotal", 0) // 1024 | |
| pct = avail_mb / total_mb * 100 if total_mb else 0 | |
| if pct > 30: | |
| s = Status.OK | |
| elif pct > 15: | |
| s = Status.WARN | |
| else: | |
| s = Status.BROKEN | |
| r = self._r(cat, "RAM Available", s, f"{avail_mb} MB ({pct:.0f}%)", | |
| ">30% = OK", lambda: MemoryOptimizer().deep_clean(), | |
| f"Total: {total_mb} MB") | |
| results.append(r) | |
| # Thermal | |
| for zone in range(2): | |
| raw = ADB.run(f"cat /sys/class/thermal/thermal_zone{zone}/temp", silent=True) | |
| if raw and raw.lstrip("-").isdigit(): | |
| temp_c = int(raw) / 1000 | |
| s = Status.OK if temp_c < 60 else (Status.WARN if temp_c < 75 else Status.BROKEN) | |
| r = self._r(cat, f"Thermal zone{zone}", s, f"{temp_c:.1f}°C", "<60°C") | |
| results.append(r) | |
| # Storage | |
| storage = ADB.run("df -h /data", silent=True) | |
| lines = storage.splitlines() | |
| if len(lines) > 1: | |
| parts = lines[1].split() | |
| use_pct_str = parts[4] if len(parts) > 4 else "?" | |
| use_pct = int(use_pct_str.replace("%", "")) if use_pct_str != "?" else 0 | |
| s = Status.OK if use_pct < 80 else (Status.WARN if use_pct < 90 else Status.BROKEN) | |
| r = self._r(cat, "/data storage", s, use_pct_str, "<80%") | |
| results.append(r) | |
| # Internet | |
| ping = ADB.run("ping -c 2 -W 3 1.1.1.1", silent=True) | |
| ok = "2 received" in ping | |
| r = self._r(cat, "Internet connectivity", Status.OK if ok else Status.BROKEN, | |
| "Reachable" if ok else "OFFLINE") | |
| results.append(r) | |
| return results | |
| # ─────────────────── CATEGORY B: CAST SERVICES ────────────────────────── | |
| def _check_cast(self) -> List[DiagResult]: | |
| results = [] | |
| cat = "CAST" | |
| for pkg, reason in ChromecastProtection.PROTECTED.items(): | |
| enabled = ADB.pkg_enabled(pkg) | |
| s = Status.OK if enabled else Status.BROKEN | |
| r = self._r(cat, pkg.split(".")[-1], s, | |
| "enabled" if enabled else "DISABLED", | |
| "enabled", ChromecastServiceManager.restore, | |
| reason) | |
| results.append(r) | |
| return results | |
| # ─────────────────── CATEGORY C: SMARTTUBE ────────────────────────────── | |
| def _check_smarttube(self) -> List[DiagResult]: | |
| results = [] | |
| cat = "SMARTTUBE" | |
| # Package detection (check all known names) | |
| found_pkg = None | |
| for pkg in [DeviceSpec.SMARTTUBE_PKG_STABLE, DeviceSpec.SMARTTUBE_PKG_BETA, | |
| DeviceSpec.SMARTTUBE_PKG_LEGACY]: | |
| if ADB.pkg_installed(pkg): | |
| found_pkg = pkg | |
| break | |
| if found_pkg: | |
| ver = ADB.pkg_version(found_pkg) | |
| r = self._r(cat, "SmartTube installed", Status.OK, f"{found_pkg} v{ver}", "installed") | |
| results.append(r) | |
| # AOT compilation status | |
| aot_check = ADB.run( | |
| f"cmd package dump {found_pkg} | grep -i compile_reason", silent=True | |
| ) | |
| aot_ok = "install" in aot_check.lower() or "bg-dexopt" in aot_check.lower() | |
| r = self._r(cat, "AOT compilation", Status.OK if aot_ok else Status.WARN, | |
| aot_check[:50] if aot_check else "unknown", | |
| "compiled", AOTCompiler.compile_all) | |
| results.append(r) | |
| else: | |
| r = self._r( | |
| cat, "SmartTube installed", Status.MISSING, | |
| "NOT INSTALLED", "org.smarttube.stable", | |
| lambda: APKDownloader.download_and_install( | |
| DeviceSpec.SMARTTUBE_STABLE_URL, | |
| DeviceSpec.SMARTTUBE_PKG_STABLE, | |
| "SmartTube Stable" | |
| ) | |
| ) | |
| results.append(r) | |
| # Codec props | |
| for prop, expected in [ | |
| ("media.vcodec.preferhw", "true"), | |
| ("debug.stagefright.ccodec", "1"), | |
| ("media.tunneled-playback.enable", "true"), | |
| ("media.codec.av1.disable", "true"), | |
| ]: | |
| val = ADB.prop(prop) | |
| ok = val == expected | |
| r = self._r(cat, f"prop:{prop.split('.')[-1]}", Status.OK if ok else Status.BROKEN, | |
| val or "not set", expected, | |
| VideoEngineOptimizer().optimize_codec_pipeline) | |
| results.append(r) | |
| return results | |
| # ─────────────────── CATEGORY D: VIDEO PIPELINE ───────────────────────── | |
| def _check_video(self) -> List[DiagResult]: | |
| results = [] | |
| cat = "VIDEO" | |
| checks = [ | |
| ("debug.hwui.renderer", "skiagl"), | |
| ("debug.renderengine.backend", "skiaglthreaded"), | |
| ("debug.sf.hw", "1"), | |
| ("debug.gr.numframebuffers", "3"), | |
| ("media.acodec.preferhw", "true"), | |
| ("persist.sys.ui.hw", "true"), | |
| ("debug.sf.latch_unsignaled", "1"), | |
| ] | |
| vo = VideoEngineOptimizer() | |
| for prop, expected in checks: | |
| val = ADB.prop(prop) | |
| ok = val == expected | |
| r = self._r(cat, prop.split(".")[-1], Status.OK if ok else Status.BROKEN, | |
| val or "not set", expected, vo.optimize_rendering) | |
| results.append(r) | |
| return results | |
| # ─────────────────── CATEGORY E: NETWORK / DNS ────────────────────────── | |
| def _check_network(self) -> List[DiagResult]: | |
| results = [] | |
| cat = "NETWORK" | |
| # DNS correctness check | |
| dot_host = ADB.setting_get("global", "private_dns_specifier") | |
| dot_mode = ADB.setting_get("global", "private_dns_mode") | |
| ip1 = ADB.prop("net.dns1") | |
| valid_dots = [v[0] for v in DeviceSpec.DNS_PROVIDERS.values()] | |
| dns_ok = dot_host in valid_dots and dot_mode == "hostname" | |
| r = self._r(cat, "Private DNS (DoT)", Status.OK if dns_ok else Status.BROKEN, | |
| f"mode={dot_mode}, host={dot_host}", | |
| "hostname + one.one.one.one", | |
| lambda: DNSManager.set_provider("cloudflare"), | |
| f"Legacy DNS (net.dns1): {ip1}") | |
| results.append(r) | |
| # Old (incorrect) Cloudflare hostname detection | |
| if dot_host == "dns.cloudflare.com": | |
| r = self._r(cat, "DNS hostname correctness", Status.BROKEN, | |
| "dns.cloudflare.com (WRONG)", | |
| "one.one.one.one", | |
| lambda: DNSManager.set_provider("cloudflare"), | |
| "Legacy hostname — will cause DoT handshake failure") | |
| results.append(r) | |
| # TCP init window | |
| rwnd = ADB.setting_get("global", "tcp_default_init_rwnd") | |
| r = self._r(cat, "TCP init rwnd", Status.OK if rwnd == "120" else Status.WARN, | |
| rwnd or "not set", "120", | |
| NetworkOptimizer().apply_tcp) | |
| results.append(r) | |
| # WiFi sleep policy | |
| sleep = ADB.setting_get("global", "wifi_sleep_policy") | |
| r = self._r(cat, "WiFi sleep policy", Status.OK if sleep == "2" else Status.WARN, | |
| sleep or "not set", "2 (always on)") | |
| results.append(r) | |
| return results | |
| # ─────────────────── CATEGORY F: AUDIO ────────────────────────────────── | |
| def _check_audio(self) -> List[DiagResult]: | |
| results = [] | |
| cat = "AUDIO" | |
| ha = HDMIOptimizer() | |
| for prop, expected in [ | |
| ("audio.offload.disable", "1"), | |
| ("audio.deep_buffer.media", "true"), | |
| ("audio.brcm.hdmi.clock_lock", "true"), | |
| ("tunnel.audio.encode", "false"), | |
| ]: | |
| val = ADB.prop(prop) | |
| ok = val == expected | |
| r = self._r(cat, prop.split(".")[-1], Status.OK if ok else Status.BROKEN, | |
| val or "not set", expected, ha.fix_audio_desync) | |
| results.append(r) | |
| return results | |
| # ─────────────────── CATEGORY G: MEMORY ───────────────────────────────── | |
| def _check_memory(self) -> List[DiagResult]: | |
| results = [] | |
| cat = "MEMORY" | |
| mo = MemoryOptimizer() | |
| for prop, expected in [ | |
| ("dalvik.vm.heapsize", DeviceSpec.DALVIK_HEAP_SIZE), | |
| ("dalvik.vm.heapgrowthlimit", DeviceSpec.DALVIK_HEAP_GROWTH), | |
| ("dalvik.vm.usejit", "true"), | |
| ("ro.lmk.kill_heaviest_task", "true"), | |
| ("ro.lmk.critical", "0"), | |
| ]: | |
| val = ADB.prop(prop) | |
| ok = val == expected | |
| r = self._r(cat, prop.split(".")[-1], Status.OK if ok else Status.BROKEN, | |
| val or "not set", expected, mo.apply_lmk_profile) | |
| results.append(r) | |
| # Swappiness | |
| swap = ADB.run_root("cat /proc/sys/vm/swappiness").strip() | |
| r = self._r(cat, "vm.swappiness", Status.OK if swap == "10" else Status.WARN, | |
| swap or "unknown", "10", mo.apply_vm_tuning) | |
| results.append(r) | |
| return results | |
| # ─────────────────── CATEGORY H: HDMI/CEC ─────────────────────────────── | |
| def _check_hdmi(self) -> List[DiagResult]: | |
| results = [] | |
| cat = "HDMI" | |
| ho = HDMIOptimizer() | |
| for prop, expected in [ | |
| ("persist.sys.cec.status", "true"), | |
| ("persist.sys.hdmi.addr.playback", "11"), # corrected v12 | |
| ("persist.sys.hdmi.keep_awake", "true"), | |
| ("persist.nx.hdmi.tx_standby_cec", "1"), | |
| ("persist.sys.hdr.enable", "1"), | |
| ]: | |
| val = ADB.prop(prop) | |
| ok = val == expected | |
| r = self._r(cat, prop.split(".")[-1], Status.OK if ok else Status.WARN, | |
| val or "not set", expected, ho.apply) | |
| results.append(r) | |
| return results | |
| # ─────────────────── RUN CATEGORY ─────────────────────────────────────── | |
| CATEGORY_MAP: Dict[str, Tuple[str, Callable]] = {} # filled post-class | |
| def run_category(self, cat_id: str) -> List[DiagResult]: | |
| cat_fn_map = { | |
| "A": ("System Health", self._check_system_health), | |
| "B": ("Cast Services", self._check_cast), | |
| "C": ("SmartTube", self._check_smarttube), | |
| "D": ("Video Pipeline", self._check_video), | |
| "E": ("Network/DNS", self._check_network), | |
| "F": ("Audio", self._check_audio), | |
| "G": ("Memory/LMK", self._check_memory), | |
| "H": ("HDMI/CEC", self._check_hdmi), | |
| } | |
| entry = cat_fn_map.get(cat_id.upper()) | |
| if not entry: | |
| return [] | |
| name, fn = entry | |
| Logger.header(f"🔎 DIAGNOSTICS: {cat_id} — {name}") | |
| results = fn() | |
| self._print_category_results(results) | |
| return results | |
| def _print_category_results(self, results: List[DiagResult]) -> None: | |
| ok_count = sum(1 for r in results if r.is_ok) | |
| bad_count = sum(1 for r in results if r.needs_repair) | |
| warn_count = sum(1 for r in results if r.status == Status.WARN) | |
| for r in results: | |
| if r.status == Status.OK: | |
| Logger.log(f" ✓ [{r.category}] {r.check}: {r.found}", "success") | |
| elif r.status == Status.WARN: | |
| Logger.log(f" ⚠ [{r.category}] {r.check}: {r.found} (expected: {r.expected})", "warning") | |
| elif r.status in (Status.BROKEN, Status.MISSING): | |
| Logger.log(f" ✗ [{r.category}] {r.check}: {r.found} (expected: {r.expected})", "error") | |
| if r.detail: | |
| Logger.log(f" └─ {r.detail}", "dim") | |
| Logger.log( | |
| f"\n Results: {ok_count} OK | {warn_count} WARN | {bad_count} NEEDS REPAIR", | |
| "cyan" | |
| ) | |
| def run_all_interactive(self) -> None: | |
| """Run all 8 categories interactively with repair offer per category.""" | |
| all_categories = ["A", "B", "C", "D", "E", "F", "G", "H"] | |
| cat_names = { | |
| "A": "System Health", | |
| "B": "Cast Services (Chromecast)", | |
| "C": "SmartTube", | |
| "D": "Video Pipeline", | |
| "E": "Network / DNS", | |
| "F": "Audio (A/V Sync)", | |
| "G": "Memory / LMK", | |
| "H": "HDMI / CEC", | |
| } | |
| Logger.header("🔎 INTERACTIVE SELF-DIAGNOSTICS — 8 Categories") | |
| all_broken: List[DiagResult] = [] | |
| for cat_id in all_categories: | |
| Logger.log(f"\n[{cat_id}] {cat_names[cat_id]}", "cyan") | |
| results = self.run_category(cat_id) | |
| broken = [r for r in results if r.needs_repair] | |
| all_broken.extend(broken) | |
| if broken: | |
| c = Logger.C | |
| Logger.warning(f"{len(broken)} issue(s) found in {cat_names[cat_id]}") | |
| choice = input( | |
| f" {c['warning']}Repair now? [Y/n/s=skip all] > {c['reset']}" | |
| ).strip().lower() | |
| if choice == "s": | |
| Logger.log(" Skipping remaining repairs", "warning") | |
| break | |
| if choice in ("", "y", "yes"): | |
| self._repair_category(broken) | |
| else: | |
| Logger.success(f" {cat_names[cat_id]}: All OK") | |
| # Summary | |
| Logger.header("📋 DIAGNOSTIC SUMMARY") | |
| total = len(self.results) | |
| ok_count = sum(1 for r in self.results if r.is_ok) | |
| broken = sum(1 for r in self.results if r.needs_repair) | |
| warn = sum(1 for r in self.results if r.status == Status.WARN) | |
| Logger.log(f" Total checks : {total}") | |
| Logger.success(f" OK : {ok_count}") | |
| Logger.warning(f" Warnings : {warn}") | |
| Logger.error (f" Need repair : {broken}") | |
| if all_broken: | |
| Logger.warning(f"\n Unresolved issues:") | |
| for r in all_broken: | |
| if r.needs_repair: | |
| Logger.log(f" ✗ [{r.category}] {r.check}: {r.found}", "error") | |
| def _repair_category(self, broken: List[DiagResult]) -> None: | |
| """Execute fix_fn for all broken items in a category.""" | |
| applied_fns: set = set() | |
| for r in broken: | |
| if r.fix_fn and id(r.fix_fn) not in applied_fns: | |
| applied_fns.add(id(r.fix_fn)) | |
| Logger.repair(f"Auto-repairing: [{r.category}] {r.check}") | |
| try: | |
| r.fix_fn() | |
| except Exception as e: | |
| Logger.error(f"Repair failed: {e}") | |
| def single_category_menu(self) -> None: | |
| """Menu for selecting which category to diagnose.""" | |
| c = Logger.C | |
| cat_map = { | |
| "A": "System Health", | |
| "B": "Cast Services (Chromecast)", | |
| "C": "SmartTube", | |
| "D": "Video Pipeline", | |
| "E": "Network / DNS", | |
| "F": "Audio (A/V Sync)", | |
| "G": "Memory / LMK", | |
| "H": "HDMI / CEC", | |
| "*": "All categories (interactive)", | |
| } | |
| Logger.header("🔎 DIAGNOSTICS — Select Category") | |
| for k, v in cat_map.items(): | |
| Logger.log(f" {c['cyan']}{k}{c['reset']}. {v}") | |
| choice = input(f"\n{c['cyan']}Category [A-H or *] > {c['reset']}").strip().upper() | |
| if choice == "*": | |
| self.run_all_interactive() | |
| elif choice in cat_map: | |
| results = self.run_category(choice) | |
| broken = [r for r in results if r.needs_repair] | |
| if broken: | |
| fix = input(f"\n{c['warning']}Auto-repair {len(broken)} issue(s)? [Y/n] > {c['reset']}").strip().lower() | |
| if fix in ("", "y"): | |
| self._repair_category(broken) | |
| else: | |
| Logger.warning("Invalid category") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # AUTO REPAIR ENGINE | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class AutoRepairEngine: | |
| """ | |
| Self-healing repair system. | |
| Detects broken sectors, prompts user, downloads APKs, reinstalls. | |
| """ | |
| REPAIR_REGISTRY: List[Dict[str, Any]] = [ | |
| { | |
| "id": "smarttube_missing", | |
| "name": "SmartTube Stable", | |
| "detect": lambda: not ADB.pkg_installed(DeviceSpec.SMARTTUBE_PKG_STABLE), | |
| "repair": lambda: APKDownloader.download_and_install( | |
| DeviceSpec.SMARTTUBE_STABLE_URL, | |
| DeviceSpec.SMARTTUBE_PKG_STABLE, | |
| "SmartTube Stable" | |
| ), | |
| }, | |
| { | |
| "id": "smarttube_old_package", | |
| "name": "SmartTube old package (com.teamsmart.videomanager.tv)", | |
| "detect": lambda: ADB.pkg_installed("com.teamsmart.videomanager.tv"), | |
| "repair": lambda: ( | |
| Logger.warning("Old SmartTube package found. " | |
| "New: org.smarttube.stable"), | |
| APKDownloader.download_and_install( | |
| DeviceSpec.SMARTTUBE_STABLE_URL, | |
| DeviceSpec.SMARTTUBE_PKG_STABLE, | |
| "SmartTube Stable (migration)" | |
| ) | |
| ), | |
| }, | |
| { | |
| "id": "cast_disabled", | |
| "name": "Chromecast Built-in Daemon disabled (mediashell)", | |
| "detect": lambda: not ADB.pkg_enabled("com.google.android.apps.mediashell"), | |
| "repair": ChromecastServiceManager.restore, | |
| }, | |
| { | |
| "id": "gms_disabled", | |
| "name": "Google Play Services disabled (GMS / Cast SDK)", | |
| "detect": lambda: not ADB.pkg_enabled("com.google.android.gms"), | |
| "repair": ChromecastServiceManager.restore, | |
| }, | |
| { | |
| "id": "wrong_dns", | |
| "name": "DNS incorrectly set (dns.cloudflare.com → should be one.one.one.one)", | |
| "detect": lambda: ADB.setting_get("global", "private_dns_specifier") == "dns.cloudflare.com", | |
| "repair": lambda: DNSManager.set_provider("cloudflare"), | |
| }, | |
| { | |
| "id": "dns_not_set", | |
| "name": "Private DNS not configured", | |
| "detect": lambda: ADB.setting_get("global", "private_dns_mode") != "hostname", | |
| "repair": lambda: DNSManager.set_provider("cloudflare"), | |
| }, | |
| { | |
| "id": "projectivy_missing", | |
| "name": "Projectivy Launcher missing", | |
| "detect": lambda: not ADB.pkg_installed(DeviceSpec.PROJECTIVY_PKG), | |
| "repair": lambda: APKDownloader.download_and_install( | |
| DeviceSpec.PROJECTIVY_URL, | |
| DeviceSpec.PROJECTIVY_PKG, | |
| "Projectivy Launcher" | |
| ), | |
| }, | |
| { | |
| "id": "shizuku_missing", | |
| "name": "Shizuku not installed", | |
| "detect": lambda: not ADB.pkg_installed(DeviceSpec.SHIZUKU_PKG), | |
| "repair": lambda: APKDownloader.download_and_install( | |
| DeviceSpec.SHIZUKU_URL, | |
| DeviceSpec.SHIZUKU_PKG, | |
| "Shizuku" | |
| ), | |
| }, | |
| { | |
| "id": "av1_not_suppressed", | |
| "name": "AV1 SW decoder active (will cause stuttering)", | |
| "detect": lambda: ADB.prop("media.codec.av1.disable") != "true", | |
| "repair": VideoEngineOptimizer().suppress_av1, | |
| }, | |
| { | |
| "id": "wrong_hdmi_addr", | |
| "name": "HDMI addr.playback=0 (should be 11 for BCM Nexus)", | |
| "detect": lambda: ADB.prop("persist.sys.hdmi.addr.playback") not in ("11", ""), | |
| "repair": HDMIOptimizer().apply, | |
| }, | |
| { | |
| "id": "audio_desync", | |
| "name": "Audio offload enabled (causes A/V desync on BCM7362 HDMI)", | |
| "detect": lambda: ADB.prop("audio.offload.disable") != "1", | |
| "repair": HDMIOptimizer().fix_audio_desync, | |
| }, | |
| ] | |
| @classmethod | |
| def scan_and_repair_interactive(cls) -> None: | |
| Logger.header("🔧 AUTO-REPAIR ENGINE — Sector Scan") | |
| Logger.log("Scanning all repair sectors...") | |
| found: List[Dict[str, Any]] = [] | |
| for entry in cls.REPAIR_REGISTRY: | |
| try: | |
| detected = entry["detect"]() | |
| except Exception: | |
| detected = False | |
| if detected: | |
| found.append(entry) | |
| Logger.error(f" ✗ BROKEN: {entry['name']}") | |
| else: | |
| Logger.log(f" ✓ OK: {entry['name']}", "dim") | |
| if not found: | |
| Logger.success("All sectors healthy — no repairs needed") | |
| return | |
| Logger.warning(f"\n{len(found)} broken sector(s) detected:") | |
| for i, entry in enumerate(found, 1): | |
| Logger.log(f" {i}. {entry['name']}", "error") | |
| c = Logger.C | |
| choice = input( | |
| f"\n{c['warning']}Repair all {len(found)} sectors? [Y=all / n=select / x=cancel] > {c['reset']}" | |
| ).strip().lower() | |
| if choice == "x": | |
| Logger.log("Repair cancelled") | |
| return | |
| if choice == "n": | |
| # Selective repair | |
| for i, entry in enumerate(found, 1): | |
| Logger.log(f"\n[{i}] {entry['name']}") | |
| sub = input(f" {c['warning']}Repair this sector? [Y/n] > {c['reset']}").strip().lower() | |
| if sub in ("", "y"): | |
| cls._apply_repair(entry) | |
| else: | |
| # Repair all | |
| for entry in found: | |
| cls._apply_repair(entry) | |
| Logger.success("Auto-repair complete") | |
| @classmethod | |
| def _apply_repair(cls, entry: Dict[str, Any]) -> None: | |
| Logger.repair(f"Repairing: {entry['name']}") | |
| try: | |
| entry["repair"]() | |
| except Exception as exc: | |
| Logger.error(f"Repair error: {exc}") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SHIZUKU MANAGER | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class ShizukuManager: | |
| CLASSPATH_CMD = ( | |
| "P=$(pm path moe.shizuku.privileged.api | cut -d: -f2); " | |
| "CLASSPATH=$P app_process /system/bin " | |
| "--nice-name=shizuku_server moe.shizuku.server.ShizukuServiceServer &" | |
| ) | |
| def deploy(self) -> None: | |
| Logger.header("🔑 SHIZUKU — Privilege Engine") | |
| pkg = DeviceSpec.SHIZUKU_PKG | |
| if not ADB.pkg_installed(pkg): | |
| apk = CACHE_DIR / "shizuku.apk" | |
| APKDownloader.download(DeviceSpec.SHIZUKU_URL, apk) | |
| APKDownloader.install(apk, "Shizuku") | |
| else: | |
| Logger.success("Shizuku already installed") | |
| Logger.log("Starting Shizuku bootstrap...") | |
| ADB.run(self.CLASSPATH_CMD) | |
| time.sleep(3) | |
| Logger.success("Shizuku server started") | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # MAIN ORCHESTRATOR | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| class PlayboxULTRA: | |
| def __init__(self, device: str) -> None: | |
| self.device = device | |
| self.video = VideoEngineOptimizer() | |
| self.memory = MemoryOptimizer() | |
| self.network = NetworkOptimizer() | |
| self.hdmi = HDMIOptimizer() | |
| self.resp = ResponsivenessOptimizer() | |
| self.debloat = SafeDebloater() | |
| self.cast = ChromecastServiceManager() | |
| self.aot = AOTCompiler() | |
| self.diag = DiagnosticEngine() | |
| self.repair = AutoRepairEngine() | |
| self.shizuku = ShizukuManager() | |
| def _banner(self) -> None: | |
| c = Logger.C | |
| print(f""" | |
| {c['header']}{c['bold']}╔══════════════════════════════════════════════════════════════════════╗ | |
| ║ PLAYBOX TITANIUM v{VERSION} | |
| ║ Sagemcom DCTIW362P │ Android TV 9 │ Broadcom BCM7362 | |
| ╠══════════════════════════════════════════════════════════════════════╣ | |
| ║ 🎬 SmartTube VP9 HW 🚇 Tunnel Mode 🛡 Cast Protected | |
| ║ 🔧 Self-Diagnostic 🩺 Auto-Repair 🔒 DNS: one.one.one.one | |
| ╚══════════════════════════════════════════════════════════════════════╝{c['reset']} | |
| Connected: {c['cyan']}{self.device}{c['reset']} | |
| """) | |
| def _menu(self) -> None: | |
| c = Logger.C | |
| while True: | |
| os.system("clear") | |
| self._banner() | |
| print(f"""{c['bold']}╔══════════════════════════════════════════════════════════════════╗{c['reset']} | |
| {c['success']}VIDEO PERFORMANCE{c['reset']} | |
| {c['success']}1.{c['reset']} Codec Pipeline (C2+OMX, ccodec, VP9 HW) | |
| {c['success']}2.{c['reset']} Rendering Engine (skiaglthreaded + VideoCore) | |
| {c['success']}3.{c['reset']} Tunnel Mode (VPU→Display direct) | |
| {c['success']}4.{c['reset']} AV1 Suppression + Dalvik Heap | |
| {c['success']}5.{c['reset']} AOT Compile (SmartTube + Projectivy + Cast) | |
| {c['header']}CHROMECAST PROTECTION{c['reset']} | |
| {c['success']}6.{c['reset']} 🛡 Audit Cast Services | |
| {c['success']}7.{c['reset']} 🛡 Restore Cast Services (emergency) | |
| {c['success']}8.{c['reset']} 📡 Cast mDNS Network Tuning | |
| {c['success']}9.{c['reset']} 🎯 Cast OOM Hardening | |
| {c['success']}P.{c['reset']} 🔍 Show Protection Registry | |
| {c['info']}DIAGNOSTICS & REPAIR{c['reset']} | |
| {c['info']}D.{c['reset']} 🔎 Interactive Self-Diagnostics (8 categories) | |
| {c['info']}R.{c['reset']} 🔧 Auto-Repair Engine (scan + fix all sectors) | |
| {c['info']}N.{c['reset']} 🔒 DNS Manager (interactive selector) | |
| {c['warning']}SYSTEM{c['reset']} | |
| {c['warning']}10.{c['reset']} Network TCP + DNS (Cloudflare auto) | |
| {c['warning']}11.{c['reset']} HDMI + CEC (BCM Nexus props, addr=11) | |
| {c['warning']}12.{c['reset']} Audio A/V Sync Fix | |
| {c['warning']}13.{c['reset']} Memory: LMK + VM + Heap | |
| {c['warning']}14.{c['reset']} System Responsiveness + I/O + CPU | |
| {c['warning']}15.{c['reset']} Safe Debloat (Cast PROTECTED) | |
| {c['warning']}16.{c['reset']} Deep Clean RAM | |
| {c['warning']}17.{c['reset']} Deploy Shizuku | |
| {c['cyan']}AUTO MODES{c['reset']} | |
| {c['cyan']}20.{c['reset']} 🚀 SMARTTUBE ULTRA (video-focused) | |
| {c['cyan']}21.{c['reset']} 🏆 FULL SYSTEM ULTRA (all modules) | |
| {c['error']}0.{c['reset']} Exit{c['bold']} | |
| ╚══════════════════════════════════════════════════════════════════════╝{c['reset']}""") | |
| choice = input(f"\n{c['cyan']}Choice > {c['reset']}").strip().lower() | |
| dispatch: Dict[str, Callable] = { | |
| "1": self.video.optimize_codec_pipeline, | |
| "2": self.video.optimize_rendering, | |
| "3": self.video.force_tunnel_mode, | |
| "4": self._codec_and_heap, | |
| "5": self.aot.compile_all, | |
| "6": self.cast.audit, | |
| "7": self.cast.restore, | |
| "8": self.cast.optimize_network, | |
| "9": self.cast.harden_oom, | |
| "p": ChromecastProtection.print_all, | |
| "d": self.diag.single_category_menu, | |
| "r": self.repair.scan_and_repair_interactive, | |
| "n": DNSManager.interactive_select, | |
| "10": self._network_full, | |
| "11": self.hdmi.apply, | |
| "12": self.hdmi.fix_audio_desync, | |
| "13": self._memory_full, | |
| "14": self.resp.apply, | |
| "15": self.debloat.run, | |
| "16": self.memory.deep_clean, | |
| "17": self.shizuku.deploy, | |
| "20": self.run_smarttube_ultra, | |
| "21": self.run_full_ultra, | |
| "0": self._exit, | |
| } | |
| fn = dispatch.get(choice) | |
| if fn: | |
| fn() | |
| else: | |
| Logger.warning("Invalid choice") | |
| if choice != "0": | |
| input(f"\n{c['cyan']}Press Enter...{c['reset']}") | |
| def _codec_and_heap(self): | |
| self.video.suppress_av1() | |
| self.memory.apply_dalvik_heap() | |
| def _network_full(self): | |
| self.network.apply_tcp() | |
| self.network.apply_dns("cloudflare") | |
| def _memory_full(self): | |
| self.memory.apply_lmk_profile() | |
| self.memory.apply_vm_tuning() | |
| self.memory.apply_dalvik_heap() | |
| def _exit(self) -> None: | |
| Logger.save() | |
| Logger.log(f"Log: {LOG_FILE}", "cyan") | |
| sys.exit(0) | |
| # ── SmartTube Ultra ────────────────────────────────────────────────────── | |
| def run_smarttube_ultra(self) -> None: | |
| Logger.header("🚀 SMARTTUBE ULTRA — Maximum Video Performance") | |
| pipeline: List[Tuple[str, Callable]] = [ | |
| ("Auto-Repair pre-check", self.repair.scan_and_repair_interactive), | |
| ("Chromecast Audit", self.cast.audit), | |
| ("Codec Pipeline (C2+OMX)", self.video.optimize_codec_pipeline), | |
| ("Rendering (skiaglthreaded)", self.video.optimize_rendering), | |
| ("Tunnel Mode", self.video.force_tunnel_mode), | |
| ("AV1 Suppression + Dalvik Heap", self._codec_and_heap), | |
| ("Audio A/V Sync", self.hdmi.fix_audio_desync), | |
| ("HDMI + CEC (BCM Nexus)", self.hdmi.apply), | |
| ("Memory: LMK + VM + Heap", self._memory_full), | |
| ("Responsiveness + I/O + CPU", self.resp.apply), | |
| ("TCP Network + DNS (Cloudflare)", self._network_full), | |
| ("Cast mDNS Tuning", self.cast.optimize_network), | |
| ("Cast OOM Hardening", self.cast.harden_oom), | |
| ("AOT Compilation", self.aot.compile_all), | |
| ("Cast Services Restore (final)", self.cast.restore), | |
| ] | |
| total = len(pipeline) | |
| for i, (name, fn) in enumerate(pipeline, 1): | |
| Logger.log(f"\n[{i}/{total}] {name}...", "cyan") | |
| fn() | |
| time.sleep(0.3) | |
| Logger.header("🎉 SMARTTUBE ULTRA COMPLETE") | |
| Logger.success("VP9 HW + Tunnel Mode + AOT + DNS: one.one.one.one + Cast Protected ✓") | |
| Logger.log("SmartTube: Settings → Player → Video codec → VP9", "warning") | |
| Logger.save() | |
| # ── Full System Ultra ──────────────────────────────────────────────────── | |
| def run_full_ultra(self) -> None: | |
| Logger.header("🏆 FULL SYSTEM ULTRA — Complete Optimization") | |
| pipeline: List[Tuple[str, Callable]] = [ | |
| ("System Diagnostics (baseline)", lambda: self.diag.run_category("A")), | |
| ("Auto-Repair pre-check", self.repair.scan_and_repair_interactive), | |
| ("Chromecast Audit", self.cast.audit), | |
| ("Codec Pipeline (C2+OMX)", self.video.optimize_codec_pipeline), | |
| ("Rendering (skiaglthreaded)", self.video.optimize_rendering), | |
| ("Tunnel Mode", self.video.force_tunnel_mode), | |
| ("AV1 Suppression + Heap", self._codec_and_heap), | |
| ("Audio A/V Sync", self.hdmi.fix_audio_desync), | |
| ("HDMI + CEC (BCM Nexus addr=11)", self.hdmi.apply), | |
| ("TCP + DNS (Cloudflare DoT)", self._network_full), | |
| ("Memory: LMK + VM + Heap", self._memory_full), | |
| ("Responsiveness + I/O + CPU", self.resp.apply), | |
| ("Safe Debloat (Cast Protected)", self.debloat.run), | |
| ("Cast mDNS Network", self.cast.optimize_network), | |
| ("Cast OOM Hardening", self.cast.harden_oom), | |
| ("AOT Compilation", self.aot.compile_all), | |
| ("Deep Clean RAM", self.memory.deep_clean), | |
| ("Final Cast Audit", self.cast.audit), | |
| ] | |
| total = len(pipeline) | |
| for i, (name, fn) in enumerate(pipeline, 1): | |
| Logger.log(f"\n[{i}/{total}] {name}...", "cyan") | |
| fn() | |
| time.sleep(0.2) | |
| Logger.header("🏆 FULL SYSTEM ULTRA COMPLETE") | |
| Logger.success("All modules applied. Chromecast: PROTECTED. DNS: FIXED. Cast: SECURED.") | |
| Logger.log(f"Reboot: adb -s {self.device} reboot", "warning") | |
| Logger.save() | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # CLI ENTRY POINT | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| def _parser() -> argparse.ArgumentParser: | |
| p = argparse.ArgumentParser( | |
| description=f"Playbox Titanium v{VERSION} — Sagemcom DCTIW362P", | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| EXAMPLES: | |
| python3 Autopilot_12_ULTRA.py # Interactive menu | |
| python3 Autopilot_12_ULTRA.py --smarttube-ultra # Video-focused ultra | |
| python3 Autopilot_12_ULTRA.py --full-ultra # All modules | |
| python3 Autopilot_12_ULTRA.py --diag # Self-diagnostics | |
| python3 Autopilot_12_ULTRA.py --repair # Auto-repair scan | |
| python3 Autopilot_12_ULTRA.py --cast-restore # Emergency Cast fix | |
| python3 Autopilot_12_ULTRA.py --dns cloudflare # Fix DNS | |
| python3 Autopilot_12_ULTRA.py --device 192.168.1.3:5555 --full-ultra | |
| """) | |
| p.add_argument("--device", default=None) | |
| p.add_argument("--smarttube-ultra", action="store_true") | |
| p.add_argument("--full-ultra", action="store_true") | |
| p.add_argument("--diag", action="store_true", help="Run interactive self-diagnostics") | |
| p.add_argument("--repair", action="store_true", help="Run auto-repair engine") | |
| p.add_argument("--cast-audit", action="store_true") | |
| p.add_argument("--cast-restore", action="store_true") | |
| p.add_argument("--dns", default=None, metavar="PROVIDER", | |
| help="Set DNS: cloudflare|google|quad9|adguard|nextdns") | |
| p.add_argument("--beta", action="store_true") | |
| return p | |
| def main() -> None: | |
| args = _parser().parse_args() | |
| device = args.device or ADB.detect() or DEFAULT_ADB_HOST | |
| if args.device: | |
| Logger.log(f"Device: {device} (specified)") | |
| elif ADB.detect(): | |
| Logger.success(f"Auto-detected: {device}") | |
| else: | |
| Logger.warning(f"No device detected — using default: {device}") | |
| if not ADB.connect(device): | |
| Logger.error(f"Cannot connect to {device}") | |
| sys.exit(1) | |
| app = PlayboxULTRA(device) | |
| if args.cast_restore: | |
| ChromecastServiceManager.restore() | |
| elif args.cast_audit: | |
| ChromecastServiceManager.audit() | |
| elif args.dns: | |
| DNSManager.set_provider(args.dns) | |
| elif args.diag: | |
| app.diag.run_all_interactive() | |
| elif args.repair: | |
| app.repair.scan_and_repair_interactive() | |
| elif args.smarttube_ultra: | |
| app.run_smarttube_ultra() | |
| elif args.full_ultra: | |
| app.run_full_ultra() | |
| else: | |
| app._banner() | |
| app._menu() | |
| if __name__ == "__main__": | |
| try: | |
| main() | |
| except KeyboardInterrupt: | |
| print() | |
| Logger.warning("Interrupted (Ctrl+C)") | |
| Logger.save() | |
| sys.exit(0) | |
| except Exception as exc: | |
| Logger.error(f"Unexpected error: {exc}") | |
| import traceback | |
| traceback.print_exc() | |
| sys.exit(1) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # ╔══════════════════════════════════════════════════════════════════╗ | |
| # ║ Samsung Galaxy Watch 4 — Ultra Optimizer & Repair Suite ║ | |
| # ║ One UI 8.0 / WearOS 6.0 / Android 16 · SM-R870 · Exynos W920 ║ | |
| # ║ Wersja: 3.0.0 | Autor: Senior Android/WearOS Eng. ║ | |
| # ╚══════════════════════════════════════════════════════════════════╝ | |
| # Bezpieczne, odwracalne, kompleksowe narzędzie do optymalizacji, | |
| # diagnostyki, debloatu i naprawy systemu bez ryzyka brick'a. | |
| # Bash strict mode — wyłączamy set -e bo potrzebujemy obsługi błędów per-komenda | |
| set -uo pipefail | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 0 — ZMIENNE GLOBALNE I STAŁE | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # Kolory terminala | |
| RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' | |
| CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' | |
| BLUE='\033[0;34m'; MAGENTA='\033[0;35m'; WHITE='\033[0;37m' | |
| BGREEN='\033[1;32m'; BRED='\033[1;31m'; BYELLOW='\033[1;33m' | |
| BCYAN='\033[1;36m'; DIM='\033[2m' | |
| # Metadane | |
| readonly SCRIPT_NAME="Watch4 Ultra Suite" | |
| readonly VERSION="3.0.0" | |
| readonly SCRIPT_DATE="2026-01-20" | |
| readonly LOG_DIR="/tmp/watch4_suite_logs" | |
| readonly BACKUP_DIR="${LOG_DIR}/backup_$(date +%Y%m%d_%H%M%S)" | |
| readonly LOG_FILE="${LOG_DIR}/session_$(date +%Y%m%d_%H%M%S).log" | |
| readonly ADB_TIMEOUT=15 | |
| readonly ADB_LONG_TIMEOUT=120 | |
| # Stan połączenia | |
| DEVICE_SERIAL="" | |
| DEVICE_MODEL="" | |
| DEVICE_ANDROID="" | |
| DEVICE_BUILD="" | |
| DEVICE_SDK="" | |
| # Flaga — czy backup ustawień wykonany w tej sesji | |
| BACKUP_DONE=false | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 1 — INFRASTRUKTURA: LOGGING, UI, BŁĘDY | |
| # ═══════════════════════════════════════════════════════════════════ | |
| mkdir -p "$LOG_DIR" "$BACKUP_DIR" | |
| ts() { date '+%H:%M:%S'; } | |
| log() { printf "[%s] %s\n" "$(ts)" "$*" >> "$LOG_FILE"; } | |
| ok() { printf "${GREEN} ✓ %s${RESET}\n" "$*"; log "OK: $*"; } | |
| err() { printf "${BRED} ✗ %s${RESET}\n" "$*" >&2; log "ERROR: $*"; } | |
| warn() { printf "${BYELLOW} ⚠ %s${RESET}\n" "$*"; log "WARN: $*"; } | |
| info() { printf "${CYAN} ℹ %s${RESET}\n" "$*"; log "INFO: $*"; } | |
| step() { printf "${BOLD}${WHITE} ▶ %s${RESET}\n" "$*"; log "STEP: $*"; } | |
| detail() { printf "${DIM} %s${RESET}\n" "$*"; log "DETAIL: $*"; } | |
| header() { | |
| local txt="$1" | |
| local width=60 | |
| local pad=$(( (width - ${#txt} - 2) / 2 )) | |
| echo | |
| printf "${BOLD}${BLUE}" | |
| printf '╔'; printf '═%.0s' $(seq 1 $((width-2))); printf '╗\n' | |
| printf '║%*s%s%*s║\n' $pad "" "$txt" $((width - pad - ${#txt} - 2)) "" | |
| printf '╚'; printf '═%.0s' $(seq 1 $((width-2))); printf '╝\n' | |
| printf "${RESET}" | |
| } | |
| sep() { printf "${BLUE}%s${RESET}\n" "$(printf '─%.0s' {1..60})"; } | |
| sep_thin() { printf "${DIM}%s${RESET}\n" "$(printf '·%.0s' {1..60})"; } | |
| status_row() { | |
| # status_row "Klucz" "Wartość" [kolor_wartości] | |
| local key="$1" val="$2" color="${3:-$CYAN}" | |
| printf " ${DIM}%-32s${RESET} ${color}%s${RESET}\n" "${key}:" "$val" | |
| } | |
| pause() { | |
| echo | |
| read -rp "$(printf " ${YELLOW}Naciśnij [Enter] aby kontynuować...${RESET}")" _ | |
| } | |
| confirm() { | |
| # confirm "Treść pytania" → 0 jeśli tak, 1 jeśli nie | |
| local msg="$1" | |
| local ans | |
| read -rp "$(printf " ${BYELLOW}%s [t/N]: ${RESET}" "$msg")" ans | |
| [[ "${ans,,}" == "t" || "${ans,,}" == "tak" ]] | |
| } | |
| spinner() { | |
| # spinner <pid> <wiadomość> | |
| local pid=$1 msg="$2" | |
| local spin=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') | |
| local i=0 | |
| while kill -0 "$pid" 2>/dev/null; do | |
| printf "\r ${CYAN}%s${RESET} %s " "${spin[$i]}" "$msg" | |
| i=$(( (i+1) % ${#spin[@]} )) | |
| sleep 0.1 | |
| done | |
| printf "\r%60s\r" " " | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 2 — WRAPPER ADB (bezpieczny, logujący) | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # Wykonaj komendę adb shell — zwraca kod wyjścia | |
| adb_cmd() { | |
| local cmd="$*" | |
| log "ADB_CMD: $cmd" | |
| local output exit_code=0 | |
| output=$(timeout "$ADB_TIMEOUT" adb -s "$DEVICE_SERIAL" shell "$cmd" 2>&1) || exit_code=$? | |
| log "ADB_OUT[$exit_code]: $output" | |
| echo "$output" | |
| return $exit_code | |
| } | |
| # Wykonaj długą komendę adb shell (dexopt, compile, itp.) | |
| adb_long() { | |
| local cmd="$*" | |
| log "ADB_LONG: $cmd" | |
| timeout "$ADB_LONG_TIMEOUT" adb -s "$DEVICE_SERIAL" shell "$cmd" 2>&1 | tee -a "$LOG_FILE" | |
| return "${PIPESTATUS[0]}" | |
| } | |
| # Pobierz wartość bez stderr | |
| adb_get() { | |
| timeout "$ADB_TIMEOUT" adb -s "$DEVICE_SERIAL" shell "$@" 2>/dev/null | tr -d '\r' | |
| } | |
| # Pobierz prop systemowy | |
| get_prop() { | |
| adb_get getprop "$1" 2>/dev/null || echo "" | |
| } | |
| # Ustawianie settings z walidacją i informowaniem | |
| safe_setting() { | |
| local namespace="$1" key="$2" value="$3" desc="${4:-}" | |
| local old_val | |
| old_val=$(adb_get "settings get ${namespace} ${key}" 2>/dev/null || echo "null") | |
| # Zapisz do backupu | |
| echo "settings put ${namespace} ${key} ${old_val}" >> "${BACKUP_DIR}/settings_restore.sh" | |
| if adb_cmd "settings put ${namespace} ${key} ${value}" >/dev/null 2>&1; then | |
| local label="${desc:-${namespace}/${key}}" | |
| ok "${label}: ${DIM}${old_val}${RESET} → ${GREEN}${value}${RESET}" | |
| return 0 | |
| else | |
| err "Nie udało się ustawić ${namespace}/${key}=${value}" | |
| return 1 | |
| fi | |
| } | |
| # Sprawdź czy urządzenie odpowiada | |
| ping_device() { | |
| timeout 5 adb -s "$DEVICE_SERIAL" shell "echo ok" 2>/dev/null | grep -q "ok" | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 3 — BANER I POŁĄCZENIE | |
| # ═══════════════════════════════════════════════════════════════════ | |
| print_banner() { | |
| clear | |
| printf "${BOLD}${MAGENTA}" | |
| cat << 'BANNER' | |
| ╔══════════════════════════════════════════════════════════════╗ | |
| ║ ║ | |
| ║ ██╗ ██╗ █████╗ ████████╗ ██████╗██╗ ██╗ ██╗ ██╗ ║ | |
| ║ ██║ ██║██╔══██╗╚══██╔══╝██╔════╝██║ ██║ ██║ ██║ ║ | |
| ║ ██║ █╗ ██║███████║ ██║ ██║ ███████║ ███████║ ║ | |
| ║ ██║███╗██║██╔══██║ ██║ ██║ ██╔══██║ ╚════██║ ║ | |
| ║ ╚███╔███╔╝██║ ██║ ██║ ╚██████╗██║ ██║ ██║ ║ | |
| ║ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ║ | |
| ║ ║ | |
| ║ Samsung Galaxy Watch 4 — Ultra Optimizer Suite ║ | |
| ║ One UI 8.0 / WearOS 6.0 / Android 16 / SM-R870 ║ | |
| ╚══════════════════════════════════════════════════════════════╝ | |
| BANNER | |
| printf "${RESET}" | |
| printf " ${DIM}Wersja: ${VERSION} · ${SCRIPT_DATE} · Log: ${LOG_FILE}${RESET}\n" | |
| sep | |
| } | |
| check_adb_installed() { | |
| if ! command -v adb &>/dev/null; then | |
| err "ADB nie znalezione w PATH!" | |
| err "Zainstaluj: https://developer.android.com/tools/releases/platform-tools" | |
| exit 1 | |
| fi | |
| local adb_ver | |
| adb_ver=$(adb version 2>/dev/null | head -1) | |
| ok "$adb_ver" | |
| } | |
| connect_device() { | |
| header "POŁĄCZENIE Z ZEGARKIEM" | |
| # Sprawdź istniejące połączenia | |
| local existing | |
| existing=$(adb devices 2>/dev/null | grep -E "^[^\s]+\s+device$" | head -1 || true) | |
| if [[ -n "$existing" ]]; then | |
| DEVICE_SERIAL=$(echo "$existing" | awk '{print $1}') | |
| ok "Znaleziono aktywne połączenie: ${CYAN}${DEVICE_SERIAL}${RESET}" | |
| else | |
| echo | |
| info "Brak aktywnych połączeń. Podaj dane zegarka:" | |
| echo | |
| read -rp "$(printf " ${CYAN}Adres IP zegarka: ${RESET}")" WATCH_IP | |
| if ! [[ "$WATCH_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then | |
| err "Nieprawidłowy format IP."; exit 1 | |
| fi | |
| read -rp "$(printf " ${CYAN}Port [domyślnie 5555]: ${RESET}")" WATCH_PORT | |
| WATCH_PORT="${WATCH_PORT:-5555}" | |
| DEVICE_SERIAL="${WATCH_IP}:${WATCH_PORT}" | |
| info "Łączę z ${DEVICE_SERIAL}..." | |
| # Wymuś restart serwera ADB na wypadek zawieszenia | |
| adb kill-server &>/dev/null; sleep 1; adb start-server &>/dev/null | |
| local conn_out | |
| conn_out=$(adb connect "$DEVICE_SERIAL" 2>&1) | |
| log "ADB connect: $conn_out" | |
| if echo "$conn_out" | grep -qiE "connected to|already connected"; then | |
| ok "Połączono z ${DEVICE_SERIAL}" | |
| elif echo "$conn_out" | grep -qi "refused"; then | |
| err "Połączenie odrzucone — sprawdź:" | |
| err " 1. Zegarek i komputer w tej samej sieci Wi-Fi" | |
| err " 2. Ustawienia → Opcje programistyczne → Debugowanie ADB → WŁ" | |
| err " 3. Port ADB: Ustawienia → Opcje programistyczne → Debugowanie ADB po sieci" | |
| exit 1 | |
| else | |
| err "Nieznany błąd połączenia: $conn_out"; exit 1 | |
| fi | |
| fi | |
| # Weryfikacja stanu urządzenia | |
| local state | |
| state=$(timeout 5 adb -s "$DEVICE_SERIAL" get-state 2>&1 || echo "error") | |
| case "$state" in | |
| device) ok "Urządzenie gotowe (device)" ;; | |
| unauthorized) | |
| err "Nieautoryzowane! Zaakceptuj dialog na zegarku → uruchom skrypt ponownie." | |
| exit 1 ;; | |
| offline) | |
| err "Urządzenie offline. Sprawdź sieć lub uruchom: adb kill-server && adb start-server" | |
| exit 1 ;; | |
| *) | |
| err "Nieznany stan: $state"; exit 1 ;; | |
| esac | |
| # Pobierz informacje o urządzeniu | |
| DEVICE_MODEL=$(get_prop ro.product.model) | |
| DEVICE_ANDROID=$(get_prop ro.build.version.release) | |
| DEVICE_BUILD=$(get_prop ro.build.display.id) | |
| DEVICE_SDK=$(get_prop ro.build.version.sdk) | |
| local device_arch | |
| device_arch=$(get_prop ro.product.cpu.abi) | |
| sep | |
| status_row "Model" "$DEVICE_MODEL" | |
| status_row "Android" "$DEVICE_ANDROID (SDK $DEVICE_SDK)" | |
| status_row "Build ID" "$DEVICE_BUILD" | |
| status_row "Architektura" "$device_arch" | |
| status_row "Połączony przez" "$DEVICE_SERIAL" | |
| sep | |
| if ! echo "$DEVICE_MODEL" | grep -qi "R870\|Watch 4"; then | |
| warn "Model ($DEVICE_MODEL) różni się od docelowego SM-R870." | |
| warn "Skrypt może wymagać dostosowania dla tego urządzenia." | |
| fi | |
| if (( DEVICE_SDK < 35 )); then | |
| warn "SDK ${DEVICE_SDK} — niektóre komendy ART/Android 16 mogą nie działać." | |
| fi | |
| # Utwórz skrypt przywracania backupu | |
| cat > "${BACKUP_DIR}/settings_restore.sh" << 'RESTORE_HEADER' | |
| #!/usr/bin/env bash | |
| # AUTO-WYGENEROWANY skrypt przywracania ustawień | |
| # Uruchom: adb -s DEVICE shell "sh /sdcard/watch4_restore.sh" | |
| echo "Przywracanie ustawień Watch4 Suite..." | |
| RESTORE_HEADER | |
| log "Sesja połączona: $DEVICE_SERIAL | Model: $DEVICE_MODEL | Android: $DEVICE_ANDROID" | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 4 — DIAGNOSTYKA PROAKTYWNA | |
| # ═══════════════════════════════════════════════════════════════════ | |
| diagnostics_menu() { | |
| while true; do | |
| header "DIAGNOSTYKA PROAKTYWNA" | |
| echo " [1] Pełny raport systemu (snapshot)" | |
| echo " [2] Monitor temperatury w czasie rzeczywistym" | |
| echo " [3] Analiza zużycia RAM i procesów" | |
| echo " [4] Inspekcja baterii (health, wear, temperatury)" | |
| echo " [5] Analiza wydajności UI (jank, frame drops)" | |
| echo " [6] Sprawdzenie usług systemowych (status krytycznych)" | |
| echo " [7] Analiza logów systemowych (ANR, crash, OOM)" | |
| echo " [8] Inspekcja stanu sieci i Bluetooth" | |
| echo " [9] Eksportuj pełny raport diagnostyczny do pliku" | |
| echo " [0] Powrót" | |
| sep | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) diag_full_snapshot ;; | |
| 2) diag_thermal_monitor ;; | |
| 3) diag_ram_analysis ;; | |
| 4) diag_battery_health ;; | |
| 5) diag_ui_performance ;; | |
| 6) diag_system_services ;; | |
| 7) diag_log_analysis ;; | |
| 8) diag_network_bt ;; | |
| 9) diag_export_report ;; | |
| 0) return ;; | |
| *) warn "Nieprawidłowy wybór." ;; | |
| esac | |
| done | |
| } | |
| diag_full_snapshot() { | |
| header "PEŁNY RAPORT SYSTEMU" | |
| sep_thin | |
| step "Informacje systemowe" | |
| status_row "Model" "$(get_prop ro.product.model)" | |
| status_row "Android" "$(get_prop ro.build.version.release)" | |
| status_row "SDK" "$(get_prop ro.build.version.sdk)" | |
| status_row "Build" "$(get_prop ro.build.display.id)" | |
| status_row "CPU ABI" "$(get_prop ro.product.cpu.abi)" | |
| status_row "Chipset" "$(get_prop ro.hardware)" | |
| status_row "Uptime" "$(adb_get cat /proc/uptime | awk '{printf "%d min %d sek", $1/60, $1%60}')" | |
| sep_thin | |
| step "Pamięć RAM" | |
| local mem_total mem_avail mem_free | |
| mem_total=$(adb_get "grep MemTotal /proc/meminfo | awk '{print \$2}'") | |
| mem_avail=$(adb_get "grep MemAvailable /proc/meminfo | awk '{print \$2}'") | |
| mem_free=$(adb_get "grep MemFree /proc/meminfo | awk '{print \$2}'") | |
| local mem_used=$(( (mem_total - mem_avail) / 1024 )) | |
| local mem_total_mb=$(( mem_total / 1024 )) | |
| local mem_avail_mb=$(( mem_avail / 1024 )) | |
| status_row "RAM łącznie" "${mem_total_mb} MB" | |
| status_row "RAM dostępna" "${mem_avail_mb} MB" | |
| status_row "RAM użyta" "${mem_used} MB" | |
| sep_thin | |
| step "Miejsce na dysku" | |
| adb_get "df /data /cache /system 2>/dev/null" | while IFS= read -r line; do | |
| printf " ${DIM}%s${RESET}\n" "$line" | |
| done | |
| sep_thin | |
| step "Temperatura CPU (przybliżona)" | |
| local temps | |
| temps=$(adb_get "cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | head -5" || echo "niedostępne") | |
| echo "$temps" | head -5 | while IFS= read -r t; do | |
| if [[ "$t" =~ ^[0-9]+$ ]]; then | |
| local deg=$(( t / 1000 )) | |
| local color="$GREEN" | |
| (( deg > 45 )) && color="$YELLOW" | |
| (( deg > 55 )) && color="$RED" | |
| printf " ${color}%d°C${RESET}\n" "$deg" | |
| fi | |
| done | |
| sep_thin | |
| step "Akumulator" | |
| adb_get "dumpsys battery 2>/dev/null" | grep -E "level|status|health|temperature|voltage|plugged" | while IFS= read -r line; do | |
| printf " ${CYAN}%s${RESET}\n" "$line" | |
| done | |
| sep_thin | |
| step "Animacje systemowe" | |
| for key in window_animation_scale transition_animation_scale animator_duration_scale; do | |
| local val | |
| val=$(adb_get "settings get global $key") | |
| status_row "$key" "$val" | |
| done | |
| step "AOD Status" | |
| local aod_val | |
| aod_val=$(adb_get "settings get secure doze_always_on") | |
| local aod_label="Wyłączone" | |
| [[ "$aod_val" == "1" ]] && aod_label="Włączone" | |
| status_row "doze_always_on" "$aod_val ($aod_label)" | |
| pause | |
| } | |
| diag_thermal_monitor() { | |
| header "MONITOR TEMPERATURY CZASU RZECZYWISTEGO" | |
| echo -e " ${YELLOW}Monitorowanie przez 30 sekund. Ctrl+C aby przerwać wcześniej.${RESET}" | |
| echo | |
| local zones | |
| zones=$(adb_get "ls /sys/class/thermal/ 2>/dev/null" | grep thermal_zone | head -8) | |
| for i in $(seq 1 30); do | |
| printf "\r ${BOLD}[%2d/30s]${RESET} " "$i" | |
| while IFS= read -r zone; do | |
| local temp | |
| temp=$(adb_get "cat /sys/class/thermal/${zone}/temp 2>/dev/null" || echo "0") | |
| if [[ "$temp" =~ ^[0-9]+$ ]]; then | |
| local deg=$(( temp / 1000 )) | |
| local color="$GREEN" | |
| (( deg > 40 )) && color="$YELLOW" | |
| (( deg > 50 )) && color="$RED" | |
| printf "${color}%d°C${RESET} " "$deg" | |
| fi | |
| done <<< "$zones" | |
| sleep 1 | |
| done | |
| echo | |
| ok "Monitorowanie zakończone." | |
| pause | |
| } | |
| diag_ram_analysis() { | |
| header "ANALIZA RAM I PROCESÓW" | |
| sep_thin | |
| step "Top 10 procesów według RAM" | |
| echo | |
| printf " ${BOLD}%-8s %-12s %-8s %s${RESET}\n" "PID" "RSS(MB)" "USER" "NAZWA" | |
| sep_thin | |
| adb_get "ps -eo pid,rss,user,comm 2>/dev/null | sort -k2 -rn | head -10" | while IFS= read -r line; do | |
| local pid rss user comm | |
| read -r pid rss user comm <<< "$line" | |
| if [[ "$rss" =~ ^[0-9]+$ ]]; then | |
| local rss_mb=$(( rss / 1024 )) | |
| local color="$WHITE" | |
| (( rss_mb > 100 )) && color="$YELLOW" | |
| (( rss_mb > 200 )) && color="$RED" | |
| printf " ${DIM}%-8s${RESET} ${color}%-12s${RESET} %-8s %s\n" "$pid" "${rss_mb} MB" "$user" "$comm" | |
| fi | |
| done | |
| sep_thin | |
| step "Statystyki LMK (Low Memory Killer)" | |
| adb_get "cat /sys/module/lowmemorykiller/parameters/minfree 2>/dev/null" | while IFS= read -r line; do | |
| detail "LMK minfree: $line" | |
| done | |
| sep_thin | |
| step "Ostatnie zabite procesy (OOM)" | |
| adb_get "logcat -d -t 50 --pid 1 2>/dev/null" | grep -i "lowmem\|oom_kill\|killing\|killed" | tail -5 | while IFS= read -r line; do | |
| printf " ${RED}%s${RESET}\n" "$line" | |
| done || detail "Brak wpisów OOM w ostatnich logach." | |
| pause | |
| } | |
| diag_battery_health() { | |
| header "INSPEKCJA BATERII" | |
| sep_thin | |
| step "Podstawowe dane" | |
| adb_get "dumpsys battery 2>/dev/null" | while IFS= read -r line; do | |
| local key val | |
| IFS=':' read -r key val <<< "$line" | |
| key=$(echo "$key" | xargs) | |
| val=$(echo "$val" | xargs) | |
| [[ -z "$key" || -z "$val" ]] && continue | |
| case "$key" in | |
| level) | |
| local color="$GREEN" | |
| (( val < 30 )) && color="$YELLOW" | |
| (( val < 15 )) && color="$RED" | |
| status_row "Poziom" "${val}%" "$color" | |
| ;; | |
| health) | |
| local h_label | |
| case "$val" in | |
| 2) h_label="Dobry ✓" ;; | |
| 3) h_label="Przegrzanie !" ;; | |
| 4) h_label="Martwy ✗" ;; | |
| *) h_label="Status: $val" ;; | |
| esac | |
| status_row "Kondycja" "$h_label" | |
| ;; | |
| temperature) | |
| local temp_c | |
| temp_c=$(echo "scale=1; $val / 10" | bc 2>/dev/null || echo "${val}/10") | |
| status_row "Temperatura" "${temp_c}°C" | |
| ;; | |
| voltage) status_row "Napięcie" "${val} mV" ;; | |
| status) | |
| local s_label | |
| case "$val" in | |
| 1) s_label="Nieznany" ;; 2) s_label="Ładowanie" ;; | |
| 3) s_label="Rozładowywanie" ;; 5) s_label="Pełny" ;; | |
| *) s_label="$val" ;; | |
| esac | |
| status_row "Status" "$s_label" | |
| ;; | |
| *) status_row "$key" "$val" ;; | |
| esac | |
| done | |
| sep_thin | |
| step "Historia zużycia baterii (ostatnie 24h)" | |
| adb_get "dumpsys batterystats 2>/dev/null" | grep -E "Discharge|charge|plugged|screen" | head -10 | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| sep_thin | |
| step "Rekomendacje" | |
| local level | |
| level=$(adb_get "dumpsys battery 2>/dev/null | grep level | awk -F: '{print \$2}' | tr -d ' \r'") | |
| if [[ "$level" =~ ^[0-9]+$ ]]; then | |
| (( level < 20 )) && warn "Niski poziom baterii — podłącz ładowarkę przed dalszą optymalizacją." | |
| (( level >= 20 )) && ok "Poziom baterii wystarczający do optymalizacji." | |
| fi | |
| pause | |
| } | |
| diag_ui_performance() { | |
| header "ANALIZA WYDAJNOŚCI UI (JANK / FRAME DROPS)" | |
| echo -e " ${YELLOW}Mierzę wydajność renderowania — dotknij ekranu zegarka podczas testu.${RESET}" | |
| step "Reset statystyk Gfxinfo" | |
| adb_cmd "dumpsys gfxinfo --reset" >/dev/null 2>&1 | |
| sleep 2 | |
| step "Czekam 5 sekund na zbieranie danych..." | |
| sleep 5 | |
| step "Odczyt statystyk klatek (SurfaceFlinger)" | |
| local gfx_out | |
| gfx_out=$(adb_get "dumpsys gfxinfo 2>/dev/null" | head -60) | |
| local total_frames janky_frames slow_frames | |
| total_frames=$(echo "$gfx_out" | grep -i "total frames" | awk '{print $NF}' | head -1) | |
| janky_frames=$(echo "$gfx_out" | grep -i "janky frames" | awk '{print $NF}' | head -1) | |
| slow_frames=$(echo "$gfx_out" | grep -i ">16ms" | awk '{print $NF}' | head -1) | |
| if [[ -n "$total_frames" && "$total_frames" -gt 0 ]] 2>/dev/null; then | |
| status_row "Całkowita liczba klatek" "$total_frames" | |
| status_row "Klatki z jankiem (>16ms)" "${janky_frames:-?}" | |
| local jank_pct=$ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # ╔══════════════════════════════════════════════════════════════════╗ | |
| # ║ Samsung Galaxy Watch 4 — Ultra Optimizer & Repair Suite ║ | |
| # ║ One UI 8.0 / WearOS 6.0 / Android 16 · SM-R870 · Exynos W920 ║ | |
| # ║ Wersja: 3.1.0 | Patch: 2026-02-23 ║ | |
| # ║ Fixes: jank% float bug, /system alert, AmbientDreamProxy, ║ | |
| # ║ GPS/GNSS killer, window_animation_scale, +debloat ║ | |
| # ╚══════════════════════════════════════════════════════════════════╝ | |
| # Bezpieczne, odwracalne, kompleksowe narzędzie do optymalizacji, | |
| # diagnostyki, debloatu i naprawy systemu bez ryzyka brick'a. | |
| # Bash strict mode — wyłączamy set -e bo potrzebujemy obsługi błędów per-komenda | |
| set -uo pipefail | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 0 — ZMIENNE GLOBALNE I STAŁE | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # Kolory terminala | |
| RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' | |
| CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' | |
| BLUE='\033[0;34m'; MAGENTA='\033[0;35m'; WHITE='\033[0;37m' | |
| BGREEN='\033[1;32m'; BRED='\033[1;31m'; BYELLOW='\033[1;33m' | |
| BCYAN='\033[1;36m'; DIM='\033[2m' | |
| # Metadane | |
| readonly SCRIPT_NAME="Watch4 Ultra Suite" | |
| readonly VERSION="3.1.0" | |
| readonly SCRIPT_DATE="2026-02-23" | |
| readonly LOG_DIR="/tmp/watch4_suite_logs" | |
| readonly BACKUP_DIR="${LOG_DIR}/backup_$(date +%Y%m%d_%H%M%S)" | |
| readonly LOG_FILE="${LOG_DIR}/session_$(date +%Y%m%d_%H%M%S).log" | |
| readonly ADB_TIMEOUT=15 | |
| readonly ADB_LONG_TIMEOUT=120 | |
| # Stan połączenia | |
| DEVICE_SERIAL="" | |
| DEVICE_MODEL="" | |
| DEVICE_ANDROID="" | |
| DEVICE_BUILD="" | |
| DEVICE_SDK="" | |
| # Flaga — czy backup ustawień wykonany w tej sesji | |
| BACKUP_DONE=false | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 1 — INFRASTRUKTURA: LOGGING, UI, BŁĘDY | |
| # ═══════════════════════════════════════════════════════════════════ | |
| mkdir -p "$LOG_DIR" "$BACKUP_DIR" | |
| ts() { date '+%H:%M:%S'; } | |
| log() { printf "[%s] %s\n" "$(ts)" "$*" >> "$LOG_FILE"; } | |
| ok() { printf "${GREEN} ✓ %s${RESET}\n" "$*"; log "OK: $*"; } | |
| err() { printf "${BRED} ✗ %s${RESET}\n" "$*" >&2; log "ERROR: $*"; } | |
| warn() { printf "${BYELLOW} ⚠ %s${RESET}\n" "$*"; log "WARN: $*"; } | |
| info() { printf "${CYAN} ℹ %s${RESET}\n" "$*"; log "INFO: $*"; } | |
| step() { printf "${BOLD}${WHITE} ▶ %s${RESET}\n" "$*"; log "STEP: $*"; } | |
| detail() { printf "${DIM} %s${RESET}\n" "$*"; log "DETAIL: $*"; } | |
| header() { | |
| local txt="$1" | |
| local width=60 | |
| local pad=$(( (width - ${#txt} - 2) / 2 )) | |
| echo | |
| printf "${BOLD}${BLUE}" | |
| printf '╔'; printf '═%.0s' $(seq 1 $((width-2))); printf '╗\n' | |
| printf '║%*s%s%*s║\n' $pad "" "$txt" $((width - pad - ${#txt} - 2)) "" | |
| printf '╚'; printf '═%.0s' $(seq 1 $((width-2))); printf '╝\n' | |
| printf "${RESET}" | |
| } | |
| sep() { printf "${BLUE}%s${RESET}\n" "$(printf '─%.0s' {1..60})"; } | |
| sep_thin() { printf "${DIM}%s${RESET}\n" "$(printf '·%.0s' {1..60})"; } | |
| status_row() { | |
| # status_row "Klucz" "Wartość" [kolor_wartości] | |
| local key="$1" val="$2" color="${3:-$CYAN}" | |
| printf " ${DIM}%-32s${RESET} ${color}%s${RESET}\n" "${key}:" "$val" | |
| } | |
| pause() { | |
| echo | |
| read -rp "$(printf " ${YELLOW}Naciśnij [Enter] aby kontynuować...${RESET}")" _ | |
| } | |
| confirm() { | |
| # confirm "Treść pytania" → 0 jeśli tak, 1 jeśli nie | |
| local msg="$1" | |
| local ans | |
| read -rp "$(printf " ${BYELLOW}%s [t/N]: ${RESET}" "$msg")" ans | |
| [[ "${ans,,}" == "t" || "${ans,,}" == "tak" ]] | |
| } | |
| spinner() { | |
| # spinner <pid> <wiadomość> | |
| local pid=$1 msg="$2" | |
| local spin=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') | |
| local i=0 | |
| while kill -0 "$pid" 2>/dev/null; do | |
| printf "\r ${CYAN}%s${RESET} %s " "${spin[$i]}" "$msg" | |
| i=$(( (i+1) % ${#spin[@]} )) | |
| sleep 0.1 | |
| done | |
| printf "\r%60s\r" " " | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 2 — WRAPPER ADB (bezpieczny, logujący) | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # Wykonaj komendę adb shell — zwraca kod wyjścia | |
| adb_cmd() { | |
| local cmd="$*" | |
| log "ADB_CMD: $cmd" | |
| local output exit_code=0 | |
| output=$(timeout "$ADB_TIMEOUT" adb -s "$DEVICE_SERIAL" shell "$cmd" 2>&1) || exit_code=$? | |
| log "ADB_OUT[$exit_code]: $output" | |
| echo "$output" | |
| return $exit_code | |
| } | |
| # Wykonaj długą komendę adb shell (dexopt, compile, itp.) | |
| adb_long() { | |
| local cmd="$*" | |
| log "ADB_LONG: $cmd" | |
| timeout "$ADB_LONG_TIMEOUT" adb -s "$DEVICE_SERIAL" shell "$cmd" 2>&1 | tee -a "$LOG_FILE" | |
| return "${PIPESTATUS[0]}" | |
| } | |
| # Pobierz wartość bez stderr | |
| adb_get() { | |
| timeout "$ADB_TIMEOUT" adb -s "$DEVICE_SERIAL" shell "$@" 2>/dev/null | tr -d '\r' | |
| } | |
| # Pobierz prop systemowy | |
| get_prop() { | |
| adb_get getprop "$1" 2>/dev/null || echo "" | |
| } | |
| # Ustawianie settings z walidacją i informowaniem | |
| safe_setting() { | |
| local namespace="$1" key="$2" value="$3" desc="${4:-}" | |
| local old_val | |
| old_val=$(adb_get "settings get ${namespace} ${key}" 2>/dev/null || echo "null") | |
| # Zapisz do backupu | |
| echo "settings put ${namespace} ${key} ${old_val}" >> "${BACKUP_DIR}/settings_restore.sh" | |
| if adb_cmd "settings put ${namespace} ${key} ${value}" >/dev/null 2>&1; then | |
| local label="${desc:-${namespace}/${key}}" | |
| ok "${label}: ${DIM}${old_val}${RESET} → ${GREEN}${value}${RESET}" | |
| return 0 | |
| else | |
| err "Nie udało się ustawić ${namespace}/${key}=${value}" | |
| return 1 | |
| fi | |
| } | |
| # Sprawdź czy urządzenie odpowiada | |
| ping_device() { | |
| timeout 5 adb -s "$DEVICE_SERIAL" shell "echo ok" 2>/dev/null | grep -q "ok" | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 3 — BANER I POŁĄCZENIE | |
| # ═══════════════════════════════════════════════════════════════════ | |
| print_banner() { | |
| clear | |
| printf "${BOLD}${MAGENTA}" | |
| cat << 'BANNER' | |
| ╔══════════════════════════════════════════════════════════════╗ | |
| ║ ║ | |
| ║ ██╗ ██╗ █████╗ ████████╗ ██████╗██╗ ██╗ ██╗ ██╗ ║ | |
| ║ ██║ ██║██╔══██╗╚══██╔══╝██╔════╝██║ ██║ ██║ ██║ ║ | |
| ║ ██║ █╗ ██║███████║ ██║ ██║ ███████║ ███████║ ║ | |
| ║ ██║███╗██║██╔══██║ ██║ ██║ ██╔══██║ ╚════██║ ║ | |
| ║ ╚███╔███╔╝██║ ██║ ██║ ╚██████╗██║ ██║ ██║ ║ | |
| ║ ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ║ | |
| ║ ║ | |
| ║ Samsung Galaxy Watch 4 — Ultra Optimizer Suite ║ | |
| ║ One UI 8.0 / WearOS 6.0 / Android 16 / SM-R870 ║ | |
| ╚══════════════════════════════════════════════════════════════╝ | |
| BANNER | |
| printf "${RESET}" | |
| printf " ${DIM}Wersja: ${VERSION} · ${SCRIPT_DATE} · Log: ${LOG_FILE}${RESET}\n" | |
| sep | |
| } | |
| check_adb_installed() { | |
| if ! command -v adb &>/dev/null; then | |
| err "ADB nie znalezione w PATH!" | |
| err "Zainstaluj: https://developer.android.com/tools/releases/platform-tools" | |
| exit 1 | |
| fi | |
| local adb_ver | |
| adb_ver=$(adb version 2>/dev/null | head -1) | |
| ok "$adb_ver" | |
| } | |
| connect_device() { | |
| header "POŁĄCZENIE Z ZEGARKIEM" | |
| # Sprawdź istniejące połączenia | |
| local existing | |
| existing=$(adb devices 2>/dev/null | grep -E "^[^\s]+\s+device$" | head -1 || true) | |
| if [[ -n "$existing" ]]; then | |
| DEVICE_SERIAL=$(echo "$existing" | awk '{print $1}') | |
| ok "Znaleziono aktywne połączenie: ${CYAN}${DEVICE_SERIAL}${RESET}" | |
| else | |
| echo | |
| info "Brak aktywnych połączeń. Podaj dane zegarka:" | |
| echo | |
| read -rp "$(printf " ${CYAN}Adres IP zegarka: ${RESET}")" WATCH_IP | |
| if ! [[ "$WATCH_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then | |
| err "Nieprawidłowy format IP."; exit 1 | |
| fi | |
| read -rp "$(printf " ${CYAN}Port [domyślnie 5555]: ${RESET}")" WATCH_PORT | |
| WATCH_PORT="${WATCH_PORT:-5555}" | |
| DEVICE_SERIAL="${WATCH_IP}:${WATCH_PORT}" | |
| info "Łączę z ${DEVICE_SERIAL}..." | |
| # Wymuś restart serwera ADB na wypadek zawieszenia | |
| adb kill-server &>/dev/null; sleep 1; adb start-server &>/dev/null | |
| local conn_out | |
| conn_out=$(adb connect "$DEVICE_SERIAL" 2>&1) | |
| log "ADB connect: $conn_out" | |
| if echo "$conn_out" | grep -qiE "connected to|already connected"; then | |
| ok "Połączono z ${DEVICE_SERIAL}" | |
| elif echo "$conn_out" | grep -qi "refused"; then | |
| err "Połączenie odrzucone — sprawdź:" | |
| err " 1. Zegarek i komputer w tej samej sieci Wi-Fi" | |
| err " 2. Ustawienia → Opcje programistyczne → Debugowanie ADB → WŁ" | |
| err " 3. Port ADB: Ustawienia → Opcje programistyczne → Debugowanie ADB po sieci" | |
| exit 1 | |
| else | |
| err "Nieznany błąd połączenia: $conn_out"; exit 1 | |
| fi | |
| fi | |
| # Weryfikacja stanu urządzenia | |
| local state | |
| state=$(timeout 5 adb -s "$DEVICE_SERIAL" get-state 2>&1 || echo "error") | |
| case "$state" in | |
| device) ok "Urządzenie gotowe (device)" ;; | |
| unauthorized) | |
| err "Nieautoryzowane! Zaakceptuj dialog na zegarku → uruchom skrypt ponownie." | |
| exit 1 ;; | |
| offline) | |
| err "Urządzenie offline. Sprawdź sieć lub uruchom: adb kill-server && adb start-server" | |
| exit 1 ;; | |
| *) | |
| err "Nieznany stan: $state"; exit 1 ;; | |
| esac | |
| # Pobierz informacje o urządzeniu | |
| DEVICE_MODEL=$(get_prop ro.product.model) | |
| DEVICE_ANDROID=$(get_prop ro.build.version.release) | |
| DEVICE_BUILD=$(get_prop ro.build.display.id) | |
| DEVICE_SDK=$(get_prop ro.build.version.sdk) | |
| local device_arch | |
| device_arch=$(get_prop ro.product.cpu.abi) | |
| sep | |
| status_row "Model" "$DEVICE_MODEL" | |
| status_row "Android" "$DEVICE_ANDROID (SDK $DEVICE_SDK)" | |
| status_row "Build ID" "$DEVICE_BUILD" | |
| status_row "Architektura" "$device_arch" | |
| status_row "Połączony przez" "$DEVICE_SERIAL" | |
| sep | |
| if ! echo "$DEVICE_MODEL" | grep -qi "R870\|Watch 4"; then | |
| warn "Model ($DEVICE_MODEL) różni się od docelowego SM-R870." | |
| warn "Skrypt może wymagać dostosowania dla tego urządzenia." | |
| fi | |
| if (( DEVICE_SDK < 35 )); then | |
| warn "SDK ${DEVICE_SDK} — niektóre komendy ART/Android 16 mogą nie działać." | |
| fi | |
| # Utwórz skrypt przywracania backupu | |
| cat > "${BACKUP_DIR}/settings_restore.sh" << 'RESTORE_HEADER' | |
| #!/usr/bin/env bash | |
| # AUTO-WYGENEROWANY skrypt przywracania ustawień | |
| # Uruchom: adb -s DEVICE shell "sh /sdcard/watch4_restore.sh" | |
| echo "Przywracanie ustawień Watch4 Suite..." | |
| RESTORE_HEADER | |
| log "Sesja połączona: $DEVICE_SERIAL | Model: $DEVICE_MODEL | Android: $DEVICE_ANDROID" | |
| # ── KRYTYCZNA KONTROLA: /system pełny (wykryto na SM-R870 + One UI 8.0) ── | |
| local sys_avail | |
| sys_avail=$(adb_get "df /system 2>/dev/null | tail -1 | awk '{print \$4}'" | tr -d ' \r' || echo "") | |
| if [[ "$sys_avail" =~ ^[0-9]+$ ]]; then | |
| if (( sys_avail < 51200 )); then | |
| echo | |
| printf "${BRED}╔══════════════════════════════════════════════════════════╗\n" | |
| printf "║ ⚠ KRYTYCZNE: PARTYCJA /system PRAWIE PEŁNA! ║\n" | |
| printf "║ Wolne: %-8s KB — ART nie może kompilować DEX! ║\n" "$sys_avail" | |
| printf "║ Zalecane: [3] Wydajność → [4] Czyść cache, potem restart ║\n" | |
| printf "╚══════════════════════════════════════════════════════════╝${RESET}\n" | |
| log "CRITICAL: /system available=${sys_avail} KB" | |
| fi | |
| fi | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 4 — DIAGNOSTYKA PROAKTYWNA | |
| # ═══════════════════════════════════════════════════════════════════ | |
| diagnostics_menu() { | |
| while true; do | |
| header "DIAGNOSTYKA PROAKTYWNA" | |
| echo " [1] Pełny raport systemu (snapshot)" | |
| echo " [2] Monitor temperatury w czasie rzeczywistym" | |
| echo " [3] Analiza zużycia RAM i procesów" | |
| echo " [4] Inspekcja baterii (health, wear, temperatury)" | |
| echo " [5] Analiza wydajności UI (jank, frame drops)" | |
| echo " [6] Sprawdzenie usług systemowych (status krytycznych)" | |
| echo " [7] Analiza logów systemowych (ANR, crash, OOM)" | |
| echo " [8] Inspekcja stanu sieci i Bluetooth" | |
| echo " [9] Eksportuj pełny raport diagnostyczny do pliku" | |
| echo " [0] Powrót" | |
| sep | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) diag_full_snapshot ;; | |
| 2) diag_thermal_monitor ;; | |
| 3) diag_ram_analysis ;; | |
| 4) diag_battery_health ;; | |
| 5) diag_ui_performance ;; | |
| 6) diag_system_services ;; | |
| 7) diag_log_analysis ;; | |
| 8) diag_network_bt ;; | |
| 9) diag_export_report ;; | |
| 0) return ;; | |
| *) warn "Nieprawidłowy wybór." ;; | |
| esac | |
| done | |
| } | |
| diag_full_snapshot() { | |
| header "PEŁNY RAPORT SYSTEMU" | |
| sep_thin | |
| step "Informacje systemowe" | |
| status_row "Model" "$(get_prop ro.product.model)" | |
| status_row "Android" "$(get_prop ro.build.version.release)" | |
| status_row "SDK" "$(get_prop ro.build.version.sdk)" | |
| status_row "Build" "$(get_prop ro.build.display.id)" | |
| status_row "CPU ABI" "$(get_prop ro.product.cpu.abi)" | |
| status_row "Chipset" "$(get_prop ro.hardware)" | |
| status_row "Uptime" "$(adb_get cat /proc/uptime | awk '{printf "%d min %d sek", $1/60, $1%60}')" | |
| sep_thin | |
| step "Pamięć RAM" | |
| local mem_total mem_avail mem_free mem_commit swap_total swap_free slab_unreclaim | |
| mem_total=$(adb_get "grep MemTotal /proc/meminfo | awk '{print \$2}'") | |
| mem_avail=$(adb_get "grep MemAvailable /proc/meminfo | awk '{print \$2}'") | |
| mem_commit=$(adb_get "grep Committed_AS /proc/meminfo | awk '{print \$2}'" || echo "0") | |
| swap_total=$(adb_get "grep SwapTotal /proc/meminfo | awk '{print \$2}'" || echo "0") | |
| swap_free=$(adb_get "grep SwapFree /proc/meminfo | awk '{print \$2}'" || echo "0") | |
| slab_unreclaim=$(adb_get "grep SUnreclaim /proc/meminfo | awk '{print \$2}'" || echo "0") | |
| local mem_used=$(( (mem_total - mem_avail) / 1024 )) | |
| local mem_total_mb=$(( mem_total / 1024 )) | |
| local mem_avail_mb=$(( mem_avail / 1024 )) | |
| local commit_mb=$(( ${mem_commit:-0} / 1024 )) | |
| local swap_used_mb=$(( (${swap_total:-0} - ${swap_free:-0}) / 1024 )) | |
| local slab_mb=$(( ${slab_unreclaim:-0} / 1024 )) | |
| local ram_color="$GREEN" | |
| (( mem_avail_mb < 250 )) && ram_color="$YELLOW" | |
| (( mem_avail_mb < 100 )) && ram_color="$RED" | |
| status_row "RAM łącznie" "${mem_total_mb} MB" | |
| status_row "RAM dostępna" "${mem_avail_mb} MB" "$ram_color" | |
| status_row "RAM użyta (est.)" "${mem_used} MB" | |
| status_row "SWAP/ZRAM użyty" "${swap_used_mb} MB / $(( ${swap_total:-0} / 1024 )) MB" | |
| status_row "Kernel SUnreclaim" "${slab_mb} MB" | |
| if [[ "$commit_mb" -gt 0 && "$mem_total_mb" -gt 0 ]] 2>/dev/null; then | |
| local overcommit_ratio | |
| overcommit_ratio=$(awk "BEGIN {printf \"%.1f\", ${commit_mb} / ${mem_total_mb}}") | |
| status_row "Committed_AS" "${commit_mb} MB (${overcommit_ratio}x fizycznego RAM)" | |
| local oc_int | |
| oc_int=$(awk "BEGIN {printf \"%d\", ${commit_mb} / ${mem_total_mb}}") | |
| (( oc_int > 10 )) && warn "Overcommit >${oc_int}x — LMK agresywnie zabija procesy tła!" | |
| fi | |
| sep_thin | |
| step "Miejsce na dysku" | |
| adb_get "df /data /cache /system 2>/dev/null" | while IFS= read -r line; do | |
| printf " ${DIM}%s${RESET}\n" "$line" | |
| done | |
| sep_thin | |
| step "Temperatura CPU (przybliżona)" | |
| local temps | |
| temps=$(adb_get "cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | head -5" || echo "niedostępne") | |
| echo "$temps" | head -5 | while IFS= read -r t; do | |
| if [[ "$t" =~ ^[0-9]+$ ]]; then | |
| local deg=$(( t / 1000 )) | |
| local color="$GREEN" | |
| (( deg > 45 )) && color="$YELLOW" | |
| (( deg > 55 )) && color="$RED" | |
| printf " ${color}%d°C${RESET}\n" "$deg" | |
| fi | |
| done | |
| sep_thin | |
| step "Akumulator" | |
| adb_get "dumpsys battery 2>/dev/null" | grep -E "level|status|health|temperature|voltage|plugged" | while IFS= read -r line; do | |
| printf " ${CYAN}%s${RESET}\n" "$line" | |
| done | |
| sep_thin | |
| step "Animacje systemowe" | |
| for key in window_animation_scale transition_animation_scale animator_duration_scale; do | |
| local val | |
| val=$(adb_get "settings get global $key") | |
| status_row "$key" "$val" | |
| done | |
| step "AOD Status" | |
| local aod_val | |
| aod_val=$(adb_get "settings get secure doze_always_on") | |
| local aod_label="Wyłączone" | |
| [[ "$aod_val" == "1" ]] && aod_label="Włączone" | |
| status_row "doze_always_on" "$aod_val ($aod_label)" | |
| pause | |
| } | |
| diag_thermal_monitor() { | |
| header "MONITOR TEMPERATURY CZASU RZECZYWISTEGO" | |
| echo -e " ${YELLOW}Monitorowanie przez 30 sekund. Ctrl+C aby przerwać wcześniej.${RESET}" | |
| echo | |
| local zones | |
| zones=$(adb_get "ls /sys/class/thermal/ 2>/dev/null" | grep thermal_zone | head -8) | |
| for i in $(seq 1 30); do | |
| printf "\r ${BOLD}[%2d/30s]${RESET} " "$i" | |
| while IFS= read -r zone; do | |
| local temp | |
| temp=$(adb_get "cat /sys/class/thermal/${zone}/temp 2>/dev/null" || echo "0") | |
| if [[ "$temp" =~ ^[0-9]+$ ]]; then | |
| local deg=$(( temp / 1000 )) | |
| local color="$GREEN" | |
| (( deg > 40 )) && color="$YELLOW" | |
| (( deg > 50 )) && color="$RED" | |
| printf "${color}%d°C${RESET} " "$deg" | |
| fi | |
| done <<< "$zones" | |
| sleep 1 | |
| done | |
| echo | |
| ok "Monitorowanie zakończone." | |
| pause | |
| } | |
| diag_ram_analysis() { | |
| header "ANALIZA RAM I PROCESÓW" | |
| sep_thin | |
| step "Top 10 procesów według RAM" | |
| echo | |
| printf " ${BOLD}%-8s %-12s %-8s %s${RESET}\n" "PID" "RSS(MB)" "USER" "NAZWA" | |
| sep_thin | |
| adb_get "ps -eo pid,rss,user,comm 2>/dev/null | sort -k2 -rn | head -10" | while IFS= read -r line; do | |
| local pid rss user comm | |
| read -r pid rss user comm <<< "$line" | |
| if [[ "$rss" =~ ^[0-9]+$ ]]; then | |
| local rss_mb=$(( rss / 1024 )) | |
| local color="$WHITE" | |
| (( rss_mb > 100 )) && color="$YELLOW" | |
| (( rss_mb > 200 )) && color="$RED" | |
| printf " ${DIM}%-8s${RESET} ${color}%-12s${RESET} %-8s %s\n" "$pid" "${rss_mb} MB" "$user" "$comm" | |
| fi | |
| done | |
| sep_thin | |
| step "Statystyki LMK (Low Memory Killer)" | |
| adb_get "cat /sys/module/lowmemorykiller/parameters/minfree 2>/dev/null" | while IFS= read -r line; do | |
| detail "LMK minfree: $line" | |
| done | |
| sep_thin | |
| step "Ostatnie zabite procesy (OOM)" | |
| adb_get "logcat -d -t 50 --pid 1 2>/dev/null" | grep -i "lowmem\|oom_kill\|killing\|killed" | tail -5 | while IFS= read -r line; do | |
| printf " ${RED}%s${RESET}\n" "$line" | |
| done || detail "Brak wpisów OOM w ostatnich logach." | |
| pause | |
| } | |
| diag_battery_health() { | |
| header "INSPEKCJA BATERII" | |
| sep_thin | |
| step "Podstawowe dane" | |
| adb_get "dumpsys battery 2>/dev/null" | while IFS= read -r line; do | |
| local key val | |
| IFS=':' read -r key val <<< "$line" | |
| key=$(echo "$key" | xargs) | |
| val=$(echo "$val" | xargs) | |
| [[ -z "$key" || -z "$val" ]] && continue | |
| case "$key" in | |
| level) | |
| local color="$GREEN" | |
| (( val < 30 )) && color="$YELLOW" | |
| (( val < 15 )) && color="$RED" | |
| status_row "Poziom" "${val}%" "$color" | |
| ;; | |
| health) | |
| local h_label | |
| case "$val" in | |
| 2) h_label="Dobry ✓" ;; | |
| 3) h_label="Przegrzanie !" ;; | |
| 4) h_label="Martwy ✗" ;; | |
| *) h_label="Status: $val" ;; | |
| esac | |
| status_row "Kondycja" "$h_label" | |
| ;; | |
| temperature) | |
| local temp_c | |
| temp_c=$(echo "scale=1; $val / 10" | bc 2>/dev/null || echo "${val}/10") | |
| status_row "Temperatura" "${temp_c}°C" | |
| ;; | |
| voltage) status_row "Napięcie" "${val} mV" ;; | |
| status) | |
| local s_label | |
| case "$val" in | |
| 1) s_label="Nieznany" ;; 2) s_label="Ładowanie" ;; | |
| 3) s_label="Rozładowywanie" ;; 5) s_label="Pełny" ;; | |
| *) s_label="$val" ;; | |
| esac | |
| status_row "Status" "$s_label" | |
| ;; | |
| *) status_row "$key" "$val" ;; | |
| esac | |
| done | |
| sep_thin | |
| step "Historia zużycia baterii (ostatnie 24h)" | |
| adb_get "dumpsys batterystats 2>/dev/null" | grep -E "Discharge|charge|plugged|screen" | head -10 | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| sep_thin | |
| step "Rekomendacje" | |
| local level | |
| level=$(adb_get "dumpsys battery 2>/dev/null | grep level | awk -F: '{print \$2}' | tr -d ' \r'") | |
| if [[ "$level" =~ ^[0-9]+$ ]]; then | |
| (( level < 20 )) && warn "Niski poziom baterii — podłącz ładowarkę przed dalszą optymalizacją." | |
| (( level >= 20 )) && ok "Poziom baterii wystarczający do optymalizacji." | |
| fi | |
| pause | |
| } | |
| diag_ui_performance() { | |
| header "ANALIZA WYDAJNOŚCI UI (JANK / FRAME DROPS)" | |
| echo -e " ${YELLOW}Mierzę wydajność renderowania — dotknij ekranu zegarka podczas testu.${RESET}" | |
| step "Reset statystyk Gfxinfo" | |
| adb_cmd "dumpsys gfxinfo --reset" >/dev/null 2>&1 | |
| sleep 2 | |
| step "Czekam 5 sekund na zbieranie danych..." | |
| sleep 5 | |
| step "Odczyt statystyk klatek (SurfaceFlinger)" | |
| local gfx_out | |
| gfx_out=$(adb_get "dumpsys gfxinfo 2>/dev/null" | head -60) | |
| local total_frames janky_frames slow_frames | |
| total_frames=$(echo "$gfx_out" | grep -i "total frames" | awk '{print $NF}' | head -1) | |
| janky_frames=$(echo "$gfx_out" | grep -i "janky frames" | awk '{print $NF}' | head -1) | |
| slow_frames=$(echo "$gfx_out" | grep -i ">16ms" | awk '{print $NF}' | head -1) | |
| if [[ -n "$total_frames" && "$total_frames" -gt 0 ]] 2>/dev/null; then | |
| status_row "Całkowita liczba klatek" "$total_frames" | |
| # BUGFIX v3.1: gfxinfo zwraca float (np. "43.48%") — użyj awk zamiast $(( )) | |
| local janky_clean | |
| janky_clean=$(echo "${janky_frames:-0}" | grep -oE '[0-9]+' | head -1) | |
| local jank_pct_raw | |
| jank_pct_raw=$(awk "BEGIN {printf \"%.1f\", ${janky_clean:-0} * 100 / ${total_frames}}") | |
| local jank_pct_int | |
| jank_pct_int=$(awk "BEGIN {printf \"%d\", ${janky_clean:-0} * 100 / ${total_frames}}") | |
| status_row "Klatki z jankiem (>16ms)" "${janky_clean:-?} (${jank_pct_raw}%)" | |
| local color="$GREEN" | |
| (( jank_pct_int > 5 )) && color="$YELLOW" | |
| (( jank_pct_int > 15 )) && color="$RED" | |
| status_row "Procent janku" "${jank_pct_raw}%" "$color" | |
| (( jank_pct_int > 10 )) && warn "Wykryto znaczny jank (${jank_pct_raw}%) — zalecana optymalizacja animacji i ART." | |
| (( jank_pct_int <= 5 )) && ok "Wydajność UI dobra (jank ${jank_pct_raw}%)." | |
| else | |
| info "Niewystarczające dane z gfxinfo. Sprawdź SurfaceFlinger:" | |
| adb_get "dumpsys SurfaceFlinger --latency 2>/dev/null" | head -15 | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| fi | |
| sep_thin | |
| step "Dispatching latency (Input)" | |
| adb_get "dumpsys input 2>/dev/null" | grep -E "latency|delay|drop" | head -5 | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| pause | |
| } | |
| diag_system_services() { | |
| header "STATUS USŁUG SYSTEMOWYCH" | |
| local critical_services=( | |
| "SurfaceFlinger:Rendering/UI" | |
| "WindowManagerService:Zarządzanie oknami" | |
| "ActivityManagerService:Zarządzanie aplikacjami" | |
| "PackageManagerService:Zarządzanie pakietami" | |
| "PowerManagerService:Zarządzanie energią" | |
| "ThermalManagerService:Zarządzanie cieplne" | |
| "WifiService:Wi-Fi" | |
| "BluetoothManagerService:Bluetooth" | |
| ) | |
| sep_thin | |
| printf " ${BOLD}%-28s %-20s %s${RESET}\n" "USŁUGA" "OPIS" "STATUS" | |
| sep_thin | |
| for entry in "${critical_services[@]}"; do | |
| local svc="${entry%%:*}" | |
| local desc="${entry##*:}" | |
| local found | |
| found=$(adb_get "service check ${svc} 2>/dev/null" | grep -ic "found" || echo "0") | |
| if [[ "$found" -gt 0 ]] 2>/dev/null; then | |
| printf " ${BOLD}%-28s${RESET} %-20s ${GREEN}● Aktywna${RESET}\n" "$svc" "$desc" | |
| else | |
| printf " ${BOLD}%-28s${RESET} %-20s ${RED}✗ Nieaktywna${RESET}\n" "$svc" "$desc" | |
| fi | |
| done | |
| sep_thin | |
| step "Procesy w stanie ZOMBIE lub DEAD" | |
| adb_get "ps -A 2>/dev/null | grep -E 'Z|D' | grep -v grep | head -10" | while IFS= read -r line; do | |
| warn "$line" | |
| done || detail "Brak procesów zombie." | |
| pause | |
| } | |
| diag_log_analysis() { | |
| header "ANALIZA LOGÓW SYSTEMOWYCH" | |
| step "ANR (Application Not Responding) — ostatnie zdarzenia" | |
| adb_get "logcat -d -t 200 2>/dev/null" | grep -i "ANR\|anr" | tail -5 | while IFS= read -r line; do | |
| printf " ${RED}%s${RESET}\n" "$line" | |
| done || detail "Brak ANR w buforze logów." | |
| sep_thin | |
| step "Crash / Wyjątki (ostatnie 5)" | |
| adb_get "logcat -d -t 200 2>/dev/null" | grep -iE "FATAL|crash|exception|NullPointer" | tail -5 | while IFS= read -r line; do | |
| printf " ${BRED}%s${RESET}\n" "$line" | |
| done || detail "Brak crashy w buforze." | |
| sep_thin | |
| step "OOM / Duszenie pamięci" | |
| adb_get "logcat -d -t 200 2>/dev/null" | grep -iE "lowmem|oom|kill" | tail -5 | while IFS= read -r line; do | |
| printf " ${YELLOW}%s${RESET}\n" "$line" | |
| done || detail "Brak wpisów OOM." | |
| sep_thin | |
| step "Throttling termiczny" | |
| adb_get "logcat -d -t 200 2>/dev/null" | grep -iE "thermal|throttl|overheat" | tail -5 | while IFS= read -r line; do | |
| printf " ${YELLOW}%s${RESET}\n" "$line" | |
| done || detail "Brak wpisów throttlingu." | |
| sep_thin | |
| step "Błędy SurfaceFlinger / HWUI" | |
| adb_get "logcat -d -t 200 -s SurfaceFlinger:W HWUI:W 2>/dev/null" | tail -10 | while IFS= read -r line; do | |
| printf " ${YELLOW}%s${RESET}\n" "$line" | |
| done || detail "Brak błędów renderowania." | |
| pause | |
| } | |
| diag_network_bt() { | |
| header "SIEĆ I BLUETOOTH" | |
| step "Status Wi-Fi" | |
| adb_get "dumpsys wifi 2>/dev/null" | grep -E "SSID|BSSID|signal|state|freq|speed|IP" | head -10 | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| sep_thin | |
| step "Status Bluetooth" | |
| adb_get "dumpsys bluetooth_manager 2>/dev/null" | grep -E "state|enabled|profile|address" | head -8 | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| sep_thin | |
| step "Połączone urządzenia BT" | |
| adb_get "dumpsys bluetooth_manager 2>/dev/null" | grep -E "name|class|connected" | head -6 | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| pause | |
| } | |
| diag_export_report() { | |
| local report_file="${LOG_DIR}/diagnostic_report_$(date +%Y%m%d_%H%M%S).txt" | |
| step "Generuję pełny raport diagnostyczny..." | |
| { | |
| echo "=== Watch4 Ultra Suite — Raport Diagnostyczny ===" | |
| echo "Data: $(date)" | |
| echo "Urządzenie: ${DEVICE_SERIAL}" | |
| echo | |
| echo "--- SYSTEM INFO ---" | |
| adb_get "getprop" 2>/dev/null | grep -E "ro\.(product|build|hardware|system)" | head -30 | |
| echo | |
| echo "--- MEMINFO ---" | |
| adb_get "cat /proc/meminfo" | |
| echo | |
| echo "--- BATTERY ---" | |
| adb_get "dumpsys battery" | |
| echo | |
| echo "--- GFXINFO ---" | |
| adb_get "dumpsys gfxinfo" | head -60 | |
| echo | |
| echo "--- TOP PROCESSES ---" | |
| adb_get "ps -eo pid,rss,comm | sort -k2 -rn | head -20" | |
| echo | |
| echo "--- THERMAL ---" | |
| adb_get "dumpsys thermalservice" 2>/dev/null | head -30 || echo "niedostępne" | |
| echo | |
| echo "--- LOGCAT ERRORS ---" | |
| adb_get "logcat -d -t 100 *:E" 2>/dev/null | head -50 | |
| } > "$report_file" & | |
| spinner $! "Zbieranie danych..." | |
| ok "Raport zapisany: ${report_file}" | |
| # Prześlij na zegarek (opcjonalnie) | |
| if confirm "Skopiować raport na zegarek (/sdcard/watch4_report.txt)?"; then | |
| adb push "$report_file" "/sdcard/watch4_report.txt" >/dev/null 2>&1 && ok "Raport na zegarku: /sdcard/watch4_report.txt" | |
| fi | |
| pause | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 5 — ZARZĄDZANIE AOD | |
| # ═══════════════════════════════════════════════════════════════════ | |
| aod_menu() { | |
| while true; do | |
| header "ZARZĄDZANIE AOD (Always-On Display)" | |
| # Status | |
| local aod_val doze_wakeup ambient_peekoff | |
| aod_val=$(adb_get "settings get secure doze_always_on") | |
| doze_wakeup=$(adb_get "settings get secure doze_wake_display_on_gesture") | |
| ambient_peekoff=$(adb_get "settings get secure doze_pick_up_gesture") | |
| status_row "AOD aktywne (doze_always_on)" "${aod_val:-null}" | |
| status_row "Wybudzenie gestem" "${doze_wakeup:-null}" | |
| status_row "Podniesienie nadgarstka" "${ambient_peekoff:-null}" | |
| sep_thin | |
| echo " [1] Wyłącz AOD (zalecane — eliminuje CPU spike przy wybudzeniu)" | |
| echo " [2] Włącz AOD" | |
| echo " [3] Tryb SMART — AOD tylko przy podniesieniu nadgarstka" | |
| echo " [4] Dezaktywuj komponenty Samsung AOD (agresywnie)" | |
| echo " [5] Przywróć komponenty AOD (cofnij opcję 4)" | |
| echo " [6] Zabij AmbientDreamProxy wakelock (NOWE — fix lagów wybudzenia)" | |
| echo " [7] Zarządzanie GPS/GNSS (wakeup killer)" | |
| echo " [8] Optymalizacja timeoutów ekranu" | |
| echo " [0] Powrót" | |
| sep | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) | |
| safe_setting "secure" "doze_always_on" "0" "AOD" | |
| safe_setting "secure" "doze_wake_display_on_gesture" "0" "Wybudzenie gestem" | |
| ok "AOD wyłączone. Eliminuje ~150-300ms opóźnienie wybudzenia Exynos W920." | |
| ;; | |
| 2) | |
| safe_setting "secure" "doze_always_on" "1" "AOD" | |
| ok "AOD włączone." | |
| ;; | |
| 3) | |
| safe_setting "secure" "doze_always_on" "0" "AOD" | |
| safe_setting "secure" "doze_wake_display_on_gesture" "1" "Wybudzenie gestem" | |
| safe_setting "secure" "doze_pick_up_gesture" "1" "Podniesienie nadgarstka" | |
| ok "Tryb SMART: AOD nieaktywne, ekran budzi się na gest/podniesienie." | |
| ;; | |
| 4) aod_disable_components ;; | |
| 5) aod_restore_components ;; | |
| 6) aod_kill_ambient_proxy ;; | |
| 7) aod_gps_control ;; | |
| 8) aod_screen_timeouts ;; | |
| 0) return ;; | |
| *) warn "Nieprawidłowy wybór." ;; | |
| esac | |
| pause | |
| done | |
| } | |
| # Pakiety Samsung AOD — bezpieczne do dezaktywacji (nie odinstalowania) | |
| declare -a SAMSUNG_AOD_COMPONENTS=( | |
| "com.samsung.android.app.aodservice" | |
| "com.samsung.systemui.aod" | |
| "com.samsung.android.app.watchface.aod" | |
| ) | |
| aod_disable_components() { | |
| header "DEZAKTYWACJA KOMPONENTÓW AOD" | |
| warn "Ta opcja dezaktywuje systemowe serwisy AOD Samsung." | |
| warn "System przełączy się na standardowy tryb doze WearOS." | |
| echo | |
| if ! confirm "Kontynuować?"; then | |
| info "Anulowano."; return | |
| fi | |
| for pkg in "${SAMSUNG_AOD_COMPONENTS[@]}"; do | |
| local pm_state | |
| pm_state=$(adb_get "pm list packages -d 2>/dev/null | grep ${pkg}" || echo "") | |
| if adb_cmd "pm disable-user --user 0 ${pkg}" >/dev/null 2>&1; then | |
| ok "Dezaktywowano: ${pkg}" | |
| else | |
| # Może już wyłączony lub nieistnieje | |
| warn "Pominięto (może nie istnieć): ${pkg}" | |
| fi | |
| done | |
| # Wymuś AOD standard WearOS | |
| safe_setting "secure" "doze_always_on" "0" "AOD (standard WearOS doze)" | |
| safe_setting "global" "always_finish_activities" "0" "Finalizacja aktywności" | |
| ok "Komponenty AOD Samsung dezaktywowane. Standardowy doze WearOS aktywny." | |
| info "Przywróć opcją [5] jeśli pojawią się problemy." | |
| } | |
| aod_restore_components() { | |
| header "PRZYWRACANIE KOMPONENTÓW AOD" | |
| for pkg in "${SAMSUNG_AOD_COMPONENTS[@]}"; do | |
| if adb_cmd "pm enable ${pkg}" >/dev/null 2>&1; then | |
| ok "Przywrócono: ${pkg}" | |
| else | |
| warn "Nie można przywrócić (lub nie istnieje): ${pkg}" | |
| fi | |
| done | |
| safe_setting "secure" "doze_always_on" "1" "AOD" | |
| ok "Komponenty AOD przywrócone." | |
| } | |
| aod_kill_ambient_proxy() { | |
| header "KILL AmbientDreamProxy WAKELOCK" | |
| echo -e " ${BYELLOW}Diagnoza z Twoich logów (14:02–14:03):${RESET}" | |
| echo -e " ${DIM}+wake_lock=u0a40:\"AmbientDreamProxy\" budzący urządzenie co ~10s${RESET}" | |
| echo -e " ${DIM}mimo doze_always_on=0. Powoduje lagi przy wybudzeniu + drenaż baterii.${RESET}" | |
| echo | |
| step "Zatrzymuję serwisy AmbientDreamProxy..." | |
| local proxy_pkgs=( | |
| "com.samsung.android.app.aodservice" | |
| "com.samsung.systemui.aod" | |
| "com.samsung.android.app.watchface.aod" | |
| ) | |
| for pkg in "${proxy_pkgs[@]}"; do | |
| if adb_cmd "am force-stop ${pkg}" >/dev/null 2>&1; then | |
| ok "force-stop: ${pkg}" | |
| else | |
| warn "Pominięto (może nie istnieć): ${pkg}" | |
| fi | |
| done | |
| step "Dezaktywuję wakelock trigger dla TILT gesture..." | |
| safe_setting "secure" "doze_wake_display_on_gesture" "0" "Wybudzenie gestem TILT" | |
| safe_setting "secure" "doze_pick_up_gesture" "0" "Podniesienie nadgarstka (doze)" | |
| safe_setting "secure" "doze_pulse_on_pick_up" "0" "Pulse on pick-up" | |
| safe_setting "secure" "doze_pulse_on_double_tap" "0" "Pulse on double-tap" | |
| step "Blokuję AmbientDreamProxy przez AppOps..." | |
| adb_cmd "appops set com.samsung.android.app.aodservice WAKE_LOCK deny" >/dev/null 2>&1 && \ | |
| ok "AppOps WAKE_LOCK: denied dla aodservice" || \ | |
| warn "AppOps — pominięto (może wymagać uprawnień)." | |
| step "Sprawdzam aktywne wakelocki po operacji..." | |
| local remaining | |
| remaining=$(adb_get "dumpsys power 2>/dev/null | grep -i 'AmbientDream'" || echo "") | |
| if [[ -z "$remaining" ]]; then | |
| ok "AmbientDreamProxy nie widoczny w aktywnych wakélockach." | |
| else | |
| warn "AmbientDreamProxy nadal obecny: ${remaining}" | |
| warn "Rozważ opcję [4] Dezaktywuj komponenty AOD dla trwałego efektu." | |
| fi | |
| info "Efekt utrzymuje się do następnego restartu. Dla trwałego — użyj opcji [4]." | |
| pause | |
| } | |
| aod_gps_control() { | |
| header "ZARZĄDZANIE GPS/GNSS (wakeup killer)" | |
| echo -e " ${BYELLOW}Diagnoza z Twoich logów:${RESET}" | |
| echo -e " ${DIM}wake_reason: \"12990000.gnss_mailbox\" — GNSS budzi CPU co kilka minut${RESET}" | |
| echo -e " ${DIM}nawet gdy żadna aplikacja nie używa GPS aktywnie.${RESET}" | |
| echo | |
| local gps_mode | |
| gps_mode=$(adb_get "settings get secure location_mode" || echo "?") | |
| local gps_label | |
| case "$gps_mode" in | |
| 0) gps_label="Wyłączone" ;; | |
| 1) gps_label="Tylko sensory (bez sieci)" ;; | |
| 2) gps_label="Tylko sieć (bez GPS)" ;; | |
| 3) gps_label="Wysoka dokładność (GPS + sieć)" ;; | |
| *) gps_label="Nieznany ($gps_mode)" ;; | |
| esac | |
| status_row "Obecny tryb lokalizacji" "$gps_label" | |
| echo | |
| echo " [1] Wyłącz GPS całkowicie (0) — eliminuje wakeup gnss_mailbox" | |
| echo " [2] Tylko sieć (2) — lokalizacja przez Wi-Fi bez GNSS hardware" | |
| echo " [3] Wysoka dokładność (3) — przywróć pełne GPS" | |
| echo " [4] Zatrzymaj proces GPS (tymczasowo)" | |
| echo " [0] Powrót" | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) | |
| safe_setting "secure" "location_mode" "0" "GPS (location_mode)" | |
| safe_setting "secure" "location_providers_allowed" "" "GPS providers" | |
| ok "GPS wyłączony. GNSS wakeup wyeliminowany." | |
| warn "Funkcje fitness wymagające GPS (np. trasy na świeżym powietrzu) będą niedostępne." | |
| ;; | |
| 2) | |
| safe_setting "secure" "location_mode" "2" "GPS (location_mode)" | |
| ok "Tryb sieciowy — lokalizacja bez budzenia GNSS hardware." | |
| ;; | |
| 3) | |
| safe_setting "secure" "location_mode" "3" "GPS (location_mode)" | |
| ok "Pełne GPS przywrócone." | |
| ;; | |
| 4) | |
| adb_cmd "am force-stop com.google.android.gms.location" >/dev/null 2>&1 || true | |
| adb_cmd "settings put secure location_providers_allowed -gps" >/dev/null 2>&1 | |
| ok "GPS tymczasowo zatrzymany — efekt do restartu urządzenia." | |
| ;; | |
| 0) return ;; | |
| esac | |
| pause | |
| } | |
| aod_screen_timeouts() { | |
| header "OPTYMALIZACJA TIMEOUTÓW EKRANU" | |
| local cur_timeout | |
| cur_timeout=$(adb_get "settings get system screen_off_timeout") | |
| status_row "Aktualny timeout" "${cur_timeout} ms ($(( ${cur_timeout:-15000} / 1000 ))s)" | |
| echo | |
| echo " [1] 5 sekund (agresywne oszczędzanie)" | |
| echo " [2] 10 sekund (zalecane dla WearOS)" | |
| echo " [3] 15 sekund (domyślne)" | |
| echo " [4] 30 sekund" | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) safe_setting "system" "screen_off_timeout" "5000" "Timeout ekranu" ;; | |
| 2) safe_setting "system" "screen_off_timeout" "10000" "Timeout ekranu" ;; | |
| 3) safe_setting "system" "screen_off_timeout" "15000" "Timeout ekranu" ;; | |
| 4) safe_setting "system" "screen_off_timeout" "30000" "Timeout ekranu" ;; | |
| *) warn "Nieprawidłowy wybór." ;; | |
| esac | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 6 — OPTYMALIZACJA WYDAJNOŚCI | |
| # ═══════════════════════════════════════════════════════════════════ | |
| performance_menu() { | |
| while true; do | |
| header "OPTYMALIZACJA WYDAJNOŚCI" | |
| echo " [1] Animacje systemowe" | |
| echo " [2] Renderer GPU (Exynos W920 — Vulkan/OpenGL)" | |
| echo " [3] Kompilacja ART / DEX (bg-dexopt, speed-profile)" | |
| echo " [4] Czyszczenie pamięci podręcznej" | |
| echo " [5] Zarządzanie CPU Governor" | |
| echo " [6] Optymalizacja Virtual Machine (Dalvik/ART flags)" | |
| echo " [7] Profil TURBO (wszystkie optymalizacje)" | |
| echo " [8] Przywróć ustawienia domyślne wydajności" | |
| echo " [0] Powrót" | |
| sep | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) manage_animations ;; | |
| 2) manage_gpu ;; | |
| 3) optimize_art ;; | |
| 4) clear_caches ;; | |
| 5) manage_cpu_governor ;; | |
| 6) optimize_vm_flags ;; | |
| 7) apply_turbo_profile ;; | |
| 8) restore_defaults_perf ;; | |
| 0) return ;; | |
| *) warn "Nieprawidłowy wybór." ;; | |
| esac | |
| done | |
| } | |
| manage_animations() { | |
| header "ANIMACJE SYSTEMOWE" | |
| local wa ts ad | |
| wa=$(adb_get "settings get global window_animation_scale") | |
| ts=$(adb_get "settings get global transition_animation_scale") | |
| ad=$(adb_get "settings get global animator_duration_scale") | |
| status_row "window_animation_scale" "$wa" | |
| status_row "transition_animation_scale" "$ts" | |
| status_row "animator_duration_scale" "$ad" | |
| sep_thin | |
| echo " [1] Wyłączone (0.0) — max responsywność, brak płynnych przejść" | |
| echo " [2] 0.3x — szybkie, prawie niezauważalne" | |
| echo " [3] 0.5x — zalecane dla WearOS" | |
| echo " [4] 1.0x — domyślne" | |
| echo " [5] Niestandardowe" | |
| echo " [0] Powrót" | |
| read -rp "$(printf " Wybór: ")" ch | |
| set_animations() { | |
| local v="$1" | |
| safe_setting "global" "window_animation_scale" "$v" "window_animation_scale" | |
| safe_setting "global" "transition_animation_scale" "$v" "transition_animation_scale" | |
| safe_setting "global" "animator_duration_scale" "$v" "animator_duration_scale" | |
| } | |
| case "$ch" in | |
| 1) set_animations "0.0" ;; | |
| 2) set_animations "0.3" ;; | |
| 3) set_animations "0.5" ;; | |
| 4) set_animations "1.0" ;; | |
| 5) | |
| read -rp "$(printf " Podaj wartość: ")" cv | |
| [[ "$cv" =~ ^[0-9]+(\.[0-9]+)?$ ]] && set_animations "$cv" || err "Nieprawidłowa wartość." | |
| ;; | |
| 0) return ;; | |
| esac | |
| pause | |
| } | |
| manage_gpu() { | |
| header "RENDERER GPU (EXYNOS W920)" | |
| local cur_renderer | |
| cur_renderer=$(adb_get "getprop debug.hwui.renderer 2>/dev/null"; echo "") | |
| local cur_pipeline | |
| cur_pipeline=$(adb_get "getprop debug.hwui.use_vulkan 2>/dev/null"; echo "") | |
| status_row "debug.hwui.renderer" "${cur_renderer:-domyślny}" | |
| status_row "debug.hwui.use_vulkan" "${cur_pipeline:-domyślny}" | |
| sep_thin | |
| echo " [1] Skia Vulkan (skiavk) — zalecane Exynos W920 + Android 16" | |
| echo " [2] Skia OpenGL (skiagl) — stabilniejszy fallback" | |
| echo " [3] Wymuś Vulkan pipeline (hwui.use_vulkan=1)" | |
| echo " [4] Wyłącz debug overdraw" | |
| echo " [5] Przywróć domyślny renderer" | |
| echo " [0] Powrót" | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) | |
| adb_cmd "setprop debug.hwui.renderer skiavk" >/dev/null | |
| ok "Renderer: Skia Vulkan — restart aplikacji wymagany dla efektu." | |
| warn "Jeśli pojawią się artefakty graficzne → użyj opcji [5]." | |
| ;; | |
| 2) | |
| adb_cmd "setprop debug.hwui.renderer skiagl" >/dev/null | |
| ok "Renderer: Skia OpenGL." | |
| ;; | |
| 3) | |
| adb_cmd "setprop debug.hwui.use_vulkan 1" >/dev/null | |
| ok "Vulkan pipeline wymuszony." | |
| ;; | |
| 4) | |
| adb_cmd "setprop debug.hwui.overdraw false" >/dev/null | |
| adb_cmd "setprop debug.hwui.show_dirty_regions false" >/dev/null | |
| ok "Debug overdraw wyłączony." | |
| ;; | |
| 5) | |
| adb_cmd "setprop debug.hwui.renderer ''" >/dev/null | |
| adb_cmd "setprop debug.hwui.use_vulkan 0" >/dev/null | |
| ok "Renderer przywrócony do domyślnego." | |
| ;; | |
| 0) return ;; | |
| esac | |
| pause | |
| } | |
| optimize_art() { | |
| header "OPTYMALIZACJA ART / DEX (Android 16)" | |
| echo " [1] bg-dexopt-job (profile-guided — ZALECANE)" | |
| echo " [2] Kompilacja speed-profile dla wszystkich aplikacji" | |
| echo " [3] Kompilacja speed (pełna — długa, więcej RAM)" | |
| echo " [4] Reset i rekompilacja baseline (po aktualizacji systemu)" | |
| echo " [5] Wymuś GC (Garbage Collection) we wszystkich procesach" | |
| echo " [0] Powrót" | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) | |
| step "Uruchamiam bg-dexopt-job (profile-guided, może potrwać 3-8 min)..." | |
| warn "Nie blokuj ekranu i pozostań w zasięgu Wi-Fi." | |
| echo | |
| if adb_long "cmd package bg-dexopt-job"; then | |
| ok "bg-dexopt-job zakończony sukcesem." | |
| else | |
| warn "bg-dexopt-job zwrócił błąd — próbuję przez pm compile..." | |
| adb_long "pm compile -m speed-profile -a" && ok "pm compile zakończony." || err "Kompilacja nieudana — sprawdź log." | |
| fi | |
| ;; | |
| 2) | |
| step "Kompiluję wszystkie pakiety (speed-profile)..." | |
| adb_long "pm compile -m speed-profile -a" && ok "Zakończono." || err "Błąd kompilacji." | |
| ;; | |
| 3) | |
| warn "Kompilacja speed zajmie więcej czasu i wygeneruje duże pliki odex." | |
| confirm "Kontynuować?" || { info "Anulowano."; pause; return; } | |
| step "Kompilacja speed (pełna)..." | |
| adb_long "pm compile -m speed -a" && ok "Zakończono." || err "Błąd." | |
| ;; | |
| 4) | |
| warn "Reset profilów ART — system rekompiluje aplikacje stopniowo po restarcie." | |
| confirm "Kontynuować?" || { info "Anulowano."; pause; return; } | |
| adb_long "pm compile --reset -a" && ok "Profile ART zresetowane." || err "Błąd resetu." | |
| info "Zalecany restart zegarka dla efektu." | |
| ;; | |
| 5) | |
| step "Wymuszam GC we wszystkich procesach..." | |
| adb_cmd "am force-stop --user all && echo ok" >/dev/null 2>&1 || true | |
| local pids | |
| pids=$(adb_get "ps -A 2>/dev/null | grep -v root | awk '{print \$1}'" | head -30) | |
| local count=0 | |
| while IFS= read -r pid; do | |
| [[ "$pid" =~ ^[0-9]+$ ]] || continue | |
| adb_cmd "kill -10 $pid" >/dev/null 2>&1 && (( count++ )) || true | |
| done <<< "$pids" | |
| ok "GC wysłany do ${count} procesów." | |
| ;; | |
| 0) return ;; | |
| esac | |
| pause | |
| } | |
| clear_caches() { | |
| header "CZYSZCZENIE PAMIĘCI PODRĘCZNEJ" | |
| echo " [1] Trim caches aplikacji (pm trim-caches)" | |
| echo " [2] Wyczyść cache SurfaceFlinger" | |
| echo " [3] Wyczyść cache thumbnail / media" | |
| echo " [4] Wymuś trim /data/cache" | |
| echo " [5] Wszystkie powyższe" | |
| echo " [0] Powrót" | |
| read -rp "$(printf " Wybór: ")" ch | |
| _trim_app_caches() { | |
| step "Przycinam cache aplikacji..." | |
| adb_cmd "pm trim-caches 4G" >/dev/null && ok "Cache aplikacji wyczyszczony." || err "pm trim-caches nieudane." | |
| } | |
| _sf_cache() { | |
| step "Restartuję SurfaceFlinger cache..." | |
| adb_cmd "service call SurfaceFlinger 1004 i32 0" >/dev/null 2>&1 && ok "SurfaceFlinger: odświeżony." || warn "SurfaceFlinger cache — pominięto (brak dostępu)." | |
| } | |
| _media_cache() { | |
| step "Czyszczę cache mediów..." | |
| adb_cmd "rm -rf /data/data/com.android.providers.media/cache/* 2>/dev/null" >/dev/null && ok "Cache mediów wyczyszczony." || warn "Cache mediów — brak dostępu (normalne bez root)." | |
| } | |
| _data_cache() { | |
| step "Wymuszam trim /data/cache..." | |
| adb_cmd "pm trim-caches 0" >/dev/null | |
| adb_cmd "sync" >/dev/null 2>&1 && ok "Sync i trim zakończony." || warn "Sync pominięty." | |
| } | |
| case "$ch" in | |
| 1) _trim_app_caches ;; | |
| 2) _sf_cache ;; | |
| 3) _media_cache ;; | |
| 4) _data_cache ;; | |
| 5) _trim_app_caches; _sf_cache; _media_cache; _data_cache ;; | |
| 0) return ;; | |
| esac | |
| pause | |
| } | |
| manage_cpu_governor() { | |
| header "CPU GOVERNOR (EXYNOS W920)" | |
| warn "Zmiana governor'a wymaga dostępu root lub odblokowanego jądra." | |
| warn "Na standardowym WearOS 6 ta opcja ogranicza się do odczytu." | |
| echo | |
| step "Odczyt obecnych ustawień CPU" | |
| local core=0 | |
| for governor_path in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do | |
| local gov | |
| gov=$(adb_get "cat ${governor_path} 2>/dev/null" || echo "niedostępny") | |
| local freq_cur | |
| freq_cur=$(adb_get "cat $(dirname "${governor_path}")/scaling_cur_freq 2>/dev/null || echo '?'") | |
| local freq_max | |
| freq_max=$(adb_get "cat $(dirname "${governor_path}")/scaling_max_freq 2>/dev/null || echo '?'") | |
| printf " ${CYAN}CPU%d${RESET}: Governor=${BOLD}%s${RESET} | Curr=%s kHz | Max=%s kHz\n" \ | |
| "$core" "$gov" "$freq_cur" "$freq_max" | |
| (( core++ )) | |
| (( core > 4 )) && break | |
| done | |
| sep_thin | |
| echo " [1] Ustaw governor 'performance' (próba bez root — może się nie udać)" | |
| echo " [2] Ustaw governor 'schedutil' (zalecany balans)" | |
| echo " [3] Odśwież odczyt stanu CPU" | |
| echo " [0] Powrót" | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) | |
| for cpu_path in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do | |
| adb_cmd "echo performance > ${cpu_path} 2>/dev/null" >/dev/null || true | |
| done | |
| ok "Próba ustawienia 'performance' — sprawdź odczyt opcją [3]." | |
| ;; | |
| 2) | |
| for cpu_path in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do | |
| adb_cmd "echo schedutil > ${cpu_path} 2>/dev/null" >/dev/null || true | |
| done | |
| ok "Próba ustawienia 'schedutil'." | |
| ;; | |
| 3) manage_cpu_governor; return ;; | |
| 0) return ;; | |
| esac | |
| pause | |
| } | |
| optimize_vm_flags() { | |
| header "OPTYMALIZACJA VM (DALVIK/ART FLAGS)" | |
| local current_heapsize | |
| current_heapsize=$(adb_get "getprop dalvik.vm.heapsize") | |
| status_row "dalvik.vm.heapsize" "${current_heapsize:-domyślny}" | |
| sep_thin | |
| echo " [1] Optymalne flagi VM dla WearOS (lepsza GC, mniejszy heap pressure)" | |
| echo " [2] Wymuś mały heap (agresywna GC — więcej wolnego RAM)" | |
| echo " [3] Przywróć domyślne flagi VM" | |
| echo " [0] Powrót" | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) | |
| # Flagi sprawdzone dla Android 14-16 WearOS | |
| adb_cmd "setprop dalvik.vm.heapgrowthlimit 48m" >/dev/null | |
| adb_cmd "setprop dalvik.vm.heapminfree 512k" >/dev/null | |
| adb_cmd "setprop dalvik.vm.heaptargetutilization 0.75" >/dev/null | |
| adb_cmd "setprop dalvik.vm.dex2oat-filter speed-profile" >/dev/null | |
| adb_cmd "setprop dalvik.vm.dex2oat-threads 2" >/dev/null | |
| ok "Flagi VM zoptymalizowane dla WearOS." | |
| ;; | |
| 2) | |
| adb_cmd "setprop dalvik.vm.heapgrowthlimit 32m" >/dev/null | |
| adb_cmd "setprop dalvik.vm.heapmaxfree 4m" >/dev/null | |
| adb_cmd "setprop dalvik.vm.heaptargetutilization 0.6" >/dev/null | |
| ok "Mały heap — więcej wolnego RAM, częstszy GC." | |
| ;; | |
| 3) | |
| adb_cmd "setprop dalvik.vm.heapgrowthlimit ''" >/dev/null | |
| adb_cmd "setprop dalvik.vm.heapminfree ''" >/dev/null | |
| adb_cmd "setprop dalvik.vm.heaptargetutilization ''" >/dev/null | |
| ok "Flagi VM przywrócone do domyślnych." | |
| ;; | |
| 0) return ;; | |
| esac | |
| pause | |
| } | |
| apply_turbo_profile() { | |
| header "PROFIL TURBO — KOMPLEKSOWA OPTYMALIZACJA" | |
| echo -e " ${BYELLOW}Wykona kolejno WSZYSTKIE bezpieczne optymalizacje:${RESET}" | |
| echo " → AOD wyłączone" | |
| echo " → AmbientDreamProxy wakelock zatrzymany" | |
| echo " → GPS tryb sieciowy (eliminuje gnss_mailbox wakeup)" | |
| echo " → WSZYSTKIE 3 animacje 0.5x (w tym window_animation_scale)" | |
| echo " → Renderer Skia Vulkan" | |
| echo " → Optymalne flagi VM" | |
| echo " → bg-dexopt-job (ART recompile)" | |
| echo " → Trim caches" | |
| echo | |
| confirm "Zastosować profil TURBO?" || { info "Anulowano."; pause; return; } | |
| sep | |
| step "1/8 — AOD..." | |
| safe_setting "secure" "doze_always_on" "0" "AOD" | |
| safe_setting "secure" "doze_wake_display_on_gesture" "0" "Wybudzenie gestem" | |
| safe_setting "secure" "doze_pick_up_gesture" "0" "Pulse on pick-up" | |
| safe_setting "secure" "doze_pulse_on_double_tap" "0" "Pulse on double-tap" | |
| step "2/8 — AmbientDreamProxy kill (fix lagów wybudzenia)..." | |
| for pkg in com.samsung.android.app.aodservice com.samsung.systemui.aod; do | |
| adb_cmd "am force-stop ${pkg}" >/dev/null 2>&1 && ok "force-stop: ${pkg}" || true | |
| done | |
| step "3/8 — GPS tryb sieciowy (eliminuje gnss_mailbox wakeup)..." | |
| safe_setting "secure" "location_mode" "2" "GPS location_mode (sieciowy)" | |
| step "4/8 — WSZYSTKIE animacje 0.5x..." | |
| # BUGFIX v3.1: ustawiamy wszystkie 3, łącznie z window (które było 1.0 w logach) | |
| safe_setting "global" "window_animation_scale" "0.5" "window_animation_scale" | |
| safe_setting "global" "transition_animation_scale" "0.5" "transition_animation_scale" | |
| safe_setting "global" "animator_duration_scale" "0.5" "animator_duration_scale" | |
| step "5/8 — Renderer GPU..." | |
| adb_cmd "setprop debug.hwui.renderer skiavk" >/dev/null && ok "Renderer: Skia Vulkan (Exynos W920)" | |
| step "6/8 — VM flags..." | |
| adb_cmd "setprop dalvik.vm.dex2oat-filter speed-profile" >/dev/null && ok "VM: speed-profile dex2oat" | |
| adb_cmd "setprop dalvik.vm.dex2oat-threads 2" >/dev/null && ok "VM: 2 dex2oat threads" | |
| step "7/8 — Cache trim..." | |
| adb_cmd "pm trim-caches 4G" >/dev/null && ok "Cache: wyczyszczony" | |
| step "8/8 — ART bg-dexopt-job (to zajmie chwilę)..." | |
| if adb_long "cmd package bg-dexopt-job" | tail -3; then | |
| ok "ART: bg-dexopt-job zakończony." | |
| else | |
| warn "bg-dexopt-job — próba pm compile..." | |
| adb_long "pm compile -m speed-profile -a" | tail -3 && ok "ART: pm compile OK." || err "ART: kompilacja nieudana." | |
| fi | |
| sep | |
| ok "Profil TURBO zastosowany!" | |
| info "Zalecany restart zegarka: opcja 'Naprawy → Restart'" | |
| pause | |
| } | |
| restore_defaults_perf() { | |
| header "PRZYWRACANIE DOMYŚLNYCH USTAWIEŃ WYDAJNOŚCI" | |
| confirm "Przywrócić domyślne ustawienia animacji, GPU, AOD?" || { info "Anulowano."; pause; return; } | |
| safe_setting "global" "window_animation_scale" "1.0" "window_animation_scale" | |
| safe_setting "global" "transition_animation_scale" "1.0" "transition_animation_scale" | |
| safe_setting "global" "animator_duration_scale" "1.0" "animator_duration_scale" | |
| safe_setting "secure" "doze_always_on" "1" "AOD" | |
| adb_cmd "setprop debug.hwui.renderer ''" >/dev/null && ok "Renderer: domyślny" | |
| adb_cmd "setprop debug.hwui.use_vulkan 0" >/dev/null && ok "Vulkan flag: wyłączony" | |
| ok "Ustawienia domyślne przywrócone." | |
| pause | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 7 — DEBLOAT | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # Katalog debloat — TYLKO aplikacje bezpieczne do wyłączenia (nie usunięcia) | |
| # Podzielone na kategorie z poziomem ryzyka | |
| declare -A DEBLOAT_PKGS_LOW=( | |
| # RYZYKO NISKIE — można bezpiecznie wyłączyć | |
| ["com.samsung.android.app.tips"]="Wskazówki Samsung" | |
| ["com.samsung.android.forest"]="Digital Wellbeing (ekran wellness)" | |
| ["com.samsung.android.game.gos"]="Game Optimizing Service" | |
| ["com.samsung.android.app.social"]="Samsung Social apps" | |
| ["com.sec.android.app.chromecustomtabs"]="Chrome Custom Tabs" | |
| ["com.samsung.systemui.bixby.wearable"]="Bixby Watch" | |
| ["com.samsung.android.bixby.agent"]="Bixby Agent" | |
| ["com.samsung.android.app.spage"]="Bixby Home" | |
| ["com.samsung.android.beaconmanager"]="Beacon Manager" | |
| ["com.samsung.android.dynamiclock"]="Dynamic Lock Screen" | |
| # Nowe — zidentyfikowane w logach 23.02.2026 jako hogi 40-50 MB | |
| ["com.samsung.android.easyMover"]="Easy Mover (~50 MB — transfer danych, zbędny na zegarku)" | |
| ["com.samsung.android.diagmonagent"]="DiagMonAgent (~41 MB — diagnostyka fabryczna)" | |
| ["com.samsung.android.app.reminder"]="Samsung Reminder (~42 MB)" | |
| ) | |
| declare -A DEBLOAT_PKGS_MEDIUM=( | |
| # RYZYKO ŚREDNIE — wyłącz ostrożnie | |
| ["com.samsung.android.app.reminder"]="Samsung Reminder" | |
| ["com.samsung.android.app.notes"]="Samsung Notes (watch)" | |
| ["com.samsung.android.app.samsungpay.wear"]="Samsung Pay Wear" | |
| ["com.samsung.android.app.watchmanager"]="Watch Manager (companion)" | |
| ["com.samsung.android.gearoplugin"]="Gear O Plugin" | |
| ["com.samsung.android.app.galaxyfind"]="Find My Watch (Podziel jeśli nieużywany)" | |
| ["com.samsung.android.mobileservice"]="Samsung Mobile Service" | |
| ) | |
| debloat_menu() { | |
| while true; do | |
| header "DEBLOAT — BEZPIECZNE ZARZĄDZANIE APLIKACJAMI" | |
| echo -e " ${GREEN}ZASADA: Tylko disable-user — żadnej aplikacji nie usuwamy!${RESET}" | |
| echo -e " ${GREEN}Wszystkie zmiany są w pełni odwracalne przez opcję [4].${RESET}" | |
| sep_thin | |
| echo " [1] Przejrzyj i wyłącz aplikacje NISKIE RYZYKO (zalecane)" | |
| echo " [2] Przejrzyj i wyłącz aplikacje ŚREDNIE RYZYKO (ostrożnie)" | |
| echo " [3] Pokaż wszystkie aktualnie wyłączone aplikacje" | |
| echo " [4] Przywróć wyłączone aplikacje Watch4 Suite" | |
| echo " [5] Skanuj zasoby pożerające aplikacje (top hog scan)" | |
| echo " [0] Powrót" | |
| sep | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) debloat_process_list "LOW" ;; | |
| 2) debloat_process_list "MEDIUM" ;; | |
| 3) debloat_show_disabled ;; | |
| 4) debloat_restore_all ;; | |
| 5) debloat_hog_scan ;; | |
| 0) return ;; | |
| *) warn "Nieprawidłowy wybór." ;; | |
| esac | |
| done | |
| } | |
| debloat_process_list() { | |
| local risk_level="$1" | |
| header "DEBLOAT — RYZYKO: ${risk_level}" | |
| # Wybierz odpowiedni słownik | |
| local -n pkg_map | |
| if [[ "$risk_level" == "LOW" ]]; then | |
| pkg_map=DEBLOAT_PKGS_LOW | |
| echo -e " ${GREEN}Aplikacje w tej kategorii są bezpieczne do wyłączenia.${RESET}" | |
| else | |
| pkg_map=DEBLOAT_PKGS_MEDIUM | |
| echo -e " ${YELLOW}Aplikacje w tej kategorii — wyłącz tylko jeśli NIE używasz ich funkcji.${RESET}" | |
| fi | |
| echo | |
| local disabled_log="${BACKUP_DIR}/debloat_${risk_level,,}.sh" | |
| echo "#!/usr/bin/env bash" > "$disabled_log" | |
| echo "# Przywracanie debloat ${risk_level}" >> "$disabled_log" | |
| local i=1 pkgs_arr=() | |
| for pkg in "${!pkg_map[@]}"; do | |
| pkgs_arr+=("$pkg") | |
| done | |
| for pkg in "${pkgs_arr[@]}"; do | |
| local desc="${pkg_map[$pkg]}" | |
| # Sprawdź czy zainstalowany | |
| local installed | |
| installed=$(adb_get "pm list packages 2>/dev/null | grep -c ${pkg}" || echo "0") | |
| local status_str | |
| local is_disabled | |
| is_disabled=$(adb_get "pm list packages -d 2>/dev/null | grep -c ${pkg}" || echo "0") | |
| if [[ "$installed" -lt 1 ]] 2>/dev/null; then | |
| status_str="${DIM}(nie zainstalowany)${RESET}" | |
| elif [[ "$is_disabled" -gt 0 ]] 2>/dev/null; then | |
| status_str="${YELLOW}(już wyłączony)${RESET}" | |
| else | |
| status_str="${GREEN}(aktywny)${RESET}" | |
| fi | |
| printf "\n [%2d] ${BOLD}%s${RESET}\n ${DIM}%s${RESET} %s\n" \ | |
| "$i" "$desc" "$pkg" "$status_str" | |
| (( i++ )) | |
| done | |
| echo | |
| sep_thin | |
| echo " Wpisz numery aplikacji do wyłączenia (np: 1 3 5) lub [A] dla wszystkich aktywnych," | |
| read -rp " lub [0] aby anulować: " selection | |
| [[ "$selection" == "0" ]] && return | |
| local selected_pkgs=() | |
| if [[ "${selection,,}" == "a" ]]; then | |
| selected_pkgs=("${pkgs_arr[@]}") | |
| else | |
| for num in $selection; do | |
| if [[ "$num" =~ ^[0-9]+$ ]] && (( num >= 1 && num <= ${#pkgs_arr[@]} )); then | |
| selected_pkgs+=("${pkgs_arr[$((num-1))]}") | |
| fi | |
| done | |
| fi | |
| if [[ ${#selected_pkgs[@]} -eq 0 ]]; then | |
| warn "Nie wybrano żadnych aplikacji."; pause; return | |
| fi | |
| echo | |
| warn "Zamierzam wyłączyć ${#selected_pkgs[@]} aplikację/aplikacje:" | |
| for pkg in "${selected_pkgs[@]}"; do | |
| printf " ${CYAN}%s${RESET} — %s\n" "$pkg" "${pkg_map[$pkg]}" | |
| done | |
| echo | |
| confirm "Potwierdzasz wyłączenie?" || { info "Anulowano."; pause; return; } | |
| echo | |
| for pkg in "${selected_pkgs[@]}"; do | |
| echo "pm enable ${pkg}" >> "$disabled_log" | |
| if adb_cmd "pm disable-user --user 0 ${pkg}" >/dev/null 2>&1; then | |
| ok "Wyłączono: ${pkg_map[$pkg]}" | |
| else | |
| warn "Pominięto (może nie istnieć na tym urządzeniu): ${pkg}" | |
| fi | |
| done | |
| echo | |
| ok "Gotowe. Skrypt przywracania: ${disabled_log}" | |
| info "Użyj opcji [4] w menu debloat aby przywrócić wszystko jednym kliknięciem." | |
| pause | |
| } | |
| debloat_show_disabled() { | |
| header "WYŁĄCZONE APLIKACJE (wszystkie)" | |
| local disabled | |
| disabled=$(adb_get "pm list packages -d 2>/dev/null" | sed 's/package://' | sort) | |
| if [[ -z "$disabled" ]]; then | |
| info "Brak wyłączonych aplikacji."; pause; return | |
| fi | |
| echo "$disabled" | while IFS= read -r pkg; do | |
| printf " ${YELLOW}●${RESET} %s\n" "$pkg" | |
| done | |
| echo | |
| info "Łącznie wyłączonych: $(echo "$disabled" | wc -l | tr -d ' ')" | |
| pause | |
| } | |
| debloat_restore_all() { | |
| header "PRZYWRACANIE APLIKACJI" | |
| info "Przywracam wszystkie aplikacje wyłączone przez Watch4 Suite..." | |
| local all_pkgs=("${!DEBLOAT_PKGS_LOW[@]}" "${!DEBLOAT_PKGS_MEDIUM[@]}") | |
| local restored=0 skipped=0 | |
| for pkg in "${all_pkgs[@]}"; do | |
| if adb_cmd "pm enable ${pkg}" >/dev/null 2>&1; then | |
| ok "Przywrócono: ${pkg}" | |
| (( restored++ )) | |
| else | |
| (( skipped++ )) | |
| fi | |
| done | |
| ok "Przywrócono: ${restored} | Pominięto (nie było wyłączone): ${skipped}" | |
| pause | |
| } | |
| debloat_hog_scan() { | |
| header "SKAN POŻERACZY ZASOBÓW" | |
| step "Analizuję zużycie CPU i RAM przez aplikacje (10 sekund)..." | |
| echo | |
| # CPU hogs | |
| printf " ${BOLD}TOP 10 PROCESÓW — RAM${RESET}\n" | |
| sep_thin | |
| printf " ${DIM}%-8s %-8s %s${RESET}\n" "PID" "RSS(MB)" "APLIKACJA" | |
| adb_get "ps -eo pid,rss,args 2>/dev/null | sort -k2 -rn | head -10" | while IFS= read -r line; do | |
| local pid rss app | |
| read -r pid rss app <<< "$line" | |
| if [[ "$rss" =~ ^[0-9]+$ ]]; then | |
| local rss_mb=$(( rss / 1024 )) | |
| local color="$WHITE" | |
| (( rss_mb > 80 )) && color="$YELLOW" | |
| (( rss_mb > 150 )) && color="$RED" | |
| printf " ${color}%-8s %-8s %s${RESET}\n" "$pid" "${rss_mb}MB" "${app:0:45}" | |
| fi | |
| done | |
| sep_thin | |
| step "Wakelock analysis (aplikacje budzące CPU)" | |
| adb_get "dumpsys power 2>/dev/null" | grep -E "PARTIAL_WAKE_LOCK|acquired|held" | head -10 | while IFS= read -r line; do | |
| printf " ${YELLOW}%s${RESET}\n" "$line" | |
| done || detail "Brak dostępnych wakelock." | |
| sep_thin | |
| step "Battery drain — wina aplikacji (BatteryStats)" | |
| adb_get "dumpsys batterystats --charged 2>/dev/null" | grep -E "Uid|wake|cpu" | head -15 | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| pause | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 8 — NAPRAWA SYSTEMU | |
| # ═══════════════════════════════════════════════════════════════════ | |
| repair_menu() { | |
| while true; do | |
| header "NAPRAWA SYSTEMU" | |
| echo " [1] Reset UI (SystemUI, Launcher — bez restartu urządzenia)" | |
| echo " [2] Reset stack Activity Manager (napraw zamrożone aplikacje)" | |
| echo " [3] Naprawa sieci Wi-Fi (reset stosu sieciowego)" | |
| echo " [4] Naprawa Bluetooth (reset stosu BT)" | |
| echo " [5] Wymuszenie synchonizacji zegara systemowego" | |
| echo " [6] Naprawa bazy danych PackageManager" | |
| echo " [7] Restart usług systemowych (bezpieczny)" | |
| echo " [8] Reset sensorów i health tracking" | |
| echo " [9] Pełny bezpieczny restart zegarka" | |
| echo " [10] Tryb odzyskiwania — eksport logów PRZED restartem" | |
| echo " [0] Powrót" | |
| sep | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) repair_ui_reset ;; | |
| 2) repair_activity_stack ;; | |
| 3) repair_wifi ;; | |
| 4) repair_bluetooth ;; | |
| 5) repair_time_sync ;; | |
| 6) repair_package_manager ;; | |
| 7) repair_services ;; | |
| 8) repair_sensors ;; | |
| 9) repair_safe_reboot ;; | |
| 10) repair_export_before_reboot ;; | |
| 0) return ;; | |
| *) warn "Nieprawidłowy wybór." ;; | |
| esac | |
| done | |
| } | |
| repair_ui_reset() { | |
| header "RESET UI SYSTEMU" | |
| warn "Ekran zegarka może migotać przez chwilę — to normalne." | |
| echo | |
| step "Restartuję SystemUI..." | |
| adb_cmd "am force-stop com.android.systemui" >/dev/null && ok "SystemUI zatrzymany." || warn "SystemUI — problem." | |
| sleep 2 | |
| # SystemUI restartuje się automatycznie; jeśli nie — poniżej | |
| adb_cmd "am startservice --user 0 -n com.android.systemui/.SystemUIService" >/dev/null 2>&1 || true | |
| sleep 2 | |
| step "Restartuję Launcher Samsung..." | |
| local launcher_pkg | |
| launcher_pkg=$(adb_get "pm list packages 2>/dev/null | grep -i 'launcher\|homescreen\|wlauncher'" | head -1 | sed 's/package://') | |
| if [[ -n "$launcher_pkg" ]]; then | |
| adb_cmd "am force-stop ${launcher_pkg}" >/dev/null && ok "Launcher zatrzymany: ${launcher_pkg}" | |
| else | |
| warn "Launcher nie zidentyfikowany." | |
| fi | |
| step "Restartuję usługę watchface..." | |
| adb_cmd "am force-stop com.samsung.android.app.watchface" >/dev/null 2>&1 && ok "Watchface service zrestartowany." || warn "Watchface service — pominięto." | |
| ok "Reset UI zakończony. Zegarek powinien odświeżyć interfejs." | |
| pause | |
| } | |
| repair_activity_stack() { | |
| header "RESET ACTIVITY MANAGER STACK" | |
| step "Czyszczę stos zadań Activity Managera..." | |
| adb_cmd "am clear-recent-tasks" >/dev/null 2>&1 && ok "Recent tasks wyczyszczone." || warn "Pominięto." | |
| step "Reset animacji przejść..." | |
| adb_cmd "wm dismiss-keyguard" >/dev/null 2>&1 || true | |
| sleep 1 | |
| adb_cmd "input keyevent KEYCODE_WAKEUP" >/dev/null 2>&1 || true | |
| step "Sprawdzam zakleszczone procesy..." | |
| adb_get "dumpsys activity processes 2>/dev/null" | grep -i "ANR\|LOCK\|WAIT" | head -5 | while IFS= read -r line; do | |
| warn "$line" | |
| done || ok "Brak zakleszczonych procesów." | |
| pause | |
| } | |
| repair_wifi() { | |
| header "NAPRAWA WI-FI" | |
| warn "Ta operacja zresetuje stos Wi-Fi — połączenie ADB chwilowo zerwane!" | |
| confirm "Kontynuować? (Skrypt automatycznie ponownie połączy)" || { info "Anulowano."; pause; return; } | |
| step "Zapisuję aktualny IP..." | |
| local saved_serial="$DEVICE_SERIAL" | |
| step "Wyłączam Wi-Fi..." | |
| adb_cmd "svc wifi disable" >/dev/null 2>&1 || true | |
| sleep 3 | |
| step "Włączam Wi-Fi..." | |
| adb_cmd "svc wifi enable" >/dev/null 2>&1 || true | |
| info "Czekam 10 sekund na reconnect..." | |
| sleep 10 | |
| step "Próba ponownego połączenia ADB..." | |
| local retries=5 | |
| while (( retries > 0 )); do | |
| if adb connect "$saved_serial" >/dev/null 2>&1 && ping_device; then | |
| DEVICE_SERIAL="$saved_serial" | |
| ok "Ponownie połączono z ${DEVICE_SERIAL}" | |
| break | |
| fi | |
| (( retries-- )) | |
| warn "Próba połączenia... pozostało ${retries}" | |
| sleep 3 | |
| done | |
| if (( retries == 0 )); then | |
| err "Nie udało się połączyć automatycznie." | |
| err "Uruchom skrypt ponownie po chwili." | |
| fi | |
| pause | |
| } | |
| repair_bluetooth() { | |
| header "NAPRAWA BLUETOOTH" | |
| step "Wyłączam Bluetooth..." | |
| adb_cmd "svc bluetooth disable" >/dev/null 2>&1 && ok "BT wyłączony." || warn "BT disable — pominięto." | |
| sleep 3 | |
| step "Włączam Bluetooth..." | |
| adb_cmd "svc bluetooth enable" >/dev/null 2>&1 && ok "BT włączony." || warn "BT enable — pominięto." | |
| sleep 2 | |
| step "Sprawdzam status BT..." | |
| local bt_state | |
| bt_state=$(adb_get "settings get global bluetooth_on 2>/dev/null") | |
| status_row "bluetooth_on" "${bt_state:-?}" | |
| ok "Reset Bluetooth zakończony." | |
| pause | |
| } | |
| repair_time_sync() { | |
| header "SYNCHRONIZACJA ZEGARA SYSTEMOWEGO" | |
| step "Obecny czas systemowy..." | |
| adb_get "date" | while IFS= read -r line; do detail "$line"; done | |
| step "Synchronizuję czas z NTP..." | |
| adb_cmd "settings put global ntp_server pool.ntp.org" >/dev/null && ok "NTP server: pool.ntp.org" | |
| adb_cmd "settings put global auto_time 1" >/dev/null && ok "Auto-time włączony" | |
| adb_cmd "settings put global auto_time_zone 1" >/dev/null && ok "Auto-timezone włączony" | |
| # Wymuś sync przez restart serwisu czasu | |
| adb_cmd "am broadcast -a android.intent.action.TIME_SET" >/dev/null 2>&1 && ok "TIME_SET broadcast wysłany." || warn "Broadcast — pominięto." | |
| step "Czas po synchronizacji:" | |
| sleep 2 | |
| adb_get "date" | while IFS= read -r line; do ok "$line"; done | |
| pause | |
| } | |
| repair_package_manager() { | |
| header "NAPRAWA BAZY DANYCH PACKAGEMANAGER" | |
| warn "Ta operacja wyczyści pamięć podręczną PackageManagera i zreoptymalizuje bazę." | |
| step "Sprawdzam integralność PackageManager..." | |
| local pm_err | |
| pm_err=$(adb_get "pm dump com.android.providers.settings 2>/dev/null | grep -i error | head -5" || echo "") | |
| [[ -n "$pm_err" ]] && warn "Wykryte błędy PM: $pm_err" || ok "PM integrity: brak widocznych błędów." | |
| step "Czyszczę cache PackageManager..." | |
| adb_cmd "pm clear com.android.providers.settings" >/dev/null 2>&1 && ok "Settings provider cache wyczyszczony." || warn "Pominięto." | |
| step "Rekompilacja zoptymalizowanego kodu PackageManager..." | |
| adb_cmd "pm compile -m speed-profile com.android.settings" >/dev/null 2>&1 && ok "Settings recompiled." || warn "Pominięto." | |
| step "Trim storage..." | |
| adb_cmd "pm trim-caches 4G" >/dev/null && ok "Storage trimmed." | |
| ok "Naprawa PM zakończona." | |
| pause | |
| } | |
| repair_services() { | |
| header "RESTART USŁUG SYSTEMOWYCH (BEZPIECZNY)" | |
| echo " [1] Restart SurfaceFlinger (napraw rendering)" | |
| echo " [2] Restart MediaServer (napraw audio/media)" | |
| echo " [3] Restart InputDispatcher (napraw dotyk)" | |
| echo " [4] Wyczyść cache i zrestartuj wszystkie kluczowe usługi" | |
| echo " [0] Powrót" | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) | |
| warn "Ekran może migać przez 2-5 sekund." | |
| adb_cmd "kill \$(pidof surfaceflinger)" >/dev/null 2>&1 | |
| sleep 3 && ok "SurfaceFlinger zrestartowany (automatycznie przez init)." | |
| ;; | |
| 2) | |
| adb_cmd "kill \$(pidof mediaserver)" >/dev/null 2>&1 | |
| sleep 2 && ok "MediaServer zrestartowany." | |
| ;; | |
| 3) | |
| adb_cmd "kill \$(pidof inputdispatcher)" >/dev/null 2>&1 | |
| sleep 1 && ok "InputDispatcher zrestartowany." | |
| ;; | |
| 4) | |
| warn "Restartuję kluczowe usługi — chwilowe zakłócenie UI." | |
| confirm "Kontynuować?" || { info "Anulowano."; return; } | |
| for svc_proc in surfaceflinger mediaserver; do | |
| adb_cmd "kill \$(pidof ${svc_proc} 2>/dev/null)" >/dev/null 2>&1 && ok "${svc_proc}: restarted" || warn "${svc_proc}: pominięto" | |
| sleep 1 | |
| done | |
| ;; | |
| 0) return ;; | |
| esac | |
| pause | |
| } | |
| repair_sensors() { | |
| header "RESET SENSORÓW I HEALTH TRACKING" | |
| step "Resetuję kalibrację sensorów..." | |
| adb_cmd "am broadcast -a android.intent.action.SENSOR_RECALIBRATION" >/dev/null 2>&1 || true | |
| adb_cmd "am force-stop com.samsung.android.wear.shealth" >/dev/null 2>&1 && ok "Samsung Health Watch zrestartowany." || warn "Samsung Health Watch — pominięto." | |
| adb_cmd "am force-stop com.samsung.android.service.health" >/dev/null 2>&1 && ok "Health Service zrestartowany." || warn "Health Service — pominięto." | |
| step "Sprawdzam sensory..." | |
| adb_get "dumpsys sensorservice 2>/dev/null | head -20" | while IFS= read -r line; do | |
| detail "$line" | |
| done | |
| ok "Reset sensorów zakończony. Pomiary zdrowia zostaną skalibrowane podczas kolejnego noszenia." | |
| pause | |
| } | |
| repair_safe_reboot() { | |
| header "BEZPIECZNY RESTART ZEGARKA" | |
| # Przed restartem — wykonaj backup | |
| if [[ "$BACKUP_DONE" == "false" ]]; then | |
| step "Tworzę backup ustawień przed restartem..." | |
| adb_get "settings list global" > "${BACKUP_DIR}/global_settings.txt" 2>/dev/null | |
| adb_get "settings list secure" > "${BACKUP_DIR}/secure_settings.txt" 2>/dev/null | |
| adb_get "settings list system" > "${BACKUP_DIR}/system_settings.txt" 2>/dev/null | |
| ok "Backup zapisany: ${BACKUP_DIR}/" | |
| BACKUP_DONE=true | |
| fi | |
| warn "Zegarek uruchomi się ponownie. Połączenie ADB zostanie zerwane." | |
| confirm "Uruchomić ponownie zegarek?" || { info "Anulowano."; pause; return; } | |
| info "Restartuję zegarek..." | |
| adb -s "$DEVICE_SERIAL" reboot | |
| ok "Restart zlecony." | |
| info "Zegarek będzie dostępny za ~60-90 sekund." | |
| info "Uruchom skrypt ponownie po restarcie." | |
| exit 0 | |
| } | |
| repair_export_before_reboot() { | |
| header "EKSPORT LOGÓW PRZED RESTARTEM" | |
| local export_dir="${LOG_DIR}/pre_reboot_$(date +%Y%m%d_%H%M%S)" | |
| mkdir -p "$export_dir" | |
| step "Eksportuje pełną diagnostykę..." | |
| { | |
| echo "=== PRE-REBOOT DUMP ===" | |
| echo "Date: $(date)" | |
| echo "Device: $DEVICE_SERIAL" | |
| adb_get "dumpsys" 2>/dev/null | |
| } > "${export_dir}/dumpsys_full.txt" & | |
| spinner $! "Zbieranie dumpsys (może potrwać 30s)..." | |
| adb_get "logcat -d 2>/dev/null" > "${export_dir}/logcat.txt" & | |
| spinner $! "Zbieranie logcat..." | |
| adb_get "getprop 2>/dev/null" > "${export_dir}/getprop.txt" | |
| ok "Eksport zakończony: ${export_dir}" | |
| info "Możesz teraz bezpiecznie wykonać restart zegarka." | |
| pause | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 9 — BACKUP / RESTORE PEŁNY | |
| # ═══════════════════════════════════════════════════════════════════ | |
| backup_menu() { | |
| while true; do | |
| header "BACKUP I PRZYWRACANIE" | |
| status_row "Katalog backup" "$BACKUP_DIR" | |
| sep_thin | |
| echo " [1] Backup wszystkich ustawień systemowych" | |
| echo " [2] Przywróć ustawienia z backupu tej sesji" | |
| echo " [3] Pokaż istniejące backupy" | |
| echo " [4] Eksportuj backup na zegarek (/sdcard/watch4_backup/)" | |
| echo " [0] Powrót" | |
| sep | |
| read -rp "$(printf " Wybór: ")" ch | |
| case "$ch" in | |
| 1) backup_settings ;; | |
| 2) restore_from_backup ;; | |
| 3) list_backups ;; | |
| 4) export_backup_to_watch ;; | |
| 0) return ;; | |
| *) warn "Nieprawidłowy wybór." ;; | |
| esac | |
| done | |
| } | |
| backup_settings() { | |
| step "Tworzę pełny backup ustawień systemowych..." | |
| adb_get "settings list global 2>/dev/null" | while IFS='=' read -r key val; do | |
| echo "settings put global '${key}' '${val}'" | |
| done > "${BACKUP_DIR}/restore_global.sh" | |
| adb_get "settings list secure 2>/dev/null" | while IFS='=' read -r key val; do | |
| echo "settings put secure '${key}' '${val}'" | |
| done > "${BACKUP_DIR}/restore_secure.sh" | |
| adb_get "settings list system 2>/dev/null" | while IFS='=' read -r key val; do | |
| echo "settings put system '${key}' '${val}'" | |
| done > "${BACKUP_DIR}/restore_system.sh" | |
| BACKUP_DONE=true | |
| ok "Backup zakończony: ${BACKUP_DIR}/" | |
| detail "restore_global.sh | restore_secure.sh | restore_system.sh" | |
| pause | |
| } | |
| restore_from_backup() { | |
| if [[ "$BACKUP_DONE" == "false" ]]; then | |
| warn "Nie wykonano backupu w tej sesji. Wykonaj najpierw opcję [1]."; pause; return | |
| fi | |
| step "Przywracam ustawienia z backupu..." | |
| for restore_script in "${BACKUP_DIR}"/restore_*.sh "${BACKUP_DIR}"/debloat_*.sh; do | |
| [[ -f "$restore_script" ]] || continue | |
| info "Przetwarzam: $(basename "$restore_script")" | |
| while IFS= read -r cmd; do | |
| [[ "$cmd" =~ ^# ]] && continue | |
| [[ -z "$cmd" ]] && continue | |
| adb_cmd "$cmd" >/dev/null 2>&1 || true | |
| done < "$restore_script" | |
| done | |
| ok "Przywracanie zakończone." | |
| pause | |
| } | |
| list_backups() { | |
| header "ISTNIEJĄCE BACKUPY" | |
| find "$LOG_DIR" -name "*.sh" -o -name "*.txt" 2>/dev/null | while IFS= read -r f; do | |
| printf " ${CYAN}%s${RESET} (%.1f KB)\n" "$f" "$(du -k "$f" | cut -f1)" | |
| done || info "Brak backupów." | |
| pause | |
| } | |
| export_backup_to_watch() { | |
| step "Eksportuje backup na zegarek..." | |
| adb_cmd "mkdir -p /sdcard/watch4_backup/" >/dev/null 2>&1 | |
| for f in "${BACKUP_DIR}"/*.sh "${BACKUP_DIR}"/*.txt; do | |
| [[ -f "$f" ]] || continue | |
| adb push "$f" "/sdcard/watch4_backup/$(basename "$f")" >/dev/null 2>&1 && ok "$(basename "$f")" || warn "Nie można przesłać: $f" | |
| done | |
| ok "Backup dostępny na zegarku: /sdcard/watch4_backup/" | |
| pause | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # SEKCJA 10 — MENU GŁÓWNE | |
| # ═══════════════════════════════════════════════════════════════════ | |
| main_menu() { | |
| while true; do | |
| print_banner | |
| printf " ${BOLD}Urządzenie: ${CYAN}%s${RESET} | ${BOLD}Model: ${CYAN}%s${RESET} | ${BOLD}Android: ${CYAN}%s${RESET}\n\n" \ | |
| "$DEVICE_SERIAL" "$DEVICE_MODEL" "$DEVICE_ANDROID" | |
| echo -e " ${BOLD}${WHITE}[ DIAGNOSTYKA ]${RESET}" | |
| echo " [1] Diagnostyka proaktywna (monitoring, logi, analiza)" | |
| echo | |
| echo -e " ${BOLD}${WHITE}[ OPTYMALIZACJA ]${RESET}" | |
| echo " [2] Zarządzanie AOD (Always-On Display)" | |
| echo " [3] Wydajność systemu (animacje, GPU, ART, CPU)" | |
| echo | |
| echo -e " ${BOLD}${WHITE}[ ZARZĄDZANIE SYSTEMEM ]${RESET}" | |
| echo " [4] Debloat — bezpieczne zarządzanie aplikacjami" | |
| echo " [5] Naprawa systemu (UI, sieć, sensory, restart)" | |
| echo | |
| echo -e " ${BOLD}${WHITE}[ DANE ]${RESET}" | |
| echo " [6] Backup i przywracanie ustawień" | |
| echo | |
| echo -e " ${BOLD}${WHITE}[ SZYBKIE AKCJE ]${RESET}" | |
| echo " [T] ⚡ Profil TURBO — pełna optymalizacja" | |
| echo " [R] ↩ Przywróć domyślne ustawienia wydajności" | |
| echo " [Q] Rozłącz i zakończ" | |
| sep | |
| read -rp "$(printf " Wybór: ")" choice | |
| case "${choice,,}" in | |
| 1) diagnostics_menu ;; | |
| 2) aod_menu ;; | |
| 3) performance_menu ;; | |
| 4) debloat_menu ;; | |
| 5) repair_menu ;; | |
| 6) backup_menu ;; | |
| t) apply_turbo_profile ;; | |
| r) restore_defaults_perf ;; | |
| q) | |
| info "Rozłączam ${DEVICE_SERIAL}..." | |
| adb disconnect "$DEVICE_SERIAL" &>/dev/null || true | |
| ok "Sesja zakończona." | |
| info "Log sesji: ${LOG_FILE}" | |
| if [[ "$BACKUP_DONE" == "true" ]]; then | |
| info "Backup ustawień: ${BACKUP_DIR}/" | |
| fi | |
| exit 0 | |
| ;; | |
| *) warn "Nieprawidłowy wybór." ; sleep 1 ;; | |
| esac | |
| done | |
| } | |
| # ═══════════════════════════════════════════════════════════════════ | |
| # PUNKT WEJŚCIA | |
| # ═══════════════════════════════════════════════════════════════════ | |
| main() { | |
| print_banner | |
| log "=== ${SCRIPT_NAME} v${VERSION} — Sesja rozpoczęta ===" | |
| log "System: $(uname -a)" | |
| check_adb_installed | |
| connect_device | |
| main_menu | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment