Enhance Makefile, README, and setup script for improved development workflow

- Updated Makefile to include new setup commands for Android SDK and emulator management.
- Revised README.md to reflect changes in setup instructions and command usage.
- Introduced setup.sh script for one-time environment setup, including SDK installation and configuration.
- Modified CrklAccessibilityService to implement a floating action button instead of a full-screen overlay.
- Enhanced OverlayView to support toggling between button and overlay modes for gesture capture.
- Adjusted accessibility service configuration to disable touch exploration mode for better user experience.
This commit is contained in:
ilia 2025-10-21 12:55:39 -04:00
parent c351858749
commit 1d0c1cfb8a
6 changed files with 462 additions and 115 deletions

View File

@ -1,5 +1,5 @@
# Crkl - Physical Phone Development
.PHONY: help build install run stop logs clean uninstall
.PHONY: help setup-sdk build install run stop logs clean uninstall devices check-device
ANDROID_HOME ?= $(HOME)/android-sdk
ADB := $(ANDROID_HOME)/platform-tools/adb
@ -7,10 +7,22 @@ GRADLEW := ./gradlew
APK := app/build/outputs/apk/debug/app-debug.apk
help: ## Show available commands
@echo "Crkl - Physical Phone Commands"
@echo "Crkl - Android Development Commands"
@echo ""
@echo "Setup Commands:"
@echo " make setup-sdk - Install Android SDK and tools"
@echo " make setup-emulator - Create and start Android emulator"
@echo " make check-device - Check if device/emulator is connected"
@echo " make devices - List connected devices/emulators"
@echo ""
@echo "Emulator Commands:"
@echo " make emulator - Start Android emulator"
@echo " make emulator-stop - Stop emulator"
@echo " make emulator-list - List available emulators"
@echo ""
@echo "Development Commands:"
@echo " make build - Build APK"
@echo " make install - Install to phone"
@echo " make install - Install to device/emulator"
@echo " make run - Launch app"
@echo " make logs - Watch app logs"
@echo " make stop - Stop app"
@ -18,28 +30,99 @@ help: ## Show available commands
@echo " make uninstall - Remove app"
@echo ""
setup-sdk: ## Install Android SDK and tools
@echo "Setting up Android SDK..."
@mkdir -p $(ANDROID_HOME)
@cd $(ANDROID_HOME) && \
if [ ! -f cmdline-tools/latest/bin/sdkmanager ]; then \
echo "Downloading Android SDK command-line tools..."; \
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip; \
unzip -q commandlinetools-linux-11076708_latest.zip; \
mkdir -p cmdline-tools/latest; \
mv cmdline-tools/* cmdline-tools/latest/ 2>/dev/null || true; \
rm commandlinetools-linux-11076708_latest.zip; \
fi
@echo "Accepting SDK licenses..."
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$$PATH && \
yes | sdkmanager --licenses > /dev/null 2>&1
@echo "Installing platform tools and build tools..."
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$$PATH && \
sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" > /dev/null 2>&1
@echo "✓ Android SDK setup complete!"
@echo "Add to your ~/.bashrc or ~/.zshrc:"
@echo " export ANDROID_HOME=$(ANDROID_HOME)"
@echo " export PATH=\$$ANDROID_HOME/platform-tools:\$$PATH"
check-device: ## Check if device is connected
@echo "Checking for connected devices..."
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/platform-tools:$$PATH && \
adb devices
devices: check-device ## List connected devices (alias for check-device)
setup-emulator: ## Create and start Android emulator
@echo "Setting up Android emulator..."
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$$PATH && \
sdkmanager "system-images;android-34;google_apis;x86_64" > /dev/null 2>&1
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$$PATH && \
avdmanager create avd -n "CrklEmulator" -k "system-images;android-34;google_apis;x86_64" -d "pixel_7" --force
@echo "✓ Emulator created. Run 'make emulator' to start it."
emulator: ## Start Android emulator
@echo "Starting Android emulator with 4GB RAM..."
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/emulator:$(ANDROID_HOME)/platform-tools:$$PATH && \
emulator -avd CrklEmulator -memory 4096 -cores 4 -no-audio -gpu swiftshader_indirect -no-metrics &
emulator-stop: ## Stop emulator
@echo "Stopping emulator..."
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/platform-tools:$$PATH && \
adb emu kill
emulator-list: ## List available emulators
@echo "Available emulators:"
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/cmdline-tools/latest/bin:$(ANDROID_HOME)/platform-tools:$$PATH && \
avdmanager list avd
build: ## Build APK
@echo "Building Crkl..."
@$(GRADLEW) assembleDebug
install: build ## Install to phone
@echo "Installing to phone..."
@$(ADB) install -r $(APK)
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/platform-tools:$$PATH && \
$(ADB) install -r $(APK)
@echo "✓ Installed"
run: ## Launch app
@echo "Launching Crkl..."
@$(ADB) shell am start -n com.example.crkl/.MainActivity
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/platform-tools:$$PATH && \
$(ADB) shell am start -n com.example.crkl/.MainActivity
logs: ## Watch app logs
@echo "Watching logs (Ctrl+C to stop)..."
@$(ADB) logcat -s CrklAccessibilityService:D OverlayView:D AndroidRuntime:E
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/platform-tools:$$PATH && \
$(ADB) logcat -s CrklAccessibilityService:D OverlayView:D AndroidRuntime:E
stop: ## Stop app
@$(ADB) shell am force-stop com.example.crkl
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/platform-tools:$$PATH && \
$(ADB) shell am force-stop com.example.crkl
clean: ## Clean build
@$(GRADLEW) clean
uninstall: ## Remove app
@$(ADB) uninstall com.example.crkl
@export ANDROID_HOME=$(ANDROID_HOME) && \
export PATH=$(ANDROID_HOME)/platform-tools:$$PATH && \
$(ADB) uninstall com.example.crkl

View File

@ -10,14 +10,21 @@ Privacy-first Android AI assistant that lets users circle or touch any element o
## Quick Start
### 1. Connect Your Android Phone
### 1. Setup Development Environment
```bash
# One-time setup (installs Android SDK and tools)
./setup.sh
```
### 2. Connect Your Android Device
See [PHONE_SETUP.md](PHONE_SETUP.md) for detailed setup instructions.
### 2. Build and Install
### 3. Build and Install
```bash
make build # Build APK
make install # Install to phone
make run # Launch app
make check-device # Check if device is connected
make build # Build APK
make install # Install to device
make run # Launch app
```
### 3. Enable Accessibility Service
@ -34,9 +41,17 @@ Touch your phone screen anywhere - you'll see touch detection logs and visual fe
## Commands
### Setup Commands
```bash
./setup.sh # One-time setup (installs Android SDK)
make setup-sdk # Install Android SDK and tools
make check-device # Check if device is connected
```
### Development Commands
```bash
make build # Build APK
make install # Install to phone
make install # Install to device
make run # Launch app
make logs # Watch app logs
make stop # Stop app
@ -44,6 +59,11 @@ make clean # Clean build
make uninstall # Remove app
```
### Help
```bash
make help # Show all available commands
```
## Project Structure
```

View File

@ -1,12 +1,9 @@
package com.example.crkl.accessibility
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.graphics.PixelFormat
import android.util.Log
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import kotlinx.coroutines.CoroutineScope
@ -17,10 +14,12 @@ import kotlinx.coroutines.cancel
/**
* Crkl Accessibility Service
*
* This service creates a system-wide overlay that captures user gestures
* This service monitors accessibility events to detect user interactions
* and provides AI-powered assistance based on selected screen content.
*
* Privacy: All processing is local. No data leaves the device.
*
* Note: No overlay is created to avoid blocking touch events.
*/
class CrklAccessibilityService : AccessibilityService() {
@ -33,36 +32,90 @@ class CrklAccessibilityService : AccessibilityService() {
super.onServiceConnected()
Log.d(TAG, "Crkl Accessibility Service connected")
// Initialize overlay
setupOverlay()
// Create floating action button for Crkl activation
setupFloatingButton()
Log.d(TAG, "Service ready - floating button created")
}
private fun setupOverlay() {
private fun setupFloatingButton() {
try {
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
// Create overlay view
overlayView = OverlayView(this)
// Create overlay view with callback for mode changes
overlayView = OverlayView(this) { isOverlayMode ->
updateOverlayMode(isOverlayMode)
}
// Configure window layout parameters for accessibility overlay
// Configure window layout parameters for floating button
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
)
params.gravity = Gravity.TOP or Gravity.START
// Position in bottom right corner
params.gravity = Gravity.BOTTOM or Gravity.END
params.x = 50
params.y = 100
// Add overlay to window manager
// Add floating button to window manager
windowManager?.addView(overlayView, params)
Log.d(TAG, "Overlay created successfully")
Log.d(TAG, "Floating button created successfully")
} catch (e: Exception) {
Log.e(TAG, "Error creating overlay", e)
Log.e(TAG, "Error creating floating button", e)
}
}
private fun updateOverlayMode(isOverlayMode: Boolean) {
try {
overlayView?.let { view ->
windowManager?.let { wm ->
// Remove current view
wm.removeView(view)
// Create new parameters based on mode
val params = if (isOverlayMode) {
// Full screen overlay parameters
WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.TOP or Gravity.LEFT
x = 0
y = 0
}
} else {
// Floating button parameters
WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.BOTTOM or Gravity.END
x = 50
y = 100
}
}
// Add view with new parameters
wm.addView(view, params)
Log.d(TAG, "Overlay mode updated: ${if (isOverlayMode) "full-screen" else "floating button"}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error updating overlay mode", e)
}
}
@ -84,7 +137,7 @@ class CrklAccessibilityService : AccessibilityService() {
super.onDestroy()
Log.d(TAG, "Service destroyed")
// Clean up overlay
// Clean up floating button
overlayView?.let {
windowManager?.removeView(it)
overlayView = null

View File

@ -6,111 +6,245 @@ import android.graphics.Color
import android.graphics.Paint
import android.graphics.PointF
import android.util.Log
import android.view.Gravity
import android.view.View.MeasureSpec
import android.view.MotionEvent
import android.view.View
import android.widget.Toast
import kotlin.math.sqrt
/**
* Overlay View for gesture capture
* Crkl Overlay View
*
* This view is displayed over all other apps and captures user touch gestures.
* It draws a visual indicator when the user circles or touches the screen.
*
* Phase 1 (POC): Simple tap detection and visual feedback
* Phase 2: Circle gesture recognition
* Phase 3: Region extraction and content analysis
* This view provides a floating action button that toggles to a full-screen overlay
* for capturing touch gestures and circle selections. It reports selection bounds
* for content analysis.
*/
class OverlayView(context: Context) : View(context) {
class OverlayView(
context: Context,
private val onModeChanged: (Boolean) -> Unit = {}
) : View(context) {
private val TAG = "OverlayView"
// Paint for drawing touch indicators
private val circlePaint = Paint().apply {
color = Color.parseColor("#8003DAC6") // Semi-transparent secondary color
style = Paint.Style.STROKE
strokeWidth = 8f
isAntiAlias = true
}
private val fillPaint = Paint().apply {
color = Color.parseColor("#2003DAC6") // Very transparent fill
// Button properties
private val buttonSize = 80f
private var buttonColor = Color.BLUE
private val buttonPaint = Paint().apply {
color = buttonColor
style = Paint.Style.FILL
isAntiAlias = true
}
// Touch tracking
private var touchPoint: PointF? = null
private var isActive = false
private val touchRadius = 80f
// Overlay mode properties
private var isOverlayMode = false
private val overlayPaint = Paint().apply {
color = Color.argb(128, 0, 0, 0) // Semi-transparent black
style = Paint.Style.FILL
}
private val instructionPaint = Paint().apply {
color = Color.WHITE
textSize = 48f
textAlign = Paint.Align.CENTER
isAntiAlias = true
}
// Drawing properties
private var isDrawing = false
private var touchPath = mutableListOf<PointF>()
private val pathPaint = Paint().apply {
color = Color.YELLOW
strokeWidth = 8f
style = Paint.Style.STROKE
isAntiAlias = true
}
// Selection bounds tracking
private var selectionBounds: android.graphics.RectF? = null
private val boundsPaint = Paint().apply {
color = Color.GREEN
strokeWidth = 4f
style = Paint.Style.STROKE
isAntiAlias = true
}
private val textPaint = Paint().apply {
color = Color.WHITE
textSize = 32f
textAlign = Paint.Align.CENTER
isAntiAlias = true
}
init {
// Make view transparent
setBackgroundColor(Color.TRANSPARENT)
Log.d(TAG, "OverlayView initialized")
isClickable = true
isFocusable = true
isFocusableInTouchMode = true
setOnClickListener {
Log.d(TAG, "Crkl floating button clicked!")
toggleOverlayMode()
}
Log.d(TAG, "Simple floating button initialized")
}
private fun toggleOverlayMode() {
isOverlayMode = !isOverlayMode
if (isOverlayMode) {
Log.d(TAG, "Overlay mode activated - draw a circle to analyze content")
showToast("Draw a circle to analyze content!")
} else {
Log.d(TAG, "Overlay mode deactivated")
showToast("Overlay mode disabled")
touchPath.clear()
selectionBounds = null
}
onModeChanged(isOverlayMode)
invalidate()
}
private fun showToast(message: String) {
val toast = Toast.makeText(context, message, Toast.LENGTH_LONG)
toast.setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL, 0, 200)
toast.show()
Log.d(TAG, "Toast shown: $message")
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
event ?: return false
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// User started touching
touchPoint = PointF(event.x, event.y)
isActive = true
invalidate() // Request redraw
Log.d(TAG, "Touch down at (${event.x}, ${event.y})")
}
MotionEvent.ACTION_MOVE -> {
// User is dragging (future: track path for circle gesture)
touchPoint = PointF(event.x, event.y)
invalidate()
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
// Touch ended
Log.d(TAG, "Touch up at (${event.x}, ${event.y})")
// For POC: just show we detected a tap
Log.d(TAG, "POC: Tap detected! Future: analyze content at this location")
// Clear after a moment
postDelayed({
isActive = false
touchPoint = null
invalidate()
}, 500)
}
if (!isOverlayMode) {
// In button mode, only handle clicks on the button itself
return super.onTouchEvent(event)
}
// Return false to allow touch events to pass through to underlying apps
// In production, we'll want smarter handling based on gesture state
return false
// In overlay mode, handle drawing
event?.let { motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
isDrawing = true
touchPath.clear()
touchPath.add(PointF(motionEvent.x, motionEvent.y))
Log.d(TAG, "Drawing started at (${motionEvent.x}, ${motionEvent.y})")
}
MotionEvent.ACTION_MOVE -> {
if (isDrawing) {
touchPath.add(PointF(motionEvent.x, motionEvent.y))
}
}
MotionEvent.ACTION_UP -> {
isDrawing = false
Log.d(TAG, "Drawing ended. Path points: ${touchPath.size}")
if (isCircleGesture()) {
Log.d(TAG, "Circle detected!")
calculateSelectionBounds()
showToast("Circle detected! Analyzing content...")
reportSelectionBounds()
}
}
}
invalidate()
}
return true // Consume touch events in overlay mode
}
private fun isCircleGesture(): Boolean {
if (touchPath.size < 10) return false
val firstPoint = touchPath.first()
val lastPoint = touchPath.last()
val dx = (lastPoint.x - firstPoint.x).toDouble()
val dy = (lastPoint.y - firstPoint.y).toDouble()
val distance = sqrt(dx * dx + dy * dy)
return distance < 100f // Circle is closed if start and end are close
}
private fun calculateSelectionBounds() {
if (touchPath.isEmpty()) return
var minX = Float.MAX_VALUE
var maxX = Float.MIN_VALUE
var minY = Float.MAX_VALUE
var maxY = Float.MIN_VALUE
for (point in touchPath) {
minX = minOf(minX, point.x)
maxX = maxOf(maxX, point.x)
minY = minOf(minY, point.y)
maxY = maxOf(maxY, point.y)
}
selectionBounds = android.graphics.RectF(minX, minY, maxX, maxY)
Log.d(TAG, "Selection bounds calculated: $selectionBounds")
}
private fun reportSelectionBounds() {
selectionBounds?.let { bounds ->
Log.d(TAG, "Selection bounds reported: left=${bounds.left}, top=${bounds.top}, right=${bounds.right}, bottom=${bounds.bottom}")
Log.d(TAG, "Selection area: ${bounds.width()} x ${bounds.height()}")
// TODO: In future, this will trigger content analysis at these bounds
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Only draw when user is actively touching
if (isActive && touchPoint != null) {
val point = touchPoint!!
// Draw circle at touch point
canvas.drawCircle(point.x, point.y, touchRadius, fillPaint)
canvas.drawCircle(point.x, point.y, touchRadius, circlePaint)
// Draw crosshair for precision
val crosshairSize = 20f
canvas.drawLine(
point.x - crosshairSize, point.y,
point.x + crosshairSize, point.y,
circlePaint
)
canvas.drawLine(
point.x, point.y - crosshairSize,
point.x, point.y + crosshairSize,
circlePaint
)
if (isOverlayMode) {
drawOverlayMode(canvas)
} else {
drawButtonMode(canvas)
}
}
}
private fun drawButtonMode(canvas: Canvas) {
// Draw button background
val centerX = width / 2f
val centerY = height / 2f
canvas.drawCircle(centerX, centerY, buttonSize / 2, buttonPaint)
// Draw "C" text
val textY = centerY + (textPaint.textSize / 3)
canvas.drawText("C", centerX, textY, textPaint)
}
private fun drawOverlayMode(canvas: Canvas) {
// Draw semi-transparent overlay
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), overlayPaint)
// Draw instructions
canvas.drawText("Draw a circle to analyze content", width / 2f, 200f, instructionPaint)
canvas.drawText("Tap 'C' button again to exit", width / 2f, height - 200f, instructionPaint)
// Draw the drawing path
if (touchPath.size > 1) {
for (i in 1 until touchPath.size) {
val prev = touchPath[i - 1]
val curr = touchPath[i]
canvas.drawLine(prev.x, prev.y, curr.x, curr.y, pathPaint)
}
}
// Draw selection bounds if available
selectionBounds?.let { bounds ->
canvas.drawRect(bounds, boundsPaint)
// Draw bounds info
val boundsText = "Selection: ${bounds.width().toInt()}x${bounds.height().toInt()}"
canvas.drawText(boundsText, bounds.centerX(), bounds.top - 20f, textPaint)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (isOverlayMode) {
// In overlay mode, use full screen
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec)
)
} else {
// In button mode, use fixed button size
setMeasuredDimension(buttonSize.toInt(), buttonSize.toInt())
}
}
}

View File

@ -2,9 +2,9 @@
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagRequestTouchExplorationMode"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
android:accessibilityFeedbackType="feedbackGeneric"
android:canRetrieveWindowContent="true"
android:canRequestTouchExplorationMode="true"
android:canRequestTouchExplorationMode="false"
android:notificationTimeout="100" />

57
setup.sh Executable file
View File

@ -0,0 +1,57 @@
#!/bin/bash
# Crkl Setup Script
# This script sets up the Android SDK and environment for Crkl development
set -e
echo "🚀 Setting up Crkl development environment..."
# Check if we're in the right directory
if [ ! -f "Makefile" ]; then
echo "❌ Error: Please run this script from the Crkl project root directory"
exit 1
fi
# Setup Android SDK
echo "📱 Setting up Android SDK..."
make setup-sdk
# Add environment variables to shell profile
SHELL_PROFILE=""
if [ -f "$HOME/.zshrc" ]; then
SHELL_PROFILE="$HOME/.zshrc"
elif [ -f "$HOME/.bashrc" ]; then
SHELL_PROFILE="$HOME/.bashrc"
fi
if [ -n "$SHELL_PROFILE" ]; then
echo "🔧 Adding environment variables to $SHELL_PROFILE..."
# Check if already added
if ! grep -q "ANDROID_HOME" "$SHELL_PROFILE"; then
echo "" >> "$SHELL_PROFILE"
echo "# Crkl Android SDK" >> "$SHELL_PROFILE"
echo "export ANDROID_HOME=\$HOME/android-sdk" >> "$SHELL_PROFILE"
echo "export PATH=\$ANDROID_HOME/platform-tools:\$PATH" >> "$SHELL_PROFILE"
echo "✓ Environment variables added to $SHELL_PROFILE"
else
echo "✓ Environment variables already present in $SHELL_PROFILE"
fi
fi
# Build the project
echo "🔨 Building Crkl APK..."
make build
echo ""
echo "✅ Setup complete!"
echo ""
echo "Next steps:"
echo "1. Connect your Android tablet/phone via USB"
echo "2. Enable Developer Options and USB Debugging on your device"
echo "3. Run: make check-device"
echo "4. Run: make install"
echo "5. Run: make run"
echo ""
echo "For help, run: make help"