Native/Platform APIs: Camera, Bluetooth, NFC, Sensors & Maps
Discussing hardware APIs confidently in senior Android interviews
Open interactive version (quiz + challenge)Real-world analogy
What is it?
Android's platform APIs provide programmatic access to device hardware: CameraX for photo and video, Bluetooth for device pairing and data transfer, NFC for contactless communication, the sensor framework for motion and environment data, and Google Maps SDK for mapping and location. Senior developers must know the correct modern API for each, common permission pitfalls, and how to discuss each one intelligently in an architecture conversation.
Real-world relevance
A field inspection app uses CameraX to capture defect photos, BLE to read data from IoT sensors on equipment, NFC to identify equipment by tapping a tag, the accelerometer to detect phone orientation for AR overlays, and Maps to show where the equipment is located. All with proper runtime permission handling and graceful degradation when hardware is absent.
Key points
- CameraX — the modern camera API — CameraX is the Jetpack library that abstracts Camera2's complexity. It exposes three use cases: Preview (show viewfinder), ImageCapture (take photos), and ImageAnalysis (process frames in real time, e.g., ML Kit barcode scanning). Lifecycle-aware — bind use cases to a LifecycleOwner and CameraX handles open/close automatically. Always prefer CameraX over Camera2 unless you need fine-grained control.
- Camera permissions and result contracts — REQUEST_CODE style camera is deprecated. Use ActivityResultContracts.TakePicture() for capturing to a URI, or TakePicturePreview() for a thumbnail Bitmap. Combine with FileProvider for secure storage. Always request Manifest.permission.CAMERA at runtime before launching.
- Bluetooth Classic vs BLE — Bluetooth Classic (BR/EDR) is for high-bandwidth continuous connections: audio streaming, file transfer, HID devices (keyboards, mice). BLE (Bluetooth Low Energy) is for low-power, intermittent data: IoT sensors, fitness trackers, beacons. They use different APIs: BluetoothSocket for Classic, BluetoothGatt for BLE. Know which your use case needs before writing a line of code.
- BLE workflow — BLE flow: 1) Get BluetoothAdapter. 2) Scan for devices (BluetoothLeScanner.startScan with ScanFilter). 3) Connect via device.connectGatt(). 4) Discover services in onServicesDiscovered callback. 5) Read/write Characteristics. 6) Enable Notifications for real-time updates. Each step is asynchronous and callback-driven. In 2026, wrap in coroutines with callbackFlow for a clean API.
- Bluetooth permissions (API 31+ changes) — Android 12 (API 31) split Bluetooth permissions: BLUETOOTH_SCAN (scanning), BLUETOOTH_CONNECT (connecting to paired devices), BLUETOOTH_ADVERTISE (advertising as peripheral). The old BLUETOOTH and BLUETOOTH_ADMIN permissions are no longer sufficient. Missing these causes silent scan failures — a common production bug on Android 12+ devices.
- NFC — reading and writing NDEF tags — NFC (Near Field Communication) uses the NDEF (NFC Data Exchange Format) for structured data on tags. enableForegroundDispatch() or enableReaderMode() puts your Activity in the NFC foreground, intercepting tag scans before other apps. Read NdefMessage from the discovered tag, parse NdefRecords. Write by formatting a tag with NdefFormatable. Used for smart posters, transit cards, and device pairing.
- Sensor framework — SensorManager gives access to all hardware sensors: accelerometer (TYPE_ACCELEROMETER), gyroscope (TYPE_GYROSCOPE), magnetometer (TYPE_MAGNETIC_FIELD), proximity (TYPE_PROXIMITY), light (TYPE_LIGHT), step counter (TYPE_STEP_COUNTER). Register a SensorEventListener with the desired sampling rate (SENSOR_DELAY_NORMAL, SENSOR_DELAY_GAME, SENSOR_DELAY_FASTEST). Always unregister in onPause() to save battery.
- Google Maps SDK integration — Add the Maps SDK dependency, get an API key from Google Cloud Console, add it to Manifest (com.google.android.geo.API_KEY). Use SupportMapFragment or MapView. Call getMapAsync() to get a GoogleMap instance. Add markers with MarkerOptions, draw routes with Polylines, customize map style with JSON style files. For location, use FusedLocationProviderClient — never the old LocationManager API.
- FusedLocationProviderClient — the right location API — FusedLocationProviderClient (from Google Play Services) automatically selects the best location source (GPS, Wi-Fi, cell) and batches updates efficiently. Use requestLocationUpdates() for continuous tracking, getCurrentLocation() for one-shot. Request ACCESS_FINE_LOCATION for GPS-accurate, ACCESS_COARSE_LOCATION for approximate. Background location (ACCESS_BACKGROUND_LOCATION) requires extra permission and policy justification since Android 10.
- Permission handling for hardware APIs — Pattern for any hardware API: 1) Declare permission in Manifest. 2) Check ContextCompat.checkSelfPermission() at runtime. 3) If denied, request via ActivityResultContracts.RequestPermission(). 4) If permanently denied, show Settings intent. 5) Handle the feature gracefully when permission is absent. Never assume permission is granted even if declared in Manifest.
- Interview breadth strategy — For hardware APIs in interviews, demonstrate: you know what the API does, you know the key permission requirements, you know the modern Jetpack/Play Services approach vs the legacy approach, and you can name a real scenario where you have used or would use it. Depth is expected only for Camera and Location — breadth suffices for BLE, NFC, and sensors.
- Feature requirements in Manifest — Use for optional hardware. This prevents Play Store from filtering out your app on devices without the hardware while still letting you check feature availability at runtime with PackageManager.hasSystemFeature().
Code example
// CameraX — capture a photo
class CameraActivity : AppCompatActivity() {
private lateinit var imageCapture: ImageCapture
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
}
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageCapture)
}, ContextCompat.getMainExecutor(this))
}
fun takePhoto() {
val photoFile = File(cacheDir, "photo_${System.currentTimeMillis()}.jpg")
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
// photo saved to photoFile
}
override fun onError(exc: ImageCaptureException) {
Log.e("Camera", "Capture failed: ${exc.message}", exc)
}
}
)
}
}
// BLE scan and connect (simplified)
class BleManager(private val context: Context) {
private val bluetoothAdapter: BluetoothAdapter? =
(context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
fun scanForDevices(onDeviceFound: (BluetoothDevice) -> Unit) {
val scanner = bluetoothAdapter?.bluetoothLeScanner ?: return
val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid(SERVICE_UUID))
.build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
scanner.startScan(listOf(filter), settings, object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
onDeviceFound(result.device)
}
})
}
fun connect(device: BluetoothDevice, onDataReceived: (ByteArray) -> Unit): BluetoothGatt {
return device.connectGatt(context, false, object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) gatt.discoverServices()
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
val char = gatt.getService(SERVICE_UUID)?.getCharacteristic(CHAR_UUID)
gatt.setCharacteristicNotification(char, true)
}
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
onDataReceived(characteristic.value)
}
})
}
companion object {
val SERVICE_UUID: UUID = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb")
val CHAR_UUID: UUID = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")
}
}
// Sensor — step counter
class StepCounterManager(context: Context) : SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
var stepCount: Long = 0
fun register() {
sensorManager.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_NORMAL)
}
fun unregister() { sensorManager.unregisterListener(this) }
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_STEP_COUNTER) {
stepCount = event.values[0].toLong()
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
// FusedLocationProviderClient
class LocationManager(private val context: Context) {
private val fusedClient = LocationServices.getFusedLocationProviderClient(context)
fun getCurrentLocation(onResult: (Location?) -> Unit) {
fusedClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
.addOnSuccessListener { location -> onResult(location) }
}
}Line-by-line walkthrough
- 1. ProcessCameraProvider.getInstance() returns a ListenableFuture — the addListener callback runs on the main executor once the camera is ready.
- 2. bindToLifecycle() ties the camera session to the Activity lifecycle — CameraX automatically opens/closes the camera on resume/pause, preventing resource leaks.
- 3. ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY prioritizes speed over quality — use CAPTURE_MODE_MAXIMIZE_QUALITY for still photos where wait time is acceptable.
- 4. ImageCapture.takePicture() saves directly to a file via OutputFileOptions — more efficient than capturing to a Bitmap in memory for full-resolution photos.
- 5. BluetoothLeScanner.startScan with a ScanFilter targeting a specific ServiceUUID reduces battery usage — scanning without filters drains battery rapidly.
- 6. onServicesDiscovered fires after connectGatt triggers service discovery — this is the correct place to get Characteristic references, not in onConnectionStateChange.
- 7. setCharacteristicNotification enables client-side notification; you also typically need to write to the CCCD descriptor on the characteristic — omitting this is a common BLE bug.
- 8. SensorManager.registerListener in onResume and unregisterListener in onPause is the correct lifecycle pattern — sensors drain battery if left registered in background.
- 9. Sensor.TYPE_STEP_COUNTER returns total steps since reboot as a monotonically increasing float — you must save a baseline at session start and subtract to get session steps.
- 10. FusedLocationProviderClient.getCurrentLocation with PRIORITY_HIGH_ACCURACY uses GPS — for battery-sensitive apps, use PRIORITY_BALANCED_POWER_ACCURACY instead.
Spot the bug
class SensorActivity : AppCompatActivity(), SensorEventListener {
private lateinit var sensorManager: SensorManager
private var accelerometer: Sensor? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_FASTEST)
}
override fun onSensorChanged(event: SensorEvent) {
val x = event.values[0]
val y = event.values[1]
val z = event.values[2]
updateUI(x, y, z)
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- CameraX overview — Android Developers (Android Docs)
- Bluetooth Low Energy — Android Developers (Android Docs)
- NFC basics — Android Developers (Android Docs)
- Sensors overview — Android Developers (Android Docs)
- FusedLocationProviderClient — Google Play Services (Google Developers)