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:
parent
c351858749
commit
1d0c1cfb8a
99
Makefile
99
Makefile
@ -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
|
||||
|
||||
32
README.md
32
README.md
@ -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
|
||||
|
||||
```
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
57
setup.sh
Executable 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"
|
||||
Loading…
x
Reference in New Issue
Block a user