Skip to content

Commit e50c5a9

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
fix: battery_health example uses system batteryproperties service instead of vendor HAL
Replace android.hardware.health.IHealth vendor HAL (blocked by SELinux) with IBatteryPropertiesRegistrar via "batteryproperties" system service. When binder access fails, falls back to reading battery data from /sys/class/power_supply/.
1 parent 8ba06fa commit e50c5a9

1 file changed

Lines changed: 158 additions & 36 deletions

File tree

examples/battery_health/main.go

Lines changed: 158 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,39 @@
1-
// Query battery health from the hardware HAL: capacity, charge status, current.
1+
// Query battery health from the system battery properties service.
2+
//
3+
// Uses IBatteryPropertiesRegistrar via the "batteryproperties" service to query
4+
// battery properties via raw binder transact. Falls back to reading sysfs
5+
// if binder access is denied.
26
//
37
// Build:
48
//
5-
// GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o build/battery_health ./examples/battery_health/
9+
// GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o build/battery_health ./examples/battery_health/
610
// adb push battery_health /data/local/tmp/ && adb shell /data/local/tmp/battery_health
711
package main
812

913
import (
1014
"context"
1115
"fmt"
1216
"os"
17+
"strings"
1318

1419
"github.com/xaionaro-go/binder/binder"
1520
"github.com/xaionaro-go/binder/binder/versionaware"
16-
"github.com/xaionaro-go/binder/android/hardware/health"
1721
"github.com/xaionaro-go/binder/kernelbinder"
22+
"github.com/xaionaro-go/binder/parcel"
1823
"github.com/xaionaro-go/binder/servicemanager"
1924
)
2025

26+
// Android BatteryManager property IDs (from android.os.BatteryManager).
27+
const (
28+
batteryPropertyChargeCounter = 1 // BATTERY_PROPERTY_CHARGE_COUNTER (µAh)
29+
batteryPropertyCurrentNow = 2 // BATTERY_PROPERTY_CURRENT_NOW (µA)
30+
batteryPropertyCurrentAvg = 3 // BATTERY_PROPERTY_CURRENT_AVERAGE (µA)
31+
batteryPropertyCapacity = 4 // BATTERY_PROPERTY_CAPACITY (%)
32+
batteryPropertyStatus = 6 // BATTERY_PROPERTY_STATUS
33+
)
34+
35+
const descriptorBatteryProps = "android.os.IBatteryPropertiesRegistrar"
36+
2137
func main() {
2238
ctx := context.Background()
2339

@@ -36,53 +52,159 @@ func main() {
3652

3753
sm := servicemanager.New(transport)
3854

39-
svc, err := sm.GetService(ctx, servicemanager.ServiceName("android.hardware.health.IHealth/default"))
55+
svc, err := sm.GetService(ctx, servicemanager.ServiceName("batteryproperties"))
4056
if err != nil {
41-
fmt.Fprintf(os.Stderr, "get health HAL: %v\n", err)
42-
fmt.Fprintf(os.Stderr, "(health HAL may not be available on this device, or access may be blocked by SELinux)\n")
43-
os.Exit(1)
57+
fmt.Fprintf(os.Stderr, "get batteryproperties service: %v\n", err)
58+
fmt.Fprintln(os.Stderr, "Falling back to sysfs...")
59+
printSysfs()
60+
os.Exit(0)
61+
}
62+
63+
binderOK := false
64+
65+
// Query each property using raw binder transact to properly read
66+
// the BatteryProperty out-parameter.
67+
type propQuery struct {
68+
name string
69+
id int32
70+
unit string
71+
}
72+
73+
queries := []propQuery{
74+
{"Battery level", batteryPropertyCapacity, "%"},
75+
{"Charge counter", batteryPropertyChargeCounter, " uAh"},
76+
{"Current draw", batteryPropertyCurrentNow, " uA"},
77+
{"Current average", batteryPropertyCurrentAvg, " uA"},
78+
{"Battery status", batteryPropertyStatus, ""},
4479
}
4580

46-
h := health.NewHealthProxy(svc)
81+
for _, q := range queries {
82+
val, status, err := getProperty(ctx, svc, q.id)
83+
if err != nil {
84+
fmt.Fprintf(os.Stderr, "GetProperty(%s): %v\n", q.name, err)
85+
continue
86+
}
87+
if status != 0 {
88+
fmt.Fprintf(os.Stderr, "GetProperty(%s): status=%d\n", q.name, status)
89+
continue
90+
}
91+
binderOK = true
92+
if q.id == batteryPropertyStatus {
93+
fmt.Printf(" %-20s %s (%d)\n", q.name+":", statusToString(int32(val)), val)
94+
} else {
95+
fmt.Printf(" %-20s %d%s\n", q.name+":", val, q.unit)
96+
}
97+
}
98+
99+
if !binderOK {
100+
fmt.Fprintln(os.Stderr, "\nBinder calls failed; falling back to sysfs...")
101+
printSysfs()
102+
}
103+
}
104+
105+
// getProperty performs a raw binder transact to IBatteryPropertiesRegistrar.getProperty,
106+
// properly reading both the BatteryProperty out-parameter and the status code.
107+
func getProperty(
108+
ctx context.Context,
109+
remote binder.IBinder,
110+
propertyID int32,
111+
) (value int64, status int32, err error) {
112+
data := parcel.New()
113+
defer data.Recycle()
114+
data.WriteInterfaceToken(descriptorBatteryProps)
115+
data.WriteInt32(propertyID)
47116

48-
capacity, err := h.GetCapacity(ctx)
117+
code, err := remote.ResolveCode(ctx, descriptorBatteryProps, "getProperty")
49118
if err != nil {
50-
fmt.Fprintf(os.Stderr, "GetCapacity: %v (health HAL may have died or returned an error)\n", err)
51-
} else {
52-
fmt.Printf("Battery level: %d%%\n", capacity)
119+
return 0, 0, fmt.Errorf("resolving getProperty: %w", err)
53120
}
54121

55-
status, err := h.GetChargeStatus(ctx)
122+
reply, err := remote.Transact(ctx, code, 0, data)
56123
if err != nil {
57-
fmt.Fprintf(os.Stderr, "GetChargeStatus: %v (health HAL may have died or returned an error)\n", err)
58-
} else {
59-
statusName := "unknown"
60-
switch status {
61-
case health.BatteryStatusUNKNOWN:
62-
statusName = "unknown"
63-
case health.BatteryStatusCHARGING:
64-
statusName = "charging"
65-
case health.BatteryStatusDISCHARGING:
66-
statusName = "discharging"
67-
case health.BatteryStatusNotCharging:
68-
statusName = "not charging"
69-
case health.BatteryStatusFULL:
70-
statusName = "full"
124+
return 0, 0, err
125+
}
126+
defer reply.Recycle()
127+
128+
if err = binder.ReadStatus(reply); err != nil {
129+
return 0, 0, err
130+
}
131+
132+
// Read the BatteryProperty out-parameter (nullable Parcelable).
133+
nullInd, err := reply.ReadInt32()
134+
if err != nil {
135+
return 0, 0, fmt.Errorf("reading null indicator: %w", err)
136+
}
137+
if nullInd != 0 {
138+
// BatteryProperty: { int64 ValueLong; String ValueString; }
139+
value, err = reply.ReadInt64()
140+
if err != nil {
141+
return 0, 0, fmt.Errorf("reading ValueLong: %w", err)
142+
}
143+
_, err = reply.ReadString() // ValueString (usually empty)
144+
if err != nil {
145+
return 0, 0, fmt.Errorf("reading ValueString: %w", err)
71146
}
72-
fmt.Printf("Charge status: %s (%d)\n", statusName, status)
73147
}
74148

75-
current, err := h.GetCurrentNowMicroamps(ctx)
149+
// Read the return value (status code: 0 = success).
150+
status, err = reply.ReadInt32()
76151
if err != nil {
77-
fmt.Fprintf(os.Stderr, "GetCurrentNowMicroamps: %v (health HAL may have died or returned an error)\n", err)
78-
} else {
79-
fmt.Printf("Current draw: %d µA\n", current)
152+
return 0, 0, fmt.Errorf("reading status: %w", err)
80153
}
81154

82-
counter, err := h.GetChargeCounterUah(ctx)
155+
return value, status, nil
156+
}
157+
158+
func statusToString(status int32) string {
159+
switch status {
160+
case 1:
161+
return "unknown"
162+
case 2:
163+
return "charging"
164+
case 3:
165+
return "discharging"
166+
case 4:
167+
return "not charging"
168+
case 5:
169+
return "full"
170+
default:
171+
return fmt.Sprintf("unknown(%d)", status)
172+
}
173+
}
174+
175+
func printSysfs() {
176+
base := "/sys/class/power_supply/"
177+
entries, err := os.ReadDir(base)
178+
if err != nil {
179+
fmt.Fprintf(os.Stderr, "read sysfs: %v\n", err)
180+
return
181+
}
182+
183+
for _, entry := range entries {
184+
dir := base + entry.Name() + "/"
185+
typ := readSysfs(dir + "type")
186+
if typ == "" {
187+
continue
188+
}
189+
fmt.Printf("\n=== %s (type: %s) ===\n", entry.Name(), typ)
190+
191+
for _, attr := range []string{
192+
"status", "capacity", "charge_counter",
193+
"current_now", "current_avg", "voltage_now",
194+
"temp", "health", "technology",
195+
} {
196+
val := readSysfs(dir + attr)
197+
if val != "" {
198+
fmt.Printf(" %-18s %s\n", attr+":", val)
199+
}
200+
}
201+
}
202+
}
203+
204+
func readSysfs(path string) string {
205+
data, err := os.ReadFile(path)
83206
if err != nil {
84-
fmt.Fprintf(os.Stderr, "GetChargeCounterUah: %v (health HAL may have died or returned an error)\n", err)
85-
} else {
86-
fmt.Printf("Charge counter: %d µAh\n", counter)
207+
return ""
87208
}
209+
return strings.TrimSpace(string(data))
88210
}

0 commit comments

Comments
 (0)