Skip to content

Technical Implementation

thejaustin edited this page Dec 25, 2025 · 1 revision

Technical Implementation

Deep dive into AutoCat's architecture, implementation details, and technical decisions.

Architecture Overview

System Components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      AutoCat Application                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚ UI Layer    β”‚  β”‚ Business     β”‚  β”‚ Data Layer       β”‚    β”‚
β”‚  β”‚             β”‚  β”‚ Logic        β”‚  β”‚                  β”‚    β”‚
β”‚  β”‚ - Settings  │──│ - Categoriz. │──│ - Room Database  β”‚    β”‚
β”‚  β”‚ - Drawer    β”‚  β”‚ - LLM Mgmt   β”‚  β”‚ - Preferences    β”‚    β”‚
β”‚  β”‚ - Compose   β”‚  β”‚ - Sync       β”‚  β”‚ - Entities       β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚         β”‚                β”‚                     β”‚              β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚                          β”‚                                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                          β”‚                                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚           External LLM Providers (API)             β”‚      β”‚
β”‚  β”‚  - Google AI  - Claude  - OpenAI  - Perplexity     β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core Systems

1. Categorization Pipeline

File: CategorizationManager.kt

Purpose: Orchestrates multi-stage categorization process

Stages:

  1. Built-in Categorizer (BuiltInCategorizer.kt)

    • Fast, rule-based categorization
    • Uses Android package patterns
    • 60-70% coverage
    • Batch database inserts for performance
  2. LLM Categorizer (LLMCategorizer.kt)

    • AI-powered categorization
    • Handles remaining apps
    • Batch API processing (20x faster)
    • Provider fallback logic
  3. Folder Sync (CategoryFolderSyncService.kt)

    • Creates persistent folders
    • Syncs drawer + home screen
    • Bidirectional sync support

Flow:

suspend fun initializeCategorization() {
    // Stage 1: Built-in categorization
    val uncategorized = builtInCategorizer.categorizeAll()

    // Stage 2: LLM categorization
    if (uncategorized.isNotEmpty()) {
        llmCategorizer.categorizeBatch(uncategorized)
    }

    // Stage 3: Folder sync
    folderSyncService.syncCategoriesToFolders()
}

2. LLM Provider System

Interface: LLMProvider.kt

Purpose: Abstraction for multiple LLM providers

Implementations:

  • GoogleAIProvider.kt - Gemini 2.0 Flash
  • ClaudeProvider.kt - Claude 3.5 Haiku/Sonnet
  • OpenAIProvider.kt - GPT-4o Mini
  • PerplexityProvider.kt - Llama 3.1 Sonar

Key Methods:

interface LLMProvider {
    suspend fun categorizeApp(
        appName: String,
        packageName: String,
        appDescription: String?,
        availableCategories: List<String>
    ): CategorizationResult

    suspend fun categorizeAppBatch(
        apps: List<AppBatchInfo>,
        availableCategories: List<String>
    ): Map<String, CategorizationResult>

    suspend fun testConnection(): TestResult
}

Features:

  • Batch processing support
  • Circuit breaker pattern
  • Confidence calibration
  • Provider-specific model selection

3. Database Layer

Database: TabDatabase.kt (Room)

Version: 6

Entities:

  1. AppTab - App category assignments

    @Entity(tableName = "app_tab")
    data class AppTab(
        @PrimaryKey val packageName: String,
        val tabName: String,
        val confidence: Float,
        val source: String,
        val isUserOverride: Boolean,
        val provider: String?,
        val model: String?,
        val timestamp: Long
    )
  2. CustomTab - User-defined categories

    @Entity(tableName = "custom_tab")
    data class CustomTab(
        @PrimaryKey(autoGenerate = true) val id: Int,
        val name: String,
        val colorHex: String,
        val isVisible: Boolean,
        val sortOrder: Int
    )
  3. ModelAccuracy - LLM performance tracking

    @Entity(tableName = "model_accuracy")
    data class ModelAccuracy(
        @PrimaryKey(autoGenerate = true) val id: Int,
        val provider: String,
        val model: String,
        val category: String,
        val wasCorrect: Boolean,
        val confidence: Float,
        val timestamp: Long,
        val packageName: String
    )

DAOs:

  • TabDao.kt - Category CRUD operations
  • AccuracyDao.kt - Accuracy metrics queries

4. Accuracy Tracking System

Components:

  1. AccuracyTracker (AccuracyTracker.kt)

    • Records user corrections
    • Tracks accepted categorizations
    • Passive learning system
  2. AdaptiveModelSelector (AdaptiveModelSelector.kt)

    • Analyzes 30-day accuracy data
    • Auto-selects best performing provider
    • Requires minimum 10 samples, 70% accuracy

Logic:

suspend fun getBestProvider(): String? {
    val since = System.currentTimeMillis() - (30 * 24 * 60 * 60 * 1000L)
    val stats = accuracyDao.getAccuracyByModel(since)

    return stats
        .filter { it.total >= 10 && it.accuracy >= 70.0 }
        .maxByOrNull { it.accuracy }
        ?.provider
}

5. Batch Processing

File: BatchCalculator.kt

Purpose: Calculate optimal batch sizes based on model context windows

Algorithm:

fun calculateBatchSize(
    apps: List<AppInfo>,
    contextWindow: Int,
    promptOverhead: Int
): Int {
    val avgTokensPerApp = 50
    val availableTokens = contextWindow - promptOverhead
    return min(apps.size, availableTokens / avgTokensPerApp)
}

Performance Impact:

  • Before: 100 apps Γ— 4s = 400 seconds
  • After: 5 batches Γ— 4s = 20 seconds
  • 20x speedup

Token Savings:

  • Before: 200 tokens Γ— 100 = 20,000 tokens
  • After: 500 tokens Γ— 5 = 2,500 tokens
  • 8x reduction

Performance Optimizations

1. Lazy Initialization

Problem: Heavy services blocking UI thread

Solution: Lazy properties with Dispatchers.IO

private val categorizationManager by lazy {
    CategorizationManager(context)
}

LaunchedEffect(Unit) {
    withContext(Dispatchers.IO) {
        categorizationManager.initialize()
    }
}

Impact: Faster app startup, no UI freezes


2. Database Batch Inserts

Problem: N+1 query problem (Issue #2)

Solution: Batch inserts in transactions

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(categories: List<AppTab>)

// Usage
categoryDao.insertAll(categorizedApps)  // Single transaction

Impact: 10x faster bulk operations


3. Skip Already Categorized Apps

Problem: Re-categorizing apps unnecessarily

Solution: Filter out apps with existing categories

val uncategorized = allApps.filter { app ->
    categoryDao.getAppCategory(app.packageName) == null
}

Impact: Faster startup, reduced API costs


4. Circuit Breaker Pattern

Problem: Provider failures causing cascading errors

Solution: ProviderCircuitBreaker.kt

class ProviderCircuitBreaker(
    private val failureThreshold: Int = 5,
    private val timeoutMs: Long = 60000
) {
    suspend fun <T> execute(block: suspend () -> T): T {
        if (state == State.OPEN) {
            throw CircuitBreakerOpenException()
        }

        return try {
            val result = block()
            onSuccess()
            result
        } catch (e: Exception) {
            onFailure()
            throw e
        }
    }
}

Impact: Better error handling, automatic provider failover


Security Implementations

1. Prompt Injection Protection

Problem: Malicious apps injecting commands via metadata (Issue #11)

Solution: Input sanitization

private fun sanitizeInput(input: String): String {
    return input
        .replace("\\", "\\\\")
        .replace("\"", "\\\"")
        .replace("\n", " ")
        .replace("\r", " ")
        .take(200)  // Limit length
}

Impact: Prevents LLM command injection


2. API Key Protection

Features:

  • Environment variable support
  • Keys not logged in debug output
  • Sanitized in crash reports
private fun getApiKey(): String {
    // Prefer environment variable
    val envKey = System.getenv("GOOGLE_AI_API_KEY")
    if (!envKey.isNullOrBlank()) return envKey

    // Fallback to preference
    return prefManager.llmGoogleAIKey.get()
}

3. Network Security

Features:

  • HTTPS-only connections
  • Connection pooling (OkHttpClient)
  • Timeout configuration
  • Secure TLS defaults
private val client = OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .build()

Database Schema Evolution

Version History

Version Changes Commit
1 Initial schema (AppCategory, CustomCategory) 6eff9b0
2-3 Renamed tables (categories β†’ tabs) 2742d3d
4 Added timestamp field -
5 Added provider/model fields 546714d
6 Added ModelAccuracy entity 432c961

Migration Strategy

Development: Destructive migration (drop and recreate)

@Database(version = 6, exportSchema = false)
abstract class TabDatabase : RoomDatabase() {
    companion object {
        fun getInstance(context: Context): TabDatabase {
            return Room.databaseBuilder(
                context,
                TabDatabase::class.java,
                "tab_database"
            )
            .fallbackToDestructiveMigration()  // Dev only
            .build()
        }
    }
}

Production: Manual migrations (future)


UI Architecture

Jetpack Compose

All AutoCat UI is built with Compose:

  • Declarative UI
  • State hoisting
  • Material 3 components
  • Theme-aware design

Example:

@Composable
fun LLMSettingsPreferences() {
    val preferenceManager = PreferenceManager.getInstance(LocalContext.current)

    PreferenceLayout(label = "LLM Settings") {
        PreferenceGroup(heading = "Provider Selection") {
            ListPreference(
                adapter = preferenceManager.llmProviderPreference.getAdapter(),
                entries = providers,
                label = "LLM Provider"
            )
        }
    }
}

Material 3 Expressive Design

Applied in Phase 20:

  • Enhanced cards with elevation
  • Better spacing (16dp/24dp grid)
  • Improved typography scale
  • Primary color accents
  • Visual hierarchy

Testing Strategy

Current Testing

Manual testing:

  • Feature functionality
  • UI/UX validation
  • Performance benchmarks
  • API integration

Automated testing:

  • GitHub Actions CI
  • Build verification
  • Code style checks (Spotless)

Planned Testing

Unit tests:

  • Database operations
  • Batch calculations
  • Provider selection logic
  • Input sanitization

Integration tests:

  • Full categorization pipeline
  • Provider fallback chains
  • Folder sync operations

Build System

GitHub Actions CI/CD

Workflow: .github/workflows/ci.yml

Triggers:

  • Push to 15-dev
  • Pull requests

Steps:

  1. Checkout code with submodules
  2. Set up Java 21
  3. Build debug APK variants
  4. Run Spotless formatting check
  5. Upload artifacts
  6. Create/update dev-latest release

Build time: ~10-15 minutes

Version Scheme

Format: 15.0.b1-autocat.{BUILD_NUMBER}

Example: 15.0.b1-autocat.100

Components:

  • 15.0 - Android version (15)
  • b1 - Lawnchair beta number
  • autocat.100 - AutoCat build number

Future Technical Improvements

Planned Enhancements

  1. Parallel Batch Processing

    • Concurrent API calls to multiple providers
    • Worker pool pattern
    • Estimated 2-3x speedup
  2. Caching Layer

    • Cache LLM responses (deduplication)
    • Reduce API costs
    • Faster re-categorization
  3. Advanced Analytics

    • Time-series accuracy data
    • Category confusion matrix
    • Token usage tracking
  4. Plugin System

    • Custom LLM provider support
    • Third-party categorization logic
    • Import/export plugins

Technical Debt

Current Debt

  1. Database Migrations

    • Using destructive migration (dev only)
    • Need production migration strategy
  2. Error Handling

    • Some error paths need better handling
    • More specific error messages needed
  3. Test Coverage

    • No automated unit tests yet
    • Integration tests needed

Mitigation Plan

  1. Q1 2026: Add unit tests for critical paths
  2. Q1 2026: Implement production database migrations
  3. Q2 2026: Improve error handling comprehensively

Performance Benchmarks

Categorization Speed

Scenario Before After Improvement
100 apps (sequential) 400s 400s -
100 apps (batch) 400s 20s 20x
Database inserts 10s 1s 10x
Startup time 3s 1s 3x

Memory Usage

Component Memory
Database ~5 MB
LLM cache ~2 MB
UI state ~1 MB
Total ~8 MB

Note: Minimal overhead compared to Lawnchair base


API Rate Limits

Provider Limits

Provider Free Tier Paid Tier
Google AI 15 RPM 60 RPM
Claude N/A 50-1000 RPM
OpenAI 3 RPM (trial) 500 RPM
Perplexity 5 RPM 50 RPM

Batch Processing Impact

Without batching:

  • 100 apps = 100 requests
  • At 15 RPM = 6.7 minutes

With batching (size 20):

  • 100 apps = 5 requests
  • At 15 RPM = 20 seconds

Rate limit avoidance: Batch processing is critical


Code Organization

Package Structure

app.lawnchair/
β”œβ”€β”€ categorization/           # Core categorization logic
β”‚   β”œβ”€β”€ llm/                  # LLM providers
β”‚   β”œβ”€β”€ stages/               # Categorization pipeline
β”‚   └── learning/             # User correction learning
β”œβ”€β”€ data/tab/                 # Database layer
β”‚   β”œβ”€β”€ entities/             # Room entities
β”‚   └── *.kt                  # DAOs
β”œβ”€β”€ ui/preferences/           # Settings UI
β”‚   β”œβ”€β”€ destinations/         # Preference screens
β”‚   └── components/           # Reusable UI components
└── preferences/              # Preference definitions

File Naming Conventions

  • Services: *Service.kt (e.g., CategoryFolderSyncService.kt)
  • Providers: *Provider.kt (e.g., GoogleAIProvider.kt)
  • Managers: *Manager.kt (e.g., CategorizationManager.kt)
  • Preferences: *Preferences.kt (e.g., LLMSettingsPreferences.kt)
  • Entities: Noun (e.g., AppTab.kt, CustomTab.kt)

References

Key Documentation

External Resources


Last Updated: 2025-12-25

Clone this wiki locally