Integrate into your Android app
Register an SDK Key
Get your free SDK key on https://dev.quickpose.ai, usage limits may apply. SDK Keys are linked to your bundle ID, please check Key before distributing to the App Store.
Installing the SDK
Maven
Step 1: Open your app's build.gradle:
Step 2: Add the Maven package and it's dependencies:
dependencies {
// recommended for starting quickpose
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'")
// CameraX core library
val camerax_version = ("1.4.1")
implementation("androidx.camera:camera-core:$camerax_version")
implementation("androidx.camera:camera-camera2:$camerax_version")
implementation("androidx.camera:camera-lifecycle:$camerax_version")
implementation("androidx.camera:camera-view:$camerax_version")
implementation("com.google.flogger:flogger:latest.release")
implementation("com.google.flogger:flogger-system-backend:latest.release")
implementation("com.google.guava:guava:27.0.1-android")
implementation("com.google.protobuf:protobuf-javalite:3.19.1")
implementation("com.microsoft.onnxruntime:onnxruntime-android:latest.release")
implementation("ai.quickpose:quickpose-mp:0.1")
implementation("ai.quickpose:quickpose-core:0.5")
}
Add Camera Permissions Check To Main App
Next, you have to explicitly request access to the camera, which will provide the Android standard camera permission prompt. This is only a demo implementation, as you'd typically want to give the user an idea of what your app does first and why camera permissions help them.
class MainActivity : ComponentActivity() {
private var hasPermissions = mutableStateOf(false)
override fun onCreate(savedInstanceState: Bundle?) {
hasPermissions.value = PermissionsHelper.checkAndRequestCameraPermissions(this)
}
override fun onResume() {
super.onResume()
hasPermissions.value = PermissionsHelper.checkAndRequestCameraPermissions(this)
if (!hasPermissions.value) {
return
}
}
We use this standard permission helper:
public class PermissionsHelper {
companion object {
fun checkAndRequestCameraPermissions(context: Activity): Boolean {
val hasPermission = cameraPermissionsGranted(context)
if (!hasPermission) {
ActivityCompat.requestPermissions(context, arrayOf(Manifest.permission.CAMERA), 0);
}
return hasPermission
}
fun cameraPermissionsGranted(context: Activity): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
}
}
}
Initialize the SDK
private var quickPose: QuickPose =
QuickPose(this, sdkKey = "YOUR SDK KEY HERE"
) // register for your free key at https://dev.quickpose.ai
Attach SDK to Views
This is our standard boilerplate implentation providing:
- Camera Permission Checking
- A fullscreen camera display.
- A canvas overlay showing the AI user's landmarks.
- Sensible memory releasing when the view is no longer visible.
- XML
- Compose
Declare an xml definition the quickpose camera and overlay stack.
<FrameLayout
android:id="@+id/quickpose_camera_and_overlay_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
</FrameLayout>
Declare a reference to the camera and overlay view group.
private var cameraAndOverlay: ViewGroup? = null
private var cameraSwitchView: QuickPoseCameraSwitchView? = null
Connect up the views
override fun onCreate(savedInstanceState: Bundle?) {
cameraAndOverlay = findViewById(R.id.quickpose_camera_and_overlay_view)
cameraSwitchView = QuickPoseCameraSwitchView(this, quickPose)
cameraAndOverlay?.addView(cameraSwitchView)
And on resume, start the camera and overlay stack and quickpose. This is a bit simplified as you need to request camera permissions.
override fun onResume() {
super.onResume()
lifecycleScope.launch {
cameraSwitchView?.start(useFrontCamera)!!
quickPose.start(
arrayOf(Feature.RangeOfMotion(RangeOfMotion.Shoulder(Side.LEFT, false))),
onFrame = { status, overlay, features, feedback, landmarks ->
landmarks?.let { println(it.allLandmarksForBody()[0].x) }
}
)
}
}
For Compose apps, apps we assume you're using a ComponentActivity
.
We provide a supported wrapper around the legacy camera view
@Composable
fun QuickPoseCamera(
quickPose: QuickPose,
useFrontCamera: Boolean = true,
) {
val coroutineScope = rememberCoroutineScope()
AndroidView(
factory = { context ->
val cameraSwitchView = QuickPoseCameraSwitchView(context, quickPose)
coroutineScope.launch { cameraSwitchView.start(useFrontCamera) }
cameraSwitchView
}
)
}
Which you can use in your Compose UI like this:
setContent {
BasicDemoTheme {
// assuming you have camera permissions
QuickPoseCamera(quickPose = quickPose)
}
}
class MainActivity : ComponentActivity() {
private var statusText = mutableStateOf("Powered by QuickPose.ai")
private var quickPose: QuickPose =
QuickPose(
this,
sdkKey = "YOUR SDK KEY HERE"
) // register for your free key at https://dev.quickpose.ai
private var hasPermissions = mutableStateOf(false)
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
WindowInsetsCompat.Type.navigationBars()
hasPermissions.value = PermissionsHelper.checkAndRequestCameraPermissions(this)
setContent {
BasicDemoTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { padding ->
Box(modifier = Modifier.fillMaxSize().padding(padding)) {
if (hasPermissions.value) {
QuickPoseCamera(quickPose = quickPose)
} else {
Text(
text = "Camera permissions are required",
color = Color.White,
fontSize = 16.sp,
modifier = Modifier.align(Alignment.Center),
textAlign = TextAlign.Center
)
}
Box(
modifier = Modifier.fillMaxSize().padding(bottom = 64.dp),
contentAlignment = Alignment.BottomCenter
) {
Text(
text = statusText.value,
color = Color.White,
fontSize = 16.sp,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
}
}
}
}
}
}
override fun onResume() {
super.onResume()
hasPermissions.value = PermissionsHelper.checkAndRequestCameraPermissions(this)
if (!hasPermissions.value) {
return
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.setDecorFitsSystemWindows(false)
val windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
windowInsetsController.hide(android.view.WindowInsets.Type.navigationBars())
lifecycleScope.launch {
quickPose.start(
arrayOf(Feature.Overlay(Landmarks.Group.WholeBody())),
onFrame = { status, overlay, features, feedback, landmarks ->
if (status is Status.Success) {
runOnUiThread {
statusText.value =
"Powered by QuickPose.ai v${quickPose.quickPoseVersion()}\n${status.fps} fps"
}
}
}
)
}
}
override fun onPause() {
super.onPause()
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
quickPose.stop()
}
}
@Composable
fun QuickPoseCamera(
quickPose: QuickPose,
useFrontCamera: Boolean = true,
) {
val coroutineScope = rememberCoroutineScope()
AndroidView(
factory = { context ->
val cameraSwitchView = QuickPoseCameraSwitchView(context, quickPose)
coroutineScope.launch { cameraSwitchView.start(useFrontCamera) }
cameraSwitchView
}
)
}
View the raw landmarks
landmarks?.let { println(it.allLandmarksForBody()[0]) }
Access the overlay image.
For performance reasons, the overlay is returned as a canvas. This differs from iOS, use PixelCopy to efficiently convert the canvas to a bitmap.
quickPose.start(
arrayOf(Feature.Overlay(Landmarks.Group.WholeBody())),
onFrame = { status, overlay, features, feedback, landmarks ->
overlay?.let { outputOverlay ->
val bitmap =
Bitmap.createBitmap(
outputOverlay.width,
outputOverlay.height,
Bitmap.Config.ARGB_8888
)
PixelCopy.request(
outputOverlay,
bitmap,
{ copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
println(
"Saved Bitmap ${bitmap.height}, ${bitmap.width}"
)
}
},
Handler(Looper.getMainLooper())
)
}
}
)