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
|
# 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
|
ANDROID_HOME ?= $(HOME)/android-sdk
|
||||||
ADB := $(ANDROID_HOME)/platform-tools/adb
|
ADB := $(ANDROID_HOME)/platform-tools/adb
|
||||||
@ -7,10 +7,22 @@ GRADLEW := ./gradlew
|
|||||||
APK := app/build/outputs/apk/debug/app-debug.apk
|
APK := app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
|
||||||
help: ## Show available commands
|
help: ## Show available commands
|
||||||
@echo "Crkl - Physical Phone Commands"
|
@echo "Crkl - Android Development Commands"
|
||||||
@echo ""
|
@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 build - Build APK"
|
||||||
@echo " make install - Install to phone"
|
@echo " make install - Install to device/emulator"
|
||||||
@echo " make run - Launch app"
|
@echo " make run - Launch app"
|
||||||
@echo " make logs - Watch app logs"
|
@echo " make logs - Watch app logs"
|
||||||
@echo " make stop - Stop app"
|
@echo " make stop - Stop app"
|
||||||
@ -18,28 +30,99 @@ help: ## Show available commands
|
|||||||
@echo " make uninstall - Remove app"
|
@echo " make uninstall - Remove app"
|
||||||
@echo ""
|
@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
|
build: ## Build APK
|
||||||
@echo "Building Crkl..."
|
@echo "Building Crkl..."
|
||||||
@$(GRADLEW) assembleDebug
|
@$(GRADLEW) assembleDebug
|
||||||
|
|
||||||
install: build ## Install to phone
|
install: build ## Install to phone
|
||||||
@echo "Installing 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"
|
@echo "✓ Installed"
|
||||||
|
|
||||||
run: ## Launch app
|
run: ## Launch app
|
||||||
@echo "Launching Crkl..."
|
@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
|
logs: ## Watch app logs
|
||||||
@echo "Watching logs (Ctrl+C to stop)..."
|
@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
|
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
|
clean: ## Clean build
|
||||||
@$(GRADLEW) clean
|
@$(GRADLEW) clean
|
||||||
|
|
||||||
uninstall: ## Remove app
|
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
|
## 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.
|
See [PHONE_SETUP.md](PHONE_SETUP.md) for detailed setup instructions.
|
||||||
|
|
||||||
### 2. Build and Install
|
### 3. Build and Install
|
||||||
```bash
|
```bash
|
||||||
make build # Build APK
|
make check-device # Check if device is connected
|
||||||
make install # Install to phone
|
make build # Build APK
|
||||||
make run # Launch app
|
make install # Install to device
|
||||||
|
make run # Launch app
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Enable Accessibility Service
|
### 3. Enable Accessibility Service
|
||||||
@ -34,9 +41,17 @@ Touch your phone screen anywhere - you'll see touch detection logs and visual fe
|
|||||||
|
|
||||||
## Commands
|
## 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
|
```bash
|
||||||
make build # Build APK
|
make build # Build APK
|
||||||
make install # Install to phone
|
make install # Install to device
|
||||||
make run # Launch app
|
make run # Launch app
|
||||||
make logs # Watch app logs
|
make logs # Watch app logs
|
||||||
make stop # Stop app
|
make stop # Stop app
|
||||||
@ -44,6 +59,11 @@ make clean # Clean build
|
|||||||
make uninstall # Remove app
|
make uninstall # Remove app
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Help
|
||||||
|
```bash
|
||||||
|
make help # Show all available commands
|
||||||
|
```
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
package com.example.crkl.accessibility
|
package com.example.crkl.accessibility
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityService
|
import android.accessibilityservice.AccessibilityService
|
||||||
import android.accessibilityservice.GestureDescription
|
|
||||||
import android.graphics.PixelFormat
|
import android.graphics.PixelFormat
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.view.accessibility.AccessibilityEvent
|
import android.view.accessibility.AccessibilityEvent
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -17,10 +14,12 @@ import kotlinx.coroutines.cancel
|
|||||||
/**
|
/**
|
||||||
* Crkl Accessibility Service
|
* 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.
|
* and provides AI-powered assistance based on selected screen content.
|
||||||
*
|
*
|
||||||
* Privacy: All processing is local. No data leaves the device.
|
* Privacy: All processing is local. No data leaves the device.
|
||||||
|
*
|
||||||
|
* Note: No overlay is created to avoid blocking touch events.
|
||||||
*/
|
*/
|
||||||
class CrklAccessibilityService : AccessibilityService() {
|
class CrklAccessibilityService : AccessibilityService() {
|
||||||
|
|
||||||
@ -33,36 +32,90 @@ class CrklAccessibilityService : AccessibilityService() {
|
|||||||
super.onServiceConnected()
|
super.onServiceConnected()
|
||||||
Log.d(TAG, "Crkl Accessibility Service connected")
|
Log.d(TAG, "Crkl Accessibility Service connected")
|
||||||
|
|
||||||
// Initialize overlay
|
// Create floating action button for Crkl activation
|
||||||
setupOverlay()
|
setupFloatingButton()
|
||||||
|
Log.d(TAG, "Service ready - floating button created")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupOverlay() {
|
private fun setupFloatingButton() {
|
||||||
try {
|
try {
|
||||||
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
|
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
|
||||||
|
|
||||||
// Create overlay view
|
// Create overlay view with callback for mode changes
|
||||||
overlayView = OverlayView(this)
|
overlayView = OverlayView(this) { isOverlayMode ->
|
||||||
|
updateOverlayMode(isOverlayMode)
|
||||||
|
}
|
||||||
|
|
||||||
// Configure window layout parameters for accessibility overlay
|
// Configure window layout parameters for floating button
|
||||||
val params = WindowManager.LayoutParams(
|
val params = WindowManager.LayoutParams(
|
||||||
WindowManager.LayoutParams.MATCH_PARENT,
|
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||||
WindowManager.LayoutParams.MATCH_PARENT,
|
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||||
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
|
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
|
||||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
||||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
|
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
|
||||||
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
|
|
||||||
PixelFormat.TRANSLUCENT
|
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)
|
windowManager?.addView(overlayView, params)
|
||||||
|
|
||||||
Log.d(TAG, "Overlay created successfully")
|
Log.d(TAG, "Floating button created successfully")
|
||||||
} catch (e: Exception) {
|
} 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()
|
super.onDestroy()
|
||||||
Log.d(TAG, "Service destroyed")
|
Log.d(TAG, "Service destroyed")
|
||||||
|
|
||||||
// Clean up overlay
|
// Clean up floating button
|
||||||
overlayView?.let {
|
overlayView?.let {
|
||||||
windowManager?.removeView(it)
|
windowManager?.removeView(it)
|
||||||
overlayView = null
|
overlayView = null
|
||||||
|
|||||||
@ -6,111 +6,245 @@ import android.graphics.Color
|
|||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.View.MeasureSpec
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
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.
|
* This view provides a floating action button that toggles to a full-screen overlay
|
||||||
* It draws a visual indicator when the user circles or touches the screen.
|
* for capturing touch gestures and circle selections. It reports selection bounds
|
||||||
*
|
* for content analysis.
|
||||||
* Phase 1 (POC): Simple tap detection and visual feedback
|
|
||||||
* Phase 2: Circle gesture recognition
|
|
||||||
* Phase 3: Region extraction and content analysis
|
|
||||||
*/
|
*/
|
||||||
class OverlayView(context: Context) : View(context) {
|
class OverlayView(
|
||||||
|
context: Context,
|
||||||
|
private val onModeChanged: (Boolean) -> Unit = {}
|
||||||
|
) : View(context) {
|
||||||
|
|
||||||
private val TAG = "OverlayView"
|
private val TAG = "OverlayView"
|
||||||
|
|
||||||
// Paint for drawing touch indicators
|
// Button properties
|
||||||
private val circlePaint = Paint().apply {
|
private val buttonSize = 80f
|
||||||
color = Color.parseColor("#8003DAC6") // Semi-transparent secondary color
|
private var buttonColor = Color.BLUE
|
||||||
style = Paint.Style.STROKE
|
private val buttonPaint = Paint().apply {
|
||||||
strokeWidth = 8f
|
color = buttonColor
|
||||||
isAntiAlias = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private val fillPaint = Paint().apply {
|
|
||||||
color = Color.parseColor("#2003DAC6") // Very transparent fill
|
|
||||||
style = Paint.Style.FILL
|
style = Paint.Style.FILL
|
||||||
isAntiAlias = true
|
isAntiAlias = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch tracking
|
// Overlay mode properties
|
||||||
private var touchPoint: PointF? = null
|
private var isOverlayMode = false
|
||||||
private var isActive = false
|
private val overlayPaint = Paint().apply {
|
||||||
private val touchRadius = 80f
|
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 {
|
init {
|
||||||
// Make view transparent
|
|
||||||
setBackgroundColor(Color.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 {
|
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||||
event ?: return false
|
if (!isOverlayMode) {
|
||||||
|
// In button mode, only handle clicks on the button itself
|
||||||
when (event.action) {
|
return super.onTouchEvent(event)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return false to allow touch events to pass through to underlying apps
|
// In overlay mode, handle drawing
|
||||||
// In production, we'll want smarter handling based on gesture state
|
event?.let { motionEvent ->
|
||||||
return false
|
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) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
|
|
||||||
// Only draw when user is actively touching
|
if (isOverlayMode) {
|
||||||
if (isActive && touchPoint != null) {
|
drawOverlayMode(canvas)
|
||||||
val point = touchPoint!!
|
} else {
|
||||||
|
drawButtonMode(canvas)
|
||||||
// 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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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"
|
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:description="@string/accessibility_service_description"
|
android:description="@string/accessibility_service_description"
|
||||||
android:accessibilityEventTypes="typeAllMask"
|
android:accessibilityEventTypes="typeAllMask"
|
||||||
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagRequestTouchExplorationMode"
|
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
|
||||||
android:accessibilityFeedbackType="feedbackGeneric"
|
android:accessibilityFeedbackType="feedbackGeneric"
|
||||||
android:canRetrieveWindowContent="true"
|
android:canRetrieveWindowContent="true"
|
||||||
android:canRequestTouchExplorationMode="true"
|
android:canRequestTouchExplorationMode="false"
|
||||||
android:notificationTimeout="100" />
|
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