package main import ( "fmt" "sort" "strings" ) type PropEntry struct { Key string `json:"key"` Value string `json:"value"` Category string `json:"category"` } // GetAllProps returns all system properties as structured entries. func (a *App) GetAllProps() ([]PropEntry, error) { out, err := a.runAdbShell("getprop") if err != nil { return nil, fmt.Errorf("failed to get props: %w", err) } var props []PropEntry for _, line := range strings.Split(out, "\n") { line = strings.TrimSpace(line) if !strings.HasPrefix(line, "[") { continue } // Format: [key]: [value] line = strings.TrimPrefix(line, "[") parts := strings.SplitN(line, "]: [", 2) if len(parts) != 2 { continue } key := parts[0] value := strings.TrimSuffix(parts[1], "]") props = append(props, PropEntry{ Key: key, Value: value, Category: categorizeProp(key), }) } sort.Slice(props, func(i, j int) bool { return props[i].Key < props[j].Key }) return props, nil } // SetProp sets a system property value. // Note: many system props are read-only and require root to change. func (a *App) SetProp(key, value string) (string, error) { if key == "" { return "", fmt.Errorf("key cannot be empty") } // Validate key characters for _, ch := range key { if ch == ';' || ch == '&' || ch == '|' || ch == '`' || ch == '$' || ch == '\n' { return "", fmt.Errorf("invalid character in property key") } } // Try setprop first (works for some props without root) _, err := a.runAdbShell("setprop", key, value) if err != nil { // Try with root _, err2 := a.runAdbShell("su", "-c", fmt.Sprintf("setprop %s %s", key, value)) if err2 != nil { return "", fmt.Errorf("failed to set prop (may be read-only or need root): %w", err) } } // Verify newVal, _ := a.runAdbShell("getprop", key) return fmt.Sprintf("Set %s = %s", key, strings.TrimSpace(newVal)), nil } // GetProp gets a single property value. func (a *App) GetProp(key string) (string, error) { if key == "" { return "", fmt.Errorf("key cannot be empty") } out, err := a.runAdbShell("getprop", key) if err != nil { return "", err } return strings.TrimSpace(out), nil } // categorizeProp assigns a category to a property based on its key prefix. func categorizeProp(key string) string { switch { // Match by substring first so these group regardless of prefix. case strings.Contains(key, "uwb"): return "UWB" case strings.Contains(key, "satellite"): return "Satellite" // Verified Boot / AVB + post-quantum signature schemes (Android 17 PQC). case strings.Contains(key, "vbmeta") || strings.Contains(key, "avb") || strings.Contains(key, "pqc") || strings.Contains(key, "dilithium") || strings.Contains(key, "ml_dsa") || strings.Contains(key, "ml-dsa") || strings.Contains(key, "sphincs") || strings.Contains(key, "falcon"): return "Verified Boot / PQC" case strings.HasPrefix(key, "ro.build"): return "Build" case strings.HasPrefix(key, "ro.product"): return "Product" case strings.HasPrefix(key, "ro.boot"): return "Boot" case strings.HasPrefix(key, "ro.hardware"): return "Hardware" case strings.HasPrefix(key, "ro.crypto"): return "Security" case strings.HasPrefix(key, "ro.debuggable") || strings.HasPrefix(key, "service.adb"): return "Debug" case strings.HasPrefix(key, "persist."): return "Persist" case strings.HasPrefix(key, "sys."): return "System" case strings.HasPrefix(key, "net.") || strings.HasPrefix(key, "dhcp.") || strings.HasPrefix(key, "wifi."): return "Network" case strings.HasPrefix(key, "gsm.") || strings.HasPrefix(key, "ril.") || strings.HasPrefix(key, "telephony."): return "Telephony" case strings.HasPrefix(key, "dalvik."): return "Dalvik/ART" case strings.HasPrefix(key, "init."): return "Init" case strings.HasPrefix(key, "dev."): return "Device" case strings.HasPrefix(key, "audio.") || strings.HasPrefix(key, "media."): return "Media" case strings.HasPrefix(key, "camera."): return "Camera" case strings.HasPrefix(key, "bluetooth."): return "Bluetooth" default: return "Other" } }