14935db63e
Adds full ADB client, device manager, static DeviceInfo fetcher, and live DeviceLiveStats poller. Exposes ListDevices, ConnectDevice, DisconnectDevice, GetDeviceInfo, GetDeviceLiveStats as Wails-bound methods with a 1s devices:changed event loop. Bundles ADB binary infrastructure via //go:embed all:bin with runtime fallback chain.
143 lines
3.5 KiB
Go
143 lines
3.5 KiB
Go
package device
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"git.achmad.dev/admin/droidscope/backend/adb"
|
|
)
|
|
|
|
type Manager struct {
|
|
adb *adb.Client
|
|
Client *adb.Client // exported for use by other packages
|
|
}
|
|
|
|
func NewManager(client *adb.Client) *Manager {
|
|
return &Manager{adb: client, Client: client}
|
|
}
|
|
|
|
// Info fetches static device information for the given device ID.
|
|
func (m *Manager) Info(ctx context.Context, deviceID string) (*DeviceInfo, error) {
|
|
return FetchInfo(ctx, m.adb, deviceID)
|
|
}
|
|
|
|
// LiveStats fetches dynamic device stats for the given device ID.
|
|
func (m *Manager) LiveStats(ctx context.Context, deviceID string) (*DeviceLiveStats, error) {
|
|
return FetchLiveStats(ctx, m.adb, deviceID)
|
|
}
|
|
|
|
// List returns all currently connected devices with their details.
|
|
func (m *Manager) List(ctx context.Context) ([]Device, error) {
|
|
raw, err := m.adb.Devices(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
serials := parseDeviceLines(raw)
|
|
devices := make([]Device, 0, len(serials))
|
|
for serial, status := range serials {
|
|
d := Device{
|
|
ID: serial,
|
|
Status: status,
|
|
ConnectionType: connectionType(serial),
|
|
}
|
|
if status == StatusOnline {
|
|
enrichDevice(ctx, m.adb, &d)
|
|
}
|
|
devices = append(devices, d)
|
|
}
|
|
return devices, nil
|
|
}
|
|
|
|
// Connect connects to a wireless ADB device by address (host:port).
|
|
func (m *Manager) Connect(ctx context.Context, address string) error {
|
|
out, err := m.adb.Connect(ctx, address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.Contains(out, "failed") || strings.Contains(out, "error") {
|
|
return &Error{Message: out}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Disconnect disconnects a device.
|
|
func (m *Manager) Disconnect(ctx context.Context, deviceID string) error {
|
|
_, err := m.adb.Disconnect(ctx, deviceID)
|
|
return err
|
|
}
|
|
|
|
// parseDeviceLines parses `adb devices -l` output into a serial→status map.
|
|
func parseDeviceLines(raw string) map[string]Status {
|
|
result := make(map[string]Status)
|
|
for _, line := range strings.Split(raw, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "List of") {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
serial := fields[0]
|
|
result[serial] = parseStatus(fields[1])
|
|
}
|
|
return result
|
|
}
|
|
|
|
func parseStatus(s string) Status {
|
|
switch s {
|
|
case "device":
|
|
return StatusOnline
|
|
case "offline":
|
|
return StatusOffline
|
|
case "unauthorized":
|
|
return StatusUnauthorized
|
|
case "recovery":
|
|
return StatusRecovery
|
|
default:
|
|
return StatusOffline
|
|
}
|
|
}
|
|
|
|
func connectionType(serial string) ConnectionType {
|
|
if strings.Contains(serial, ":") {
|
|
return ConnectionWifi
|
|
}
|
|
return ConnectionUSB
|
|
}
|
|
|
|
// enrichDevice fetches device properties via adb shell getprop.
|
|
func enrichDevice(ctx context.Context, client *adb.Client, d *Device) {
|
|
props := map[string]*string{
|
|
"ro.product.model": &d.Model,
|
|
"ro.product.manufacturer": &d.Manufacturer,
|
|
"ro.build.version.release": &d.AndroidVersion,
|
|
"ro.product.cpu.abi": &d.ABI,
|
|
"ro.product.name": &d.Product,
|
|
}
|
|
for prop, dest := range props {
|
|
if val, err := client.Shell(ctx, d.ID, "getprop "+prop); err == nil {
|
|
*dest = strings.TrimSpace(val)
|
|
}
|
|
}
|
|
if level, err := client.Shell(ctx, d.ID, "dumpsys battery | grep level | tr -dc '0-9'"); err == nil {
|
|
d.BatteryLevel = parseIntSafe(strings.TrimSpace(level))
|
|
}
|
|
}
|
|
|
|
func parseIntSafe(s string) int {
|
|
n := 0
|
|
for _, c := range s {
|
|
if c >= '0' && c <= '9' {
|
|
n = n*10 + int(c-'0')
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
type Error struct {
|
|
Message string
|
|
}
|
|
|
|
func (e *Error) Error() string { return e.Message }
|