Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
214 changes: 213 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,213 @@
# AndroidAgent
# AndroidAgent 🤖

A native Android personal AI companion app powered by a local LLM. Take your AI friend with you, and watch it grow into a trusted companion that can do everything from translating text to reading and replying to your mail — with a bit of fun thrown in!

---

## ✨ Features

### 🧠 Local LLM Integration
Connect to your own local AI — no cloud subscription required. AndroidAgent supports:

| Provider | Default URL |
|----------|-------------|
| **Ollama** | `http://localhost:11434` |
| **LM Studio** | `http://localhost:1234` |
| **LLaMA.cpp Server** | `http://localhost:8080` |
| **Jan.ai** | `http://localhost:1337` |
| **GPT4All** | `http://localhost:4891` |
| **Custom OpenAI-Compatible** | *(any)* |

All configs are pre-filled with defaults — just edit the model name and hit **Fetch** to pull available models from your server.

---

### 🤖 Your Robot Team

Five named robot agents handle tasks and report back conversationally on the home screen:

| Robot | Role | Status examples |
|-------|------|-----------------|
| **ARIA 🤖** | Main AI companion — chat, mood, personality | "Ready to chat! ✨", "Thinking… 🤔" |
| **MAXIE 💬** | Messaging — SMS, WhatsApp, email monitoring & auto-reply | "New SMS from Alice! 📱", "Replying… ✍️" |
| **FELIX 📁** | File manager — workspace files, create/read/delete | "Scanning files… 📂", "File saved!" |
| **NEXUS 🌐** | Web — internet lookups and downloads | "Searching the web… 🔍" |
| **MEMO 🧠** | Memory — learns from conversations, builds knowledge | "Updating memory… 💡" |

---

### 💬 Chat Interface
- Friendly conversation bubbles (user vs ARIA messages)
- Typing indicator while the LLM responds
- Voice input button (microphone)
- Camera input button
- Per-conversation history
- Clear conversation via menu

---

### 📱 Message Monitoring & Auto-Reply
- **SMS receiver** — intercepts incoming SMS, routes to MAXIE
- **Notification listener** — monitors WhatsApp, Gmail, Outlook, and SMS apps
- **Per-contact auto-reply** — configure which contacts/chats trigger automatic LLM replies
- Per-channel toggles: SMS / Email / WhatsApp

---

### 💾 Persistent Memory (SQLite / Room)
- Every conversation is stored in an SQLite database
- **Memory table** stores key facts (name, preferences, important info) with importance scores
- Memories are injected into the LLM system context so ARIA "remembers" across sessions
- Clear individual memories or all memories from Settings

---

### 📁 File Manager
- Dedicated workspace directory on the device
- Create, read, and delete text files
- SwipeRefreshLayout to refresh
- File size and date shown in list

---

### 🎨 Animated Companion Face
`CompanionFaceView` is a custom Canvas view showing ARIA's face:
- Metallic rounded-rectangle head with gradient shading
- Blinking eyes with wandering pupils
- Mood-driven mouth (smile / talking animation)
- Antenna with glowing accent dot
- Accent colour changes with mood (blue = calm, green = happy, yellow = thinking, red = alert)

---

### ⚙️ Settings
- LLM Configuration (full CRUD with Test Connection)
- Auto-Reply master toggle + per-channel toggles
- Contacts manager for auto-reply allowlist
- Memory section (view / clear)
- App version & robot roster

---

## 🏗️ Architecture

```
app/
├── CompanionApplication.kt # Application class; initialises DB + AgentManager
├── MainActivity.kt # Single activity; Navigation Component + BottomNav
├── agents/
│ ├── AgentRobot.kt # Data class + Robot interface + RobotMood enum
│ ├── AgentManager.kt # Singleton; LiveData robot status; triggerRobot()
│ └── robots/
│ ├── AriaRobot.kt # Main companion with AriaMood
│ ├── MaxieRobot.kt # SMS / email / WhatsApp handler
│ ├── FelixRobot.kt # File manager
│ ├── NexusRobot.kt # Web / internet
│ └── MemoRobot.kt # Memory & learning
├── data/
│ ├── database/
│ │ ├── AppDatabase.kt # Room database (v1)
│ │ ├── dao/ # MessageDao, MemoryDao, AgentContactDao, LLMConfigDao
│ │ └── entities/ # Message, Memory, AgentContact, LLMConfig
│ └── repository/
│ ├── ChatRepository.kt # Wraps MessageDao + LLMManager
│ └── MemoryRepository.kt # Memory CRUD + context-summary builder
├── llm/
│ ├── LLMProvider.kt # Interface: chat(), listModels()
│ ├── LLMManager.kt # Routes to provider; injects memory context
│ └── providers/
│ ├── OllamaProvider.kt # Ollama streaming NDJSON
│ └── OpenAICompatibleProvider.kt # /v1/chat/completions for all others
├── services/
│ ├── AgentNotificationListenerService.kt # NotificationListenerService
│ └── AgentSmsReceiver.kt # BroadcastReceiver for SMS_RECEIVED
└── ui/
├── main/
│ ├── CompanionFaceView.kt # Custom Canvas animated face
│ └── MainFragment.kt # Home screen: face + robot cards
├── chat/
│ ├── ChatFragment.kt
│ ├── ChatAdapter.kt # Dual ViewHolder (user / agent bubbles)
│ └── ChatViewModel.kt
├── files/
│ ├── FilesFragment.kt
│ ├── FilesAdapter.kt
│ └── FilesViewModel.kt
└── settings/
├── SettingsFragment.kt
├── LLMConfigFragment.kt # Full LLM config with preset URLs + Fetch Models
└── ContactsAutoReplyFragment.kt
```

**Tech stack:** Kotlin · Room (SQLite) · OkHttp · Gson · Jetpack Navigation · LiveData · Coroutines · Material Design 3 · ViewBinding

---

## 🚀 Getting Started

### Prerequisites
1. Android Studio Hedgehog (2023.1.1) or newer
2. A running local LLM server (e.g. [Ollama](https://ollama.ai/))

### Build & Run
```bash
git clone https://github.com/magicalmutation-coder/AndroidAgent.git
cd AndroidAgent
# Open in Android Studio and let Gradle sync, then Run
```

Or from the command line (requires `ANDROID_HOME` set and Gradle wrapper):
```bash
./gradlew assembleDebug
```

### First Launch
1. Grant the requested permissions (SMS, Contacts, Notifications)
2. Go to **Settings → Configure LLM Server**
3. Select your provider (e.g. Ollama), verify the URL, enter your model name, tap **Test Connection**
4. Tap **Set Active** to make it the active config
5. Return to **Home** — tap the chat bubble at the bottom to start talking to ARIA!

### Notification Listener Permission
To enable WhatsApp / email monitoring, Android requires a special system permission:

> Settings → Special app access → Notification access → AndroidAgent → Enable

The app will prompt you with instructions on first use.

---

## 📋 Permissions

| Permission | Purpose |
|-----------|---------|
| `INTERNET` | LLM server communication + web lookups |
| `READ_SMS` / `RECEIVE_SMS` / `SEND_SMS` | SMS monitoring and auto-reply |
| `READ_CONTACTS` | Contact name resolution |
| `RECORD_AUDIO` | Voice input |
| `CAMERA` | Camera input for context |
| `BIND_NOTIFICATION_LISTENER_SERVICE` | WhatsApp / email notification monitoring |
| `FOREGROUND_SERVICE` | Background processing |

---

## 🛣️ Roadmap
- [ ] Voice-to-text (SpeechRecognizer integration)
- [ ] Camera-to-text (ML Kit OCR)
- [ ] WhatsApp auto-reply via Accessibility Service
- [ ] Translation feature (NEXUS robot)
- [ ] Web search (NEXUS robot)
- [ ] Reminder / calendar integration
- [ ] Themed robot animations (walking across screen)
- [ ] Export / import conversation memories
- [ ] Multiple conversation threads

---

## 📄 License
MIT License — see [LICENSE](LICENSE) for details.
61 changes: 61 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.kapt'
}

android {
namespace 'com.androidagent'
compileSdk 34

defaultConfig {
applicationId "com.androidagent"
minSdk 26
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
buildFeatures {
viewBinding true
}
}

dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.room:room-ktx:2.6.1'
kapt 'androidx.room:room-compiler:2.6.1'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
5 changes: 5 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Default ProGuard rules
-keepattributes *Annotation*
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.androidagent

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.androidagent", appContext.packageName)
}
}
59 changes: 59 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:name=".CompanionApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidAgent"
android:usesCleartextTraffic="true">

<activity
android:name=".MainActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service
android:name=".services.AgentNotificationListenerService"
android:exported="true"
android:label="@string/notification_listener_label"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>

<receiver
android:name=".services.AgentSmsReceiver"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>

</application>

</manifest>
23 changes: 23 additions & 0 deletions app/src/main/java/com/androidagent/CompanionApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.androidagent

import android.app.Application
import com.androidagent.agents.AgentManager
import com.androidagent.data.database.AppDatabase

class CompanionApplication : Application() {

val database: AppDatabase by lazy { AppDatabase.getInstance(this) }
val agentManager: AgentManager by lazy { AgentManager.getInstance(this) }

override fun onCreate() {
super.onCreate()
instance = this
// Initialize agent manager eagerly so robots are ready
agentManager.initialize()
}

companion object {
lateinit var instance: CompanionApplication
private set
}
}
Loading