# Binder Binder is a pure Go library that enables direct communication with Android system services via the Binder IPC (Inter-Process Communication) protocol. It provides type-safe access to approximately 14,000 methods across 1,500+ Android interfaces including ActivityManager, PowerManager, SurfaceFlinger, PackageManager, camera HALs, sensor HALs, and more. The library speaks the Binder wire protocol directly via `/dev/binder` ioctl syscalls, requiring no Java, NDK, or cgo dependencies. The project includes a complete AIDL (Android Interface Definition Language) compiler that parses `.aidl` files and generates Go proxies, a version-aware runtime that adapts transaction codes across Android API levels (34, 35, 36+), and a CLI tool (`bindercli`) for interactive service discovery and invocation. The library enables building pure Go applications that run on Android devices or any Linux system with binder driver support, making it ideal for lightweight CLI tools, system utilities, and embedded applications. ## Installation Install the library with `go get`: ```bash go get github.com/AndroidGoLab/binder ``` ## Core Setup: Opening a Binder Connection Every binder interaction starts with opening the kernel driver and creating a version-aware transport. This pattern is used by all examples and applications. ```go package main import ( "context" "fmt" "os" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/servicemanager" ) func main() { ctx := context.Background() // Open the binder kernel driver with a 128KB memory map driver, err := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) if err != nil { fmt.Fprintf(os.Stderr, "open binder: %v\n", err) os.Exit(1) } defer driver.Close(ctx) // Create version-aware transport for cross-version compatibility transport, err := versionaware.NewTransport(ctx, driver, 0) if err != nil { fmt.Fprintf(os.Stderr, "version-aware transport: %v\n", err) os.Exit(1) } // Create ServiceManager client - gateway to all Android services sm := servicemanager.New(transport) // Now use sm to get services... services, _ := sm.ListServices(ctx) fmt.Printf("Found %d registered services\n", len(services)) } ``` ## ServiceManager: Listing and Discovering Services The ServiceManager is Android's registry of all system services. Use it to list available services and check their status. ```go package main import ( "context" "fmt" "os" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/servicemanager" ) func main() { ctx := context.Background() driver, _ := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) defer driver.Close(ctx) transport, _ := versionaware.NewTransport(ctx, driver, 0) sm := servicemanager.New(transport) // List all registered services services, err := sm.ListServices(ctx) if err != nil { fmt.Fprintf(os.Stderr, "list services: %v\n", err) os.Exit(1) } fmt.Printf("Found %d registered services:\n\n", len(services)) // Check each service and ping it for i, name := range services { svc, err := sm.CheckService(ctx, name) status := "unreachable" if err == nil && svc != nil { if svc.IsAlive(ctx) { status = "alive" } else { status = "dead" } } fmt.Printf(" [%3d] %-60s %s\n", i+1, name, status) } } // Output: // Found 245 registered services: // [ 1] accessibility alive // [ 2] account alive // [ 3] activity alive // ... ``` ## PowerManager: Querying Power State Query device power state including screen status, power save mode, and idle mode. ```go package main import ( "context" "fmt" "os" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" genOs "github.com/AndroidGoLab/binder/android/os" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/servicemanager" ) func main() { ctx := context.Background() driver, _ := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) defer driver.Close(ctx) transport, _ := versionaware.NewTransport(ctx, driver, 0) sm := servicemanager.New(transport) // Get PowerManager using the helper function power, err := genOs.GetPowerManager(ctx, sm) if err != nil { fmt.Fprintf(os.Stderr, "get power service: %v\n", err) os.Exit(1) } // Check if screen is on interactive, _ := power.IsInteractive(ctx) fmt.Printf("Interactive (screen on): %v\n", interactive) // Check power save mode powerSave, _ := power.IsPowerSaveMode(ctx) fmt.Printf("Power save mode: %v\n", powerSave) // Check battery saver support batterySaver, _ := power.IsBatterySaverSupported(ctx) fmt.Printf("Battery saver supported: %v\n", batterySaver) // Check Doze mode idle, _ := power.IsDeviceIdleMode(ctx) fmt.Printf("Device idle (Doze): %v\n", idle) // Check low power standby lowPower, _ := power.IsLowPowerStandbyEnabled(ctx) fmt.Printf("Low power standby: %v\n", lowPower) } // Output: // Interactive (screen on): true // Power save mode: false // Battery saver supported: true // Device idle (Doze): false // Low power standby: false ``` ## ActivityManager: Process Management and Permissions Query process limits, check permissions, and get information about running processes. ```go package main import ( "context" "fmt" "os" "github.com/AndroidGoLab/binder/android/app" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/servicemanager" ) func main() { ctx := context.Background() driver, _ := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) defer driver.Close(ctx) transport, _ := versionaware.NewTransport(ctx, driver, 0) sm := servicemanager.New(transport) // Get ActivityManager service svc, err := sm.GetService(ctx, servicemanager.ActivityService) if err != nil { fmt.Fprintf(os.Stderr, "get activity service: %v\n", err) os.Exit(1) } am := app.NewActivityManagerProxy(svc) // Query process limit limit, _ := am.GetProcessLimit(ctx) fmt.Printf("Process limit: %d\n", limit) // Check if running under automated test (monkey) monkey, _ := am.IsUserAMonkey(ctx) fmt.Printf("Is monkey test: %v\n", monkey) // Check app freezer support freezer, _ := am.IsAppFreezerSupported(ctx) fmt.Printf("App freezer: %v\n", freezer) // Check permissions for current process myPid := int32(os.Getpid()) myUid := int32(os.Getuid()) permissions := []string{ "android.permission.INTERNET", "android.permission.CAMERA", "android.permission.ACCESS_FINE_LOCATION", } fmt.Printf("\nPermission check (pid=%d, uid=%d):\n", myPid, myUid) for _, perm := range permissions { result, err := am.CheckPermission(ctx, perm, myPid, myUid) if err != nil { fmt.Printf(" %-45s error: %v\n", perm, err) continue } status := "DENIED" if result == 0 { // 0 = PERMISSION_GRANTED status = "GRANTED" } fmt.Printf(" %-45s %s\n", perm, status) } } // Output: // Process limit: -1 // Is monkey test: false // App freezer: true // Permission check (pid=1234, uid=2000): // android.permission.INTERNET GRANTED // android.permission.CAMERA DENIED // android.permission.ACCESS_FINE_LOCATION DENIED ``` ## PackageManager: Querying Installed Packages Check package availability, get target SDK versions, and query installer information. ```go package main import ( "context" "fmt" "os" "github.com/AndroidGoLab/binder/android/content/pm" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/servicemanager" ) func main() { ctx := context.Background() driver, _ := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) defer driver.Close(ctx) transport, _ := versionaware.NewTransport(ctx, driver, 0) sm := servicemanager.New(transport) // Get PackageManager service svc, _ := sm.GetService(ctx, servicemanager.ServiceName("package")) pkgMgr := pm.NewPackageManagerProxy(svc) packages := []string{ "com.android.settings", "com.android.systemui", "com.google.android.gms", "com.example.nonexistent", } fmt.Println("Package availability:") for _, pkg := range packages { available, err := pkgMgr.IsPackageAvailable(ctx, pkg) if err != nil { fmt.Printf(" %-40s error: %v\n", pkg, err) continue } fmt.Printf(" %-40s %v\n", pkg, available) } fmt.Println("\nPackage details:") for _, pkg := range packages { sdk, err := pkgMgr.GetTargetSdkVersion(ctx, pkg) if err != nil { continue } installer, _ := pkgMgr.GetInstallerPackageName(ctx, pkg) fmt.Printf(" %-40s SDK %d installer: %s\n", pkg, sdk, installer) } } // Output: // Package availability: // com.android.settings true // com.android.systemui true // com.google.android.gms true // com.example.nonexistent false // Package details: // com.android.settings SDK 34 installer: // com.android.systemui SDK 34 installer: // com.google.android.gms SDK 34 installer: com.android.vending ``` ## LocationManager: GPS Location with Callbacks Register a location listener to receive GPS fixes using the callback pattern. ```go package main import ( "context" "fmt" "math" "os" "time" "github.com/AndroidGoLab/binder/android/location" androidos "github.com/AndroidGoLab/binder/android/os" osTypes "github.com/AndroidGoLab/binder/android/os/types" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/servicemanager" ) // gpsListener implements the ILocationListenerServer interface type gpsListener struct { fixCh chan location.Location } func (l *gpsListener) OnLocationChanged( _ context.Context, locations []location.Location, _ androidos.IRemoteCallback, ) error { for _, loc := range locations { select { case l.fixCh <- loc: default: } } return nil } func (l *gpsListener) OnProviderEnabledChanged(_ context.Context, _ string, _ bool) error { return nil } func (l *gpsListener) OnFlushComplete(_ context.Context, _ int32) error { return nil } func main() { ctx := context.Background() drv, _ := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) defer drv.Close(ctx) transport, _ := versionaware.NewTransport(ctx, drv, 0) sm := servicemanager.New(transport) // Get LocationManager lm, err := location.GetLocationManager(ctx, sm) if err != nil { fmt.Fprintf(os.Stderr, "get location manager: %v\n", err) os.Exit(1) } // Create listener stub impl := &gpsListener{fixCh: make(chan location.Location, 1)} listener := location.NewLocationListenerStub(impl) // Configure location request request := location.LocationRequest{ Provider: location.GpsProvider, IntervalMillis: 1000, Quality: 102, // QUALITY_BALANCED_POWER_ACCURACY ExpireAtRealtimeMillis: math.MaxInt64, DurationMillis: math.MaxInt64, MaxUpdates: math.MaxInt32, MinUpdateIntervalMillis: -1, WorkSource: &osTypes.WorkSource{}, } packageName := binder.DefaultCallerIdentity().PackageName // Register listener err = lm.RegisterLocationListener(ctx, location.GpsProvider, request, listener, packageName, "gps-example") if err != nil { fmt.Fprintf(os.Stderr, "register listener: %v\n", err) os.Exit(1) } fmt.Println("Waiting for GPS fix (30s timeout)...") select { case loc := <-impl.fixCh: fmt.Printf("Lat: %.6f\n", loc.LatitudeDegrees) fmt.Printf("Lon: %.6f\n", loc.LongitudeDegrees) fmt.Printf("Alt: %.1f m\n", loc.AltitudeMeters) fmt.Printf("Speed: %.1f m/s\n", loc.SpeedMetersPerSecond) fmt.Printf("Accuracy: %.1f m\n", loc.HorizontalAccuracyMeters) case <-time.After(30 * time.Second): fmt.Fprintln(os.Stderr, "timed out waiting for GPS fix") } // Cleanup lm.UnregisterLocationListener(ctx, listener) } // Output: // Waiting for GPS fix (30s timeout)... // Lat: 37.421998 // Lon: -122.084000 // Alt: 10.0 m // Speed: 0.0 m/s // Accuracy: 20.0 m ``` ## DisplayManager: Screen Information Query display IDs, brightness levels, and color settings. ```go package main import ( "context" "fmt" "os" "github.com/AndroidGoLab/binder/android/hardware/display" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/servicemanager" ) func main() { ctx := context.Background() driver, _ := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) defer driver.Close(ctx) transport, _ := versionaware.NewTransport(ctx, driver, 0) sm := servicemanager.New(transport) // Get DisplayManager svc, _ := sm.GetService(ctx, servicemanager.DisplayService) dm := display.NewDisplayManagerProxy(svc) // Get display IDs ids, err := dm.GetDisplayIds(ctx, false) if err != nil { fmt.Fprintf(os.Stderr, "GetDisplayIds: %v\n", err) os.Exit(1) } fmt.Printf("Display IDs: %v\n", ids) for _, id := range ids { brightness, err := dm.GetBrightness(ctx, id) if err != nil { fmt.Fprintf(os.Stderr, " Display %d brightness: %v\n", id, err) } else { fmt.Printf(" Display %d brightness: %.2f\n", id, brightness) } } // Get ColorDisplayManager for night mode colorSvc, _ := sm.GetService(ctx, servicemanager.ColorDisplayService) cdm := display.NewColorDisplayManagerProxy(colorSvc) nightMode, _ := cdm.IsNightDisplayActivated(ctx) fmt.Printf("\nNight mode active: %v\n", nightMode) colorMode, _ := cdm.GetColorMode(ctx) fmt.Printf("Color mode: %d\n", colorMode) nightTemp, _ := cdm.GetNightDisplayColorTemperature(ctx) fmt.Printf("Night color temp: %d K\n", nightTemp) } // Output: // Display IDs: [0] // Display 0 brightness: 0.50 // Night mode active: false // Color mode: 0 // Night color temp: 2850 K ``` ## Camera Service: Connecting with Callbacks Connect to the camera service using callback stubs for device events. ```go package main import ( "context" "fmt" "os" "github.com/AndroidGoLab/binder/android/frameworks/cameraservice/device" "github.com/AndroidGoLab/binder/android/frameworks/cameraservice/service" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/servicemanager" ) // noopCallback implements ICameraDeviceCallbackServer type noopCallback struct{} func (c *noopCallback) OnCaptureStarted(_ context.Context, _ device.CaptureResultExtras, _ int64) error { return nil } func (c *noopCallback) OnDeviceError(_ context.Context, _ device.ErrorCode, _ device.CaptureResultExtras) error { return nil } func (c *noopCallback) OnDeviceIdle(_ context.Context) error { return nil } func (c *noopCallback) OnPrepared(_ context.Context, _ int32) error { return nil } func (c *noopCallback) OnRepeatingRequestError(_ context.Context, _ int64, _ int32) error { return nil } func (c *noopCallback) OnResultReceived(_ context.Context, _ device.CaptureMetadataInfo, _ device.CaptureResultExtras, _ []device.PhysicalCaptureResultInfo) error { return nil } func (c *noopCallback) OnClientSharedAccessPriorityChanged(_ context.Context, _ bool) error { return nil } func main() { ctx := context.Background() drv, _ := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) defer drv.Close(ctx) transport, _ := versionaware.NewTransport(ctx, drv, 0) sm := servicemanager.New(transport) // Get camera service cameraSvc, err := sm.GetService(ctx, "android.frameworks.cameraservice.service.ICameraService/default") if err != nil { fmt.Fprintf(os.Stderr, "get camera service: %v\n", err) os.Exit(1) } cameraProxy := service.NewCameraServiceProxy(cameraSvc) // Create callback stub callback := device.NewCameraDeviceCallbackStub(&noopCallback{}) fmt.Println("Created callback stub, calling ConnectDevice...") // Try to connect (requires camera permission) deviceUser, err := cameraProxy.ConnectDevice(ctx, callback, "0") if err != nil { fmt.Fprintf(os.Stderr, "ConnectDevice error (permission required): %v\n", err) return } fmt.Printf("ConnectDevice succeeded: %v\n", deviceUser) deviceUser.Disconnect(ctx) } // Output (without camera permission): // Created callback stub, calling ConnectDevice... // ConnectDevice error (permission required): security exception: ... ``` ## Raw Binder Transactions For services without generated proxies, use raw binder transactions with manual parcel marshaling. ```go package main import ( "context" "fmt" "os" "github.com/AndroidGoLab/binder/binder" "github.com/AndroidGoLab/binder/binder/versionaware" "github.com/AndroidGoLab/binder/kernelbinder" "github.com/AndroidGoLab/binder/parcel" "github.com/AndroidGoLab/binder/servicemanager" ) const descriptorBatteryProps = "android.os.IBatteryPropertiesRegistrar" const batteryPropertyCapacity = int32(4) // BATTERY_PROPERTY_CAPACITY (%) func main() { ctx := context.Background() driver, _ := kernelbinder.Open(ctx, binder.WithMapSize(128*1024)) defer driver.Close(ctx) transport, _ := versionaware.NewTransport(ctx, driver, 0) sm := servicemanager.New(transport) // Get battery properties service svc, err := sm.GetService(ctx, servicemanager.ServiceName("batteryproperties")) if err != nil { fmt.Fprintf(os.Stderr, "get service: %v\n", err) os.Exit(1) } // Build request parcel manually data := parcel.New() defer data.Recycle() data.WriteInterfaceToken(descriptorBatteryProps) data.WriteInt32(batteryPropertyCapacity) // Resolve transaction code dynamically code, err := svc.ResolveCode(ctx, descriptorBatteryProps, "getProperty") if err != nil { fmt.Fprintf(os.Stderr, "resolve code: %v\n", err) os.Exit(1) } // Execute transaction reply, err := svc.Transact(ctx, code, 0, data) if err != nil { fmt.Fprintf(os.Stderr, "transact: %v\n", err) os.Exit(1) } defer reply.Recycle() // Check AIDL status if err = binder.ReadStatus(reply); err != nil { fmt.Fprintf(os.Stderr, "status: %v\n", err) os.Exit(1) } // Read BatteryProperty out-parameter (nullable Parcelable) nullInd, _ := reply.ReadInt32() if nullInd != 0 { value, _ := reply.ReadInt64() reply.ReadString() // ValueString (usually empty) status, _ := reply.ReadInt32() if status == 0 { fmt.Printf("Battery level: %d%%\n", value) } } } // Output: // Battery level: 85% ``` ## bindercli: Command-Line Tool Build and deploy the CLI tool to interact with binder services from the command line. ```bash # Build for Android ARM64 GOOS=linux GOARCH=arm64 go build -o build/bindercli ./cmd/bindercli/ adb push build/bindercli /data/local/tmp/ # List all services adb shell /data/local/tmp/bindercli service list # Inspect a specific service adb shell /data/local/tmp/bindercli service inspect SurfaceFlinger # Check if screen is on adb shell /data/local/tmp/bindercli android.os.IPowerManager is-interactive # Output: {"result":true} # Check power save mode adb shell /data/local/tmp/bindercli android.os.IPowerManager is-power-save-mode # Output: {"result":false} # Get battery health info adb shell /data/local/tmp/bindercli android.hardware.health.IHealth get-health-info # Check if package is installed adb shell /data/local/tmp/bindercli android.content.pm.IPackageManager is-package-available \ --packageName com.android.settings --userId 0 # Output: {"result":true} # Get display IDs adb shell /data/local/tmp/bindercli android.hardware.display.IDisplayManager get-display-ids \ --includeDisabled false # Output: {"result":[0]} # Check ActivityManager permission adb shell /data/local/tmp/bindercli android.app.IActivityManager check-permission \ --permission android.permission.INTERNET --pid 1 --uid 0 ``` ## Remote Access via ADB Access binder services from a host machine using the gadb proxy. ```go package main import ( "context" "fmt" "github.com/AndroidGoLab/binder/interop/gadb/proxy" "github.com/AndroidGoLab/binder/servicemanager" ) func main() { ctx := context.Background() // Create session (auto-discovers device, pushes daemon) sess, err := proxy.NewSession(ctx, "") // empty = auto-detect device if err != nil { panic(err) } defer sess.Close(ctx) // Use ServiceManager as if running on-device sm := servicemanager.New(sess.Transport()) services, _ := sm.ListServices(ctx) fmt.Printf("Found %d services on device\n", len(services)) } ``` ## Service Name Constants The library provides predefined constants for common Android service names. ```go import "github.com/AndroidGoLab/binder/servicemanager" // Common service name constants servicemanager.ActivityService // "activity" servicemanager.PowerService // "power" servicemanager.DisplayService // "display" servicemanager.ColorDisplayService // "color_display" servicemanager.AudioService // "audio" servicemanager.CameraService // "camera" servicemanager.BluetoothService // "bluetooth" servicemanager.WifiService // "wifi" servicemanager.TelephonyService // "phone" servicemanager.LocationService // "location" servicemanager.ConnectivityService // "connectivity" servicemanager.SensorService // "sensorservice" servicemanager.VibratorService // "vibrator" servicemanager.BatteryService // "batterymanager" ``` ## Summary The Binder library is ideal for building pure Go applications that need direct access to Android system services without the overhead of Java, the Android NDK, or cgo dependencies. Common use cases include system monitoring tools (battery, power, thermal status), device management utilities (package queries, permission checks, display control), hardware interaction (camera, sensors, audio), and automated testing frameworks. The library works on Android devices running API level 34+ and on any Linux system with binder driver support (such as container environments with binder modules). Integration patterns follow a consistent structure: open the kernel driver, create a version-aware transport, instantiate the ServiceManager, retrieve specific services by name, and call type-safe proxy methods. The version-aware runtime automatically adapts transaction codes across different Android versions by extracting method codes from DEX bytecode at runtime. For services without generated proxies, raw transactions with manual parcel marshaling provide full flexibility. The included `bindercli` tool enables rapid prototyping and debugging directly from the command line.