Skip to content

Commit 719cbd2

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
fix: E2E validation — handle permission errors gracefully in examples
flashlight_torch: pass non-null client binder token (camera service rejects nil with NullPointerException), make GetNumberOfCameras non-fatal, document android.permission.CAMERA requirement. server_service: handle SELinux denial on AddService gracefully by falling back to in-process self-test when running from shell context. Validated on Pixel device (41041JEKB08092): - list_packages: 325 packages listed successfully - error_handling: all 4 sections pass (availability, permissions, typed errors, degradation) - server_service: SELinux fallback works, in-process ping/echo pass - flashlight_torch: permission error reported clearly (requires CAMERA permission)
1 parent 71b3df5 commit 719cbd2

4 files changed

Lines changed: 190 additions & 51 deletions

File tree

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,13 @@ import (
289289

290290
### Toggle Flashlight
291291

292+
Requires `android.permission.CAMERA`; see [`examples/flashlight_torch/`](examples/flashlight_torch/) for the full runnable example with permission handling.
293+
292294
```go
293295
import (
294296
"github.com/AndroidGoLab/binder/android/hardware"
297+
"github.com/AndroidGoLab/binder/binder"
298+
"github.com/AndroidGoLab/binder/parcel"
295299
"github.com/AndroidGoLab/binder/servicemanager"
296300
)
297301

@@ -301,14 +305,23 @@ import (
301305
}
302306
camera := hardware.NewCameraServiceProxy(svc)
303307

308+
// The camera service requires a non-null client binder token.
309+
type token struct{}
310+
func (t *token) Descriptor() string { return "torch.client" }
311+
func (t *token) OnTransaction(_ context.Context, _ binder.TransactionCode, _ *parcel.Parcel) (*parcel.Parcel, error) {
312+
return parcel.New(), nil
313+
}
314+
clientToken := binder.NewStubBinder(&token{})
315+
clientToken.RegisterWithTransport(ctx, transport)
316+
304317
// Turn torch on for camera "0"
305-
if err := camera.SetTorchMode(ctx, "0", true, nil); err != nil {
318+
if err := camera.SetTorchMode(ctx, "0", true, clientToken); err != nil {
306319
log.Fatal(err)
307320
}
308321
fmt.Println("Torch ON")
309322

310323
// Turn torch off
311-
_ = camera.SetTorchMode(ctx, "0", false, nil)
324+
_ = camera.SetTorchMode(ctx, "0", false, clientToken)
312325
```
313326

314327
### List All Installed Packages

examples/flashlight_torch/main.go

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
// By default, turns the torch ON for 3 seconds, then turns it OFF.
55
// Pass "on" or "off" as a command-line argument to set a specific state.
66
//
7+
// Note: the framework camera service (media.camera) requires the caller
8+
// to hold android.permission.CAMERA. From the adb shell context this
9+
// typically fails with a permission error. Run from an app context or
10+
// with elevated privileges for full functionality.
11+
//
712
// Build:
813
//
914
// GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o build/flashlight_torch ./examples/flashlight_torch/
@@ -20,9 +25,25 @@ import (
2025
"github.com/AndroidGoLab/binder/binder"
2126
"github.com/AndroidGoLab/binder/binder/versionaware"
2227
"github.com/AndroidGoLab/binder/kernelbinder"
28+
"github.com/AndroidGoLab/binder/parcel"
2329
"github.com/AndroidGoLab/binder/servicemanager"
2430
)
2531

32+
// torchClientToken is a minimal TransactionReceiver used as the client
33+
// binder token for SetTorchMode. The camera service requires a non-null
34+
// binder to track torch ownership.
35+
type torchClientToken struct{}
36+
37+
func (t *torchClientToken) Descriptor() string { return "torch.client" }
38+
39+
func (t *torchClientToken) OnTransaction(
40+
_ context.Context,
41+
_ binder.TransactionCode,
42+
_ *parcel.Parcel,
43+
) (*parcel.Parcel, error) {
44+
return parcel.New(), nil
45+
}
46+
2647
func main() {
2748
ctx := context.Background()
2849

@@ -49,13 +70,14 @@ func main() {
4970

5071
cam := hardware.NewCameraServiceProxy(svc)
5172

52-
// Report the number of available cameras.
73+
// Report the number of available cameras (informational; may fail
74+
// if the caller lacks android.permission.CAMERA).
5375
numCameras, err := cam.GetNumberOfCameras(ctx, hardware.ICameraServiceCameraTypeBackwardCompatible)
5476
if err != nil {
55-
fmt.Fprintf(os.Stderr, "GetNumberOfCameras: %v\n", err)
56-
os.Exit(1)
77+
fmt.Fprintf(os.Stderr, "GetNumberOfCameras: %v (continuing anyway)\n", err)
78+
} else {
79+
fmt.Printf("Number of cameras: %d\n", numCameras)
5780
}
58-
fmt.Printf("Number of cameras: %d\n", numCameras)
5981

6082
// Determine the desired action from command-line arguments.
6183
action := "toggle" // default: on, wait, off
@@ -71,40 +93,46 @@ func main() {
7193
}
7294
}
7395

96+
// The camera service requires a non-null client binder token to
97+
// track torch ownership. Create a stub and register it with the
98+
// transport so it gets a valid cookie.
99+
clientToken := binder.NewStubBinder(&torchClientToken{})
100+
clientToken.RegisterWithTransport(ctx, transport)
101+
74102
const cameraID = "0"
75103

76104
switch action {
77105
case "on":
78-
fmt.Printf("Turning torch ON for camera %s\n", cameraID)
79-
if err := cam.SetTorchMode(ctx, cameraID, true, nil); err != nil {
80-
fmt.Fprintf(os.Stderr, "SetTorchMode(on): %v\n", err)
81-
os.Exit(1)
82-
}
83-
fmt.Println("Torch is ON.")
106+
setTorch(ctx, cam, cameraID, true, clientToken)
84107

85108
case "off":
86-
fmt.Printf("Turning torch OFF for camera %s\n", cameraID)
87-
if err := cam.SetTorchMode(ctx, cameraID, false, nil); err != nil {
88-
fmt.Fprintf(os.Stderr, "SetTorchMode(off): %v\n", err)
89-
os.Exit(1)
90-
}
91-
fmt.Println("Torch is OFF.")
109+
setTorch(ctx, cam, cameraID, false, clientToken)
92110

93111
default: // toggle
94-
fmt.Printf("Turning torch ON for camera %s\n", cameraID)
95-
if err := cam.SetTorchMode(ctx, cameraID, true, nil); err != nil {
96-
fmt.Fprintf(os.Stderr, "SetTorchMode(on): %v\n", err)
97-
os.Exit(1)
98-
}
99-
fmt.Println("Torch is ON. Waiting 3 seconds...")
100-
112+
setTorch(ctx, cam, cameraID, true, clientToken)
113+
fmt.Println("Waiting 3 seconds...")
101114
time.Sleep(3 * time.Second)
115+
setTorch(ctx, cam, cameraID, false, clientToken)
116+
}
117+
}
102118

103-
fmt.Printf("Turning torch OFF for camera %s\n", cameraID)
104-
if err := cam.SetTorchMode(ctx, cameraID, false, nil); err != nil {
105-
fmt.Fprintf(os.Stderr, "SetTorchMode(off): %v\n", err)
106-
os.Exit(1)
107-
}
108-
fmt.Println("Torch is OFF.")
119+
func setTorch(
120+
ctx context.Context,
121+
cam *hardware.CameraServiceProxy,
122+
cameraID string,
123+
enabled bool,
124+
clientToken binder.IBinder,
125+
) {
126+
state := "OFF"
127+
if enabled {
128+
state = "ON"
129+
}
130+
131+
fmt.Printf("Turning torch %s for camera %s\n", state, cameraID)
132+
if err := cam.SetTorchMode(ctx, cameraID, enabled, clientToken); err != nil {
133+
fmt.Fprintf(os.Stderr, "SetTorchMode(%s): %v\n", state, err)
134+
fmt.Fprintf(os.Stderr, "Hint: this requires android.permission.CAMERA; from adb shell, try running as root.\n")
135+
os.Exit(1)
109136
}
137+
fmt.Printf("Torch is %s.\n", state)
110138
}

examples/server_service/main.go

Lines changed: 103 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
// Register a Go service with the ServiceManager and call it back.
22
//
3+
// Demonstrates implementing binder.TransactionReceiver — the server-side
4+
// binder interface — and registering it via AddService. When running in
5+
// a restricted SELinux context (e.g. shell), AddService will be denied;
6+
// in that case the example falls back to an in-process self-test that
7+
// exercises the same OnTransaction code path.
8+
//
39
// Build:
410
//
5-
// GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o build/server_service ./examples/server_service/
6-
// adb push server_service /data/local/tmp/ && adb shell /data/local/tmp/server_service
11+
// GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o build/server_service ./examples/server_service/
12+
// adb push build/server_service /data/local/tmp/ && adb shell /data/local/tmp/server_service
713
package main
814

915
import (
1016
"context"
17+
"errors"
1118
"fmt"
1219
"os"
1320

1421
"github.com/AndroidGoLab/binder/binder"
1522
"github.com/AndroidGoLab/binder/binder/versionaware"
23+
aidlerrors "github.com/AndroidGoLab/binder/errors"
1624
"github.com/AndroidGoLab/binder/kernelbinder"
1725
"github.com/AndroidGoLab/binder/parcel"
1826
"github.com/AndroidGoLab/binder/servicemanager"
@@ -88,71 +96,148 @@ func main() {
8896

8997
// Register our ping service with the service manager.
9098
svc := &pingService{}
91-
if err := sm.AddService(ctx, servicemanager.ServiceName(pingServiceName), svc, false, 0); err != nil {
92-
fmt.Fprintf(os.Stderr, "add service: %v\n", err)
93-
os.Exit(1)
99+
err = sm.AddService(ctx, servicemanager.ServiceName(pingServiceName), svc, false, 0)
100+
101+
switch {
102+
case err == nil:
103+
fmt.Printf("Registered service %q\n", pingServiceName)
104+
remoteTest(ctx, sm)
105+
106+
default:
107+
// AddService typically fails from the shell SELinux context.
108+
// Show the error and fall back to an in-process self-test.
109+
var se *aidlerrors.StatusError
110+
if errors.As(err, &se) && se.Exception == aidlerrors.ExceptionSecurity {
111+
fmt.Printf("AddService denied by SELinux (expected from shell context).\n")
112+
fmt.Printf("Falling back to in-process self-test.\n\n")
113+
} else {
114+
fmt.Fprintf(os.Stderr, "add service: %v\n", err)
115+
fmt.Fprintf(os.Stderr, "Falling back to in-process self-test.\n\n")
116+
}
117+
inProcessTest(ctx, svc)
94118
}
95-
fmt.Printf("Registered service %q\n", pingServiceName)
119+
}
96120

97-
// Self-test: look up the service we just registered and call it.
121+
// remoteTest looks up the service via the ServiceManager and calls it
122+
// through the binder driver, exercising the full IPC path.
123+
func remoteTest(ctx context.Context, sm *servicemanager.ServiceManager) {
98124
remote, err := sm.GetService(ctx, servicemanager.ServiceName(pingServiceName))
99125
if err != nil {
100126
fmt.Fprintf(os.Stderr, "get service: %v\n", err)
101127
os.Exit(1)
102128
}
103129

104-
// Call Ping (codePing).
130+
callPing(ctx, remote)
131+
callEcho(ctx, remote, "hello from Go")
132+
fmt.Println("All remote self-tests passed.")
133+
}
134+
135+
// inProcessTest calls OnTransaction directly, verifying the handler
136+
// logic without requiring ServiceManager registration.
137+
func inProcessTest(ctx context.Context, svc *pingService) {
138+
// Ping
105139
{
106140
data := parcel.New()
107141
defer data.Recycle()
108142
data.WriteInterfaceToken(pingServiceDescriptor)
109143

110-
reply, err := remote.Transact(ctx, codePing, 0, data)
144+
reply, err := svc.OnTransaction(ctx, codePing, data)
111145
if err != nil {
112-
fmt.Fprintf(os.Stderr, "ping transact: %v\n", err)
146+
fmt.Fprintf(os.Stderr, "in-process ping: %v\n", err)
113147
os.Exit(1)
114148
}
115149
defer reply.Recycle()
116150

117151
if err := binder.ReadStatus(reply); err != nil {
118-
fmt.Fprintf(os.Stderr, "ping status: %v\n", err)
152+
fmt.Fprintf(os.Stderr, "in-process ping status: %v\n", err)
119153
os.Exit(1)
120154
}
121155

122156
result, err := reply.ReadString16()
123157
if err != nil {
124-
fmt.Fprintf(os.Stderr, "ping read result: %v\n", err)
158+
fmt.Fprintf(os.Stderr, "in-process ping read: %v\n", err)
125159
os.Exit(1)
126160
}
127161
fmt.Printf("Ping -> %q\n", result)
128162
}
129163

130-
// Call Echo (codeEcho).
164+
// Echo
131165
{
132166
data := parcel.New()
133167
defer data.Recycle()
134168
data.WriteInterfaceToken(pingServiceDescriptor)
135169
data.WriteString16("hello from Go")
136170

137-
reply, err := remote.Transact(ctx, codeEcho, 0, data)
171+
reply, err := svc.OnTransaction(ctx, codeEcho, data)
138172
if err != nil {
139-
fmt.Fprintf(os.Stderr, "echo transact: %v\n", err)
173+
fmt.Fprintf(os.Stderr, "in-process echo: %v\n", err)
140174
os.Exit(1)
141175
}
142176
defer reply.Recycle()
143177

144178
if err := binder.ReadStatus(reply); err != nil {
145-
fmt.Fprintf(os.Stderr, "echo status: %v\n", err)
179+
fmt.Fprintf(os.Stderr, "in-process echo status: %v\n", err)
146180
os.Exit(1)
147181
}
148182

149183
result, err := reply.ReadString16()
150184
if err != nil {
151-
fmt.Fprintf(os.Stderr, "echo read result: %v\n", err)
185+
fmt.Fprintf(os.Stderr, "in-process echo read: %v\n", err)
152186
os.Exit(1)
153187
}
154188
fmt.Printf("Echo -> %q\n", result)
155189
}
156190

157-
fmt.Println("All self-tests passed.")
191+
fmt.Println("All in-process self-tests passed.")
192+
}
193+
194+
func callPing(ctx context.Context, remote binder.IBinder) {
195+
data := parcel.New()
196+
defer data.Recycle()
197+
data.WriteInterfaceToken(pingServiceDescriptor)
198+
199+
reply, err := remote.Transact(ctx, codePing, 0, data)
200+
if err != nil {
201+
fmt.Fprintf(os.Stderr, "ping transact: %v\n", err)
202+
os.Exit(1)
203+
}
204+
defer reply.Recycle()
205+
206+
if err := binder.ReadStatus(reply); err != nil {
207+
fmt.Fprintf(os.Stderr, "ping status: %v\n", err)
208+
os.Exit(1)
209+
}
210+
211+
result, err := reply.ReadString16()
212+
if err != nil {
213+
fmt.Fprintf(os.Stderr, "ping read result: %v\n", err)
214+
os.Exit(1)
215+
}
216+
fmt.Printf("Ping -> %q\n", result)
217+
}
218+
219+
func callEcho(ctx context.Context, remote binder.IBinder, msg string) {
220+
data := parcel.New()
221+
defer data.Recycle()
222+
data.WriteInterfaceToken(pingServiceDescriptor)
223+
data.WriteString16(msg)
224+
225+
reply, err := remote.Transact(ctx, codeEcho, 0, data)
226+
if err != nil {
227+
fmt.Fprintf(os.Stderr, "echo transact: %v\n", err)
228+
os.Exit(1)
229+
}
230+
defer reply.Recycle()
231+
232+
if err := binder.ReadStatus(reply); err != nil {
233+
fmt.Fprintf(os.Stderr, "echo status: %v\n", err)
234+
os.Exit(1)
235+
}
236+
237+
result, err := reply.ReadString16()
238+
if err != nil {
239+
fmt.Fprintf(os.Stderr, "echo read result: %v\n", err)
240+
os.Exit(1)
241+
}
242+
fmt.Printf("Echo -> %q\n", result)
158243
}

0 commit comments

Comments
 (0)