This document describes the overall architecture of the LimeIME (LIME Input Method Engine) Android application, focusing on the relationships and interactions between the core components: LIMEService, DBServer, SearchServer, UI components, and the IME logic flow including query paths and learning paths.
- Separation of Concerns: Each component has a clear, focused responsibility
- Single Interface:
SearchServeris the single interface for all database operations - Centralized Operations:
LimeDB: All low-level SQL operationsDBServer: All file operations (zip, unzip, import, export, backup, restore)SearchServer: All database operations (search, query, configuration)
- No Direct Database Access: UI components and
LIMEServiceshould not callLimeDBdirectly
┌─────────────────────────────────────────────────────────────────────────────┐
│ LIMEService (InputMethodService) │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Input Handling Layer │ │
│ │ - onKey() / onKeyDown() / onKeyUp() │ │
│ │ - Composing text management │ │
│ │ - InputConnection operations │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────┐ ┌──────────────────────────────────────┐ │
│ │ LIMEKeyboardView │ │ CandidateView / CandidateViewContainer│ │
│ │ (Soft Keyboard) │ │ (Candidate Window) │ │
│ │ - LIMEKeyboard │ │ - Candidate selection │ │
│ │ - LIMEBaseKeyboard │ │ - Related phrase display │ │
│ │ - LIMEKeyboardSwitcher │ │ - CandidateViewHandler │ │
│ └──────────────────────────────┘ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ SearchServer │
│ (Unified interface for all database search operations) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ LimeDB │
│ (SQL operations / parameterized queries) │
│ ┌────────────────────────────┐ ┌────────────────────────────────────────┐ │
│ │ LimeHanConverter │ │ EmojiConverter │ │
│ │ (hanconvertv2.db) │ │ (emoji.db) │ │
│ └────────────────────────────┘ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
SQLiteDatabase (main)
UI Components → SearchServer → LimeDB → SQLiteDatabase (main)
├─> LimeHanConverter → SQLiteDatabase (hanconvertv2.db)
└─> EmojiConverter → SQLiteDatabase (emoji.db)
UI Components → DBServer → LimeDB → SQLiteDatabase (main)
Role: Android Input Method Service - handles user input and provides IME functionality
Responsibilities:
- Handle keyboard input events (soft keyboard and hardware keyboard)
- Display candidate suggestions via CandidateView
- Manage input method lifecycle
- Coordinate with SearchServer for search operations
- Manage composing text and InputConnection operations
- Handle learning flow (score updates, related phrases, LD phrases)
- Manage UI state and keyboard switching
Key Sub-components:
- LIMEKeyboardView: Soft keyboard rendering and touch handling
- LIMEKeyboard: Keyboard layout management
- LIMEBaseKeyboard: Base keyboard functionality
- LIMEKeyboardSwitcher: Keyboard type switching
- CandidateView/CandidateViewContainer: Candidate word display
- Candidate selection handling
- Related phrase display
- CandidateViewHandler: Candidate list management
Dependencies:
SearchServer- For all database operations (search, configuration)LIMEPreferenceManager- For user preferences- Android IME framework
Key Methods:
onKey()- Handles soft keyboard key pressesonKeyDown()/onKeyUp()- Handles hardware keyboard eventsonCreate()- Initializes SearchServerupdateCandidates()- Updates candidate list based on composing textpickCandidateManually()- Handles candidate selectioncommitText()- Commits selected text to InputConnectionswitchKeyboard()- Switches between input methodspostFinishInput()- Triggers learning flow at session end
Architecture Position:
LIMEService (IME Service Layer)
├─> Input Handling Layer (onKey, onKeyDown, onKeyUp)
├─> LIMEKeyboardView (Soft Keyboard)
├─> CandidateView (Candidate Window)
└─> SearchServer (Database Interface Layer)
└─> LimeDB (SQL Operations Layer)
Role: Single interface for all database operations - search, query, and configuration
Responsibilities:
- Provide search/query operations with caching
- Provide IM configuration operations
- Manage cache for performance
- Coordinate runtime phrase suggestions
- Handle user dictionary learning (score updates, related phrases, LD phrases)
Dependencies:
LimeDB- For all SQL operations (main database and helper converters)LIMEPreferenceManager- For user preferencesContext- For application context
Helper Converters (Separate Databases):
LimeHanConverter- Manages separatehanconvertv2.dbfor Traditional/Simplified Chinese conversionEmojiConverter- Manages separateemoji.dbfor emoji conversion- Note:
SearchServercan access these converters directly or throughLimeDB. They manage their own separate databases.
Key Operations:
getMappingByCode()- Search record mappings by code (with caching)getRelatedPhrase()- Get related phrase suggestionsgetEnglishSuggestions()- Get English record suggestionsaddLDPhrase()- Add learning dictionary phraselearnRelatedPhraseAndUpdateScore()- Learn phrase patterns and update scores
learnRelatedPhraseAndUpdateScore(mapping)- Update score and add to learning scorelistlearnRelatedPhrase(scorelist)- Create related phrase records from consecutive word selectionslearnLDPhrase(phraselist)- Create LD (Learning Dictionary) phrase records with Quick Phrase codes
getKeyboardList()- Get list of available keyboardsgetImList()- Get list of available input methodsgetIm(code, type)- Get IM list by type (for UI)getKeyboard()- Get keyboard list (for UI)getImInfo(),setImInfo()- Get/set IM configuration infosetIMKeyboard()- Set IM keyboard assignmentgetTablename(),setTablename()- Manage active IM table (part of search logic)getSelkey()- Get selection key mapping (with caching)keyToKeyname()- Convert code to key names (with caching)
hanConvert()- Traditional/Simplified Chinese conversion (uses separatehanconvertv2.db)emojiConvert()- Emoji conversion (uses separateemoji.db, with caching)getCodeListStringFromWord()- Reverse lookup (record to code)
Architecture Position:
SearchServer (Database Interface Layer)
├─> Receives requests from LIMEService
├─> Receives requests from UI Components
├─> Delegates to LimeDB for SQL operations (main database)
├─> Accesses LimeHanConverter for Chinese conversion (separate database)
└─> Accesses EmojiConverter for emoji conversion (separate database)
State Management:
- Maintains
tablenamestate (current active IM table) - Maintains
hasNumberMapping,hasSymbolMappingflags - Manages multiple caches (mapping cache, emoji cache, keyname cache, etc.)
- Manages learning scorelist for session-based learning
Role: Singleton service for database file operations
Responsibilities:
- Handle file import/export operations
- Manage database backup/restore
- Coordinate file compression/decompression
- Manage database file operations (zip, unzip)
Dependencies:
LimeDB- For database operations during import/exportLIMEUtilities- For file operations (zip, unzip)SearchServer- For cache invalidation after operationsContext- For application context
Key Operations:
importTxtTable(String filename, String tablename, LIMEProgressListener)- Import text mapping file (.lime, .cin) from file pathimportTxtTable(File sourcefile, String tablename, LIMEProgressListener)- Import text mapping file (.lime, .cin) from File objectimportDb(File sourcedb, String tableName)- Import uncompressed database file into specified IM tableimportZippedDb(File compressedSourceDB, String tableName)- Import compressed database file (.limedb) into specified IM table (handles unzip)importDbRelated(File sourcedb)- Import uncompressed related database file into related tableimportZippedDbRelated(File compressedSourceDB)- Import compressed related database file (.limedb) into related table (handles unzip)
exportZippedDb(String tableName, File targetFile, Runnable progressCallback)- Export IM database to zipped .limedb file (renamed fromexportImDatabase)exportZippedDbRelated(File targetFile, Runnable progressCallback)- Export related phrase database to zipped .limedb file (renamed fromexportRelatedDatabase)
backupDatabase(Uri uri)- Backup entire database and preferences to URI (for Android backup service)restoreDatabase(Uri uri)- Restore database and preferences from URI (for Android backup service)restoreDatabase(String srcFilePath)- Restore database and preferences from file pathbackupDefaultSharedPreference(File sharePrefs)- Backup shared preferences to filerestoreDefaultSharedPreference(File sharePrefs)- Restore shared preferences from file
zip()- Compress file to zip (renamed fromcompressFile)unzip()- Decompress zip file (renamed fromdecompressFile)
Architecture Position:
DBServer (File Operations Layer)
├─> Receives requests from UI Components
├─> Uses LimeDB for database operations
├─> Uses LIMEUtilities for file operations
└─> Notifies SearchServer to reset cache after operations
Singleton Pattern:
- Uses double-checked locking for thread-safe singleton
- Stores ApplicationContext to prevent memory leaks
- Shared instance across all components
Role: SQL operations layer for all database queries and updates
Responsibilities:
- Execute all SQL operations on the main database
- Manage database schema and migrations
- Provide low-level database access methods
- Handle database connection management
- Validate table names to prevent SQL injection
Dependencies:
SQLiteDatabase- Main database (lime.db)LimeHanConverter- Separate database for Chinese conversion (hanconvertv2.db)EmojiConverter- Separate database for emoji conversion (emoji.db)Context- For application context
Key Operations:
importDb(File sourceFile, List<String> tableNames, boolean includeRelated, boolean overwriteExisting)- Unified method to import database tables from backup file (supports backup format and direct format)importDbRelated(File sourcedbfile)- Import related phrase data from backup database file (convenience wrapper)importTxtTable(String table, LIMEProgressListener progressListener)- Import text mapping file (.lime, .cin, delimited text) into database table
exportTxtTable(String table, File targetFile, List<Im> imConfigInfo)- Export database table to text file format (.lime format with IM info header)
prepareBackup(File targetFile, List<String> tableNames, boolean includeRelated)- Unified method to prepare backup of database tables (creates backup database file with attached sourceDB)prepareBackupDb(String sourcedbfile, String sourcetable)- Prepare backup for single table (deprecated wrapper, useprepareBackup())prepareBackupRelatedDb(String sourcedbfile)- Prepare backup for related table (deprecated wrapper, useprepareBackup())backupUserRecords(String table)- Backup user-learned records (score > 0) to backup table (table + "_user")
restoreUserRecords(String table)- Restore user-learned records from backup table (table + "_user") back to main tablegetBackupTableRecords(String backupTableName)- Get all records from backup table (returns Cursor)
checkBackupTable(String table)- Check if backup table exists and has records (returns boolean)
Architecture Position:
LimeDB (SQL Operations Layer)
├─> SQLiteDatabase (main database - lime.db)
├─> LimeHanConverter (separate database - hanconvertv2.db)
└─> EmojiConverter (separate database - emoji.db)
Important Notes:
- All SQL operations are centralized in
LimeDB - Table name validation is enforced to prevent SQL injection
- Database connection management (hold/release) is handled internally
- Methods use parameterized queries where possible
- Backup format: Uses
sourceDB.customtable for mapping data - Direct format: Uses
sourceDB.{tableName}for direct table-to-table copy
Role: User interface for configuration and management
Components:
MainActivity- Main settings activity with MVC architectureNavigationManager- Manages fragment navigation and IM listProgressManager- Manages progress overlay UIShareManager- Manages sharing/export operations
SetupImFragment- Setup input methodsManageImFragment- Manage IM record dictionaryManageRelatedFragment- Manage related phrasesImportDialog- Import text/filesShareDialog- Share/export databasesSetupImLoadDialog- Load IM from remote/download- Various dialogs and fragments
Responsibilities:
- Display IM configuration options
- Allow users to import/export databases
- Manage record dictionaries
- Configure input method settings
Dependencies:
SearchServer- For all database operations (search, configuration)DBServer- For file operations (import, export, backup, restore)- Android UI framework
Architecture Position:
UI Components (Presentation Layer)
├─> SearchServer (for database operations)
│ └─> LimeDB (SQL operations)
└─> DBServer (for file operations)
└─> LimeDB (SQL operations)
Important: UI components should NOT create LimeDB instances directly. All database operations (including hanConvert and emojiConvert) should go through SearchServer.
The application uses a set of Data Transfer Objects (DTOs) to represent domain entities and transfer data between layers. All DTOs are pure POJOs (Plain Old Java Objects) with no database code - database operations are centralized in LimeDB.
Mapping (Core unified data model)
├── Record (extends Mapping - UI alias for Setup/Manage IM)
└── Related (extends Mapping - UI alias for Related Phrases)
ImConfig (Input Method configuration)
Keyboard (Keyboard layout configuration)
ImKeyboardConfig (Lightweight IM + Keyboard pair)
NavigationMenuItem (wraps Im with navigation metadata)
ChineseSymbol (Utility class providing symbol data)
Location: org.limeime.data.Mapping
Purpose: Unified data model serving multiple purposes throughout the application
Use Cases:
- IME candidates (code → word mappings)
- Database records (for import/export and management UI)
- Related phrases (parent → child word associations)
Fields:
| Field | Type | Description |
|---|---|---|
id |
String | Unique identifier |
code |
String | Input code (e.g., "ㄋㄧ" for "你") |
codeorig |
String | Original code before processing |
code3r |
String | Code without tone keys |
word |
String | Output word/phrase |
pword |
String | Parent word (for related phrases) |
related |
String | Related phrase info |
score |
int | User score (learned frequency) |
basescore |
int | Base/initial score |
highLighted |
Boolean | UI highlight flag |
recordType |
int | Record type classification |
Record Types:
| Constant | Value | Description |
|---|---|---|
RECORD_COMPOSING_CODE |
1 | User's current composing text |
RECORD_EXACT_MATCH_TO_CODE |
2 | Exact match to input code |
RECORD_PARTIAL_MATCH_TO_CODE |
3 | Partial/fuzzy match to code |
RECORD_RELATED_PHRASE |
4 | Related phrase suggestion |
RECORD_ENGLISH_SUGGESTION |
5 | English word suggestion |
RECORD_RUNTIME_BUILT_PHRASE |
6 | Runtime-generated phrase |
RECORD_CHINESE_PUNCTUATION_SYMBOL |
7 | Chinese punctuation |
RECORD_HAS_MORE_RECORDS_MARK |
8 | "More results available" marker |
RECORD_EXACT_MATCH_TO_WORD |
9 | Exact match to word (reverse) |
RECORD_PARTIAL_MATCH_TO_WORD |
10 | Partial match to word (reverse) |
RECORD_COMPLETION_SUGGESTION_WORD |
11 | Completion suggestion |
RECORD_EMOJI_WORD |
12 | Emoji suggestion |
Key Methods:
// Record type checkers
boolean isExactMatchToCodeRecord()
boolean isPartialMatchToCodeRecord()
boolean isRelatedPhraseRecord()
boolean isEnglishSuggestionRecord()
// ... and others
// Copy constructor
Mapping(Mapping other)
// Standard getters/setters
String getWord()
void setWord(String word)
int getScore()
void setScore(int score)
// ... etcExample Usage:
// Query path - display candidates
List<Mapping> candidates = searchServer.getMappingByCode("ㄋㄧ", softKeyboard, false);
for (Mapping mapping : candidates) {
if (mapping.isExactMatchToCodeRecord()) {
// Display: "你" (score: 150)
}
}
// Learning path - update score
Mapping selected = candidates.get(0);
searchServer.learnRelatedPhraseAndUpdateScore(selected);Location: org.limeime.data.Record
Purpose: UI alias for Mapping used in Setup/Manage IM contexts
Inheritance: extends Mapping
Use Cases:
- ManageImFragment (record dictionary management)
- Database record import/export operations
- Record editing dialogs
Why Separate Class:
- Provides semantic distinction - "Record" terminology in UI/database maintenance contexts
- Same underlying data structure as
Mapping - No additional fields or methods
Example Usage:
// In ManageImFragment
List<Record> records = searchServer.getRecordsByWord(searchWord);
for (Record record : records) {
// Display record in list
String display = record.getCode() + " → " + record.getWord();
}Location: org.limeime.data.Related
Purpose: UI alias for Mapping used in Related Phrases management
Inheritance: extends Mapping
Additional Accessors:
String getPword() // Parent word
String getCword() // Child word (alias for word)
int getUserscore() // User score (alias for score)
int getBasescore() // Base scoreUse Cases:
- ManageRelatedFragment (related phrase dictionary management)
- Related phrase learning operations
- Related phrase editing dialogs
Example Usage:
// In ManageRelatedFragment
List<Related> phrases = searchServer.getRelatedPhrase("你");
for (Related phrase : phrases) {
// Display: "你" → "好" (score: 25)
String display = phrase.getPword() + " → " + phrase.getCword()
+ " (score: " + phrase.getUserscore() + ")";
}Location: org.limeime.data.ImConfig
Purpose: Represents an Input Method (IM) configuration
Fields:
| Field | Type | Description |
|---|---|---|
id |
int | Database ID |
code |
String | IM identifier (e.g., "boshiamy", "phonetic") |
title |
String | Display title (e.g., "嘸蝦米") |
desc |
String | Description |
keyboard |
String | Assigned keyboard code |
disable |
boolean | Enable/disable flag |
selkey |
String | Selection keys (e.g., "123456789") |
endkey |
String | End keys configuration |
spacestyle |
String | Space key behavior |
Key Methods:
// Factory method from database cursor
static Im get(Cursor cursor)
// Safe cursor parsing helpers
static String getCursorString(Cursor cursor, String column)
static int getCursorInt(Cursor cursor, String column)
// Standard getters/setters
String getCode()
String getDesc()
boolean getDisable()Example Usage:
// Load available IMs for setup
List<Im> imConfigList = searchServer.getImList(null, LIME.IM_FULL_NAME);
for (Im imConfig : imConfigList) {
// Display: "嘸蝦米 (boshiamy)"
String display = imConfig.getDesc() + " (" + imConfig.getCode() + ")";
}
// Configure IM keyboard
searchServer.setIMKeyboard(imConfig.getCode(), "bpmf");Database Schema:
CREATE TABLE imConfig (
id INTEGER PRIMARY KEY,
code TEXT,
title TEXT,
desc TEXT,
keyboard TEXT,
disable INTEGER,
selkey TEXT,
endkey TEXT,
spacestyle TEXT
)Location: org.limeime.data.Keyboard
Purpose: Represents keyboard layout configuration with support for different types and shift variants
Fields:
| Field | Type | Description |
|---|---|---|
id, code, name |
String | Identity fields |
desc |
String | Description |
type |
String | Keyboard type |
image |
String | Image resource reference |
imkb, imshiftkb |
String | IM keyboard and shift variant |
engkb, engshiftkb |
String | English keyboard variants |
symbolkb, symbolshiftkb |
String | Symbol keyboard variants |
defaultkb, defaultshiftkb |
String | Default keyboard variants |
extendedkb, extendedshiftkb |
String | Extended keyboard variants |
disable |
boolean | Enable/disable flag |
Key Methods:
// Get keyboard with optional number row
String getEngkb()
String getEngkb(boolean showNumberRow)
// Standard getters/setters
String getCode()
String getImkb()
String getImshiftkb()Example Usage:
// Load keyboards for selection
List<Keyboard> keyboards = searchServer.getKeyboardList();
for (Keyboard kb : keyboards) {
// Display: "注音 (bpmf)"
String display = kb.getName() + " (" + kb.getCode() + ")";
}
// Assign keyboard to IM
searchServer.setIMKeyboard(imCode, keyboard.getCode());Location: org.limeime.data.ImKeyboardConfig
Purpose: Lightweight DTO containing only IM code and keyboard assignment
Fields:
| Field | Type | Description |
|---|---|---|
code |
String | IM code |
keyboard |
String | Keyboard assignment |
Key Methods:
String getCode()
void setCode(String code)
String getKeyboard()
void setKeyboard(String keyboard)Example Usage:
// Simplified IM configuration transfer
ImKeyboardConfig config = new ImKeyboardConfig();
config.setCode("phonetic");
config.setKeyboard("bpmf");
// Used in lightweight contexts where full Im object is unnecessaryLocation: org.limeime.ui.view.NavigationMenuItem
Purpose: Wraps an Im object with navigation menu context
Fields:
| Field | Type | Description |
|---|---|---|
imConfig |
ImConfig | The ImConfig object (or null for fixed items) |
position |
int | Position in navigation menu |
isSelected |
boolean | Selection state |
Key Methods:
ImConfig getImConfig()
int getPosition()
boolean isSelected()Example Usage:
// In SetupImController
List<NavigationMenuItem> items = new ArrayList<>();
// Fixed items (imConfig = null)
items.add(new NavigationMenuItem(null, 0, false)); // Initial
items.add(new NavigationMenuItem(null, 1, false)); // Related
// IM items
for (int i = 0; i < imConfigList.size(); i++) {
ImConfig imConfig = imConfigList.get(i);
items.add(new NavigationMenuItem(imConfig, i + 2, false));
}Position Convention:
- Position 0: SetupImFragment (Initial)
- Position 1: ManageRelatedFragment (Related)
- Position 2+: ManageImFragment for specific IM tables
Alternative Definition:
There's also a nested class in NavigationManager with a slightly different structure:
public static class NavigationMenuItem {
private final String code;
private final String title;
private final int iconResId;
}This nested class is used internally by NavigationManager for rendering menu items.
Location: org.limeime.data.ChineseSymbol
Purpose: Utility class for Chinese punctuation symbol conversion
Static Data:
private static String chineseSymbols = "。「」,『』、:;!~?";Key Methods:
// Convert English punctuation to Chinese
static char getSymbol(char symbol)
// Example: getSymbol('.') → '。'
// Example: getSymbol(',') → ','
// Get complete list of Chinese symbols as Mapping objects
static List<Mapping> getChineseSymoblList()Conversion Mapping:
| English | Chinese | Description |
|---|---|---|
. |
。 |
Period |
" |
「」 |
Quotation marks |
, |
, |
Comma |
' |
『』 |
Single quotes |
/ |
、 |
Enumeration comma |
: |
: |
Colon |
; |
; |
Semicolon |
! |
! |
Exclamation |
~ |
~ |
Tilde |
? |
? |
Question mark |
Example Usage:
// Convert user input to Chinese punctuation
char englishPunct = '.';
char chinesePunct = ChineseSymbol.getSymbol(englishPunct);
// Result: '。'
// Display Chinese punctuation candidates
List<Mapping> symbols = ChineseSymbol.getChineseSymoblList();
// Each Mapping has recordType = RECORD_CHINESE_PUNCTUATION_SYMBOL// Input: User types "ㄋㄧ"
String code = "ㄋㄧ";
// Query database via SearchServer
List<Mapping> candidates = searchServer.getMappingByCode(code, softKeyboard, false);
// Process candidates by record type
for (Mapping mapping : candidates) {
if (mapping.isExactMatchToCodeRecord()) {
// Exact match: "你", "尼", "呢" (sorted by score)
candidateView.addCandidate(mapping);
} else if (mapping.isRelatedPhraseRecord()) {
// Related phrase: "你好"
candidateView.addRelatedPhrase(mapping);
}
}// User selects candidate
Mapping selected = candidates.get(selectedIndex);
// Update score (asynchronous)
searchServer.learnRelatedPhraseAndUpdateScore(selected);
// At session end, learn related phrases
List<Mapping> scorelist = getSessionScorelist();
searchServer.learnRelatedPhrase(scorelist);
// If qualified, learn LD phrases
searchServer.learnLDPhrase(LDPhraseListArray);// Load available IMs
List<ImConfig> imConfigList = searchServer.getAllImKeyboardConfig();
// Load available keyboards
List<Keyboard> keyboards = searchServer.getKeyboardList();
// User selects IM and keyboard
ImConfig selectedImConfig = imConfigList.get(userChoice);
Keyboard selectedKb = keyboards.get(userChoice);
// Save configuration
searchServer.setIMKeyboard(selectedImConfig.getCode(), selectedKb.getCode());// Build navigation menu
List<NavigationMenuItem> menuItems = new ArrayList<>();
// Add fixed items
menuItems.add(new NavigationMenuItem(null, 0, false)); // Setup
menuItems.add(new NavigationMenuItem(null, 1, false)); // Related
// Add IM items
List<ImConfig> imConfigList = searchServer.getAllImKeyboardConfig();
for (int i = 0; i < imConfigList.size(); i++) {
menuItems.add(new NavigationMenuItem(imConfigList.get(i), i + 2, false));
}
// User selects item
NavigationMenuItem selected = menuItems.get(position);
if (selected.getIm() != null) {
// Navigate to ManageImFragment for specific IM
String tableName = selected.getIm().getCode();
navigateToManageIm(tableName);
}// In ManageImFragment - search records
List<Record> records = searchServer.getRecordsByCode(searchCode);
// Display records in RecyclerView
for (Record record : records) {
String display = String.format("%s → %s (score: %d)",
record.getCode(),
record.getWord(),
record.getScore());
adapter.addItem(display);
}
// User edits record
record.setWord(newWord);
record.setScore(newScore);
searchServer.updateRecord(record);// In ManageRelatedFragment - load related phrases
List<Related> phrases = searchServer.getRelatedPhrase(parentWord);
// Display parent → child relationships
for (Related phrase : phrases) {
String display = String.format("%s → %s (score: %d)",
phrase.getPword(),
phrase.getCword(),
phrase.getUserscore());
adapter.addItem(display);
}
// User deletes related phrase
searchServer.deleteRelatedPhrase(phrase.getPword(), phrase.getCword());DTOs contain no business logic - they are pure data containers:
- ✅ Getters and setters
- ✅ Record type checkers (simple boolean methods)
- ✅ Copy constructors
- ❌ Database operations (belong in LimeDB)
- ❌ Search logic (belongs in SearchServer)
- ❌ UI rendering (belongs in Views)
Record and Related extend Mapping to provide:
- Semantic distinction: Different terminology in different UI contexts
- Code reusability: Same underlying data structure
- Type safety: Method signatures can specify
RecordvsRelatedvsMapping
NavigationMenuItem wraps Im rather than extending it:
- Adds navigation-specific metadata (position, selection state)
- Maintains clear separation between domain model (Im) and UI model (NavigationMenuItem)
- Allows
imConfigto be null for fixed menu items
ImKeyboardConfig demonstrates the DTO pattern for specific use cases:
- Contains only essential fields needed for keyboard assignment
- Avoids transferring unnecessary data
- Used in contexts where full
Imobject is overkill
Im.get(Cursor cursor) pattern:
- Centralizes cursor parsing logic
- Provides safe extraction with null checks
- Reduces boilerplate in database layer
codeandwordshould not be null for most record typesscore≥ 0 (negative scores not allowed)recordTypemust be one of the defined constants (1-12)
codemust be unique (database primary key)selkeytypically "123456789" or customdisablecontrols whether IM appears in selection UI
codemust be unique- At least one keyboard variant (
imkb,engkb, etc.) should be defined typedetermines which keyboard variant is used
pword(parent) andcword(child) must not be null- Score accumulates with repeated phrase usage
- Bi-gram model: only consecutive word pairs are learned
- Phrase length: 2-4 characters only (enforced in learning logic)
- Cannot contain English suggestions (code.equals(word))
- Requires related phrase score > 20 to trigger creation
- Quick Phrase code built from first character of each unit
- Clear data contracts: DTOs define the shape of data transferred between layers
- Type safety: Compiler catches mistakes in data access
- Semantic clarity: Different DTO types (Mapping/Record/Related) clarify intent
- No database code: DTOs remain pure - database operations in LimeDB
- Reusability: Same DTOs used across IME, UI, and database layers
- Testability: DTOs are simple POJOs, easy to construct in tests
✅ DO:
- Use
Mappingfor IME candidates and general database records - Use
Recordin Setup/Manage IM UI contexts - Use
Relatedin Related Phrase management - Use
ImandKeyboardfor configuration - Use
NavigationMenuItemfor navigation state
❌ DON'T:
- Add database operations to DTOs
- Add business logic to DTOs (belongs in SearchServer/LimeDB)
- Create unnecessary DTO variants (prefer reuse)
The query path represents the flow from user input to candidate word display. This is the hot path for IME performance.
User presses key on soft keyboard
└─> LIMEService.onKey(primaryCode, keyCodes)
├─> Update composing text (append key code)
├─> Call updateCandidates()
│ └─> SearchServer.getMappingByCode(code, softKeyboard, getAllRecords)
│ ├─> [Check mapping cache]
│ │ └─> Key: tablename + keyboardType + code
│ ├─> [If cache miss]
│ │ └─> LimeDB.getMappingByCode(code, tablename, ...)
│ │ ├─> Apply remapping rules
│ │ ├─> Apply dual-code expansion
│ │ ├─> Apply blacklist filtering
│ │ └─> Execute SQL query: SELECT * FROM {table} WHERE code=? ORDER BY score DESC
│ ├─> [Cache result]
│ └─> Return List<Mapping>
└─> Display candidates in CandidateView
├─> Sort by score (learned entries first)
├─> Show related phrases (if available)
└─> Enable candidate selection
Key Characteristics:
- Cached: Second query with same code uses cache (faster)
- Sorted by score: Learned words (score > 0) appear first
- Blacklist filtering: Removes unwanted candidates based on configuration
- Dual-code expansion: Expands compatible input codes (e.g., bopomofo variants)
User presses hardware key
└─> LIMEService.onKeyDown(keyCode, event)
├─> translateKeyDown(keyCode, event)
│ └─> Convert hardware key to IM code using keyboard layout
├─> Update composing text
├─> Call updateCandidates()
│ └─> [Same as soft keyboard flow above]
└─> Display candidates
User releases hardware key
└─> LIMEService.onKeyUp(keyCode, event)
└─> Finalize key input (handle shift/meta states)
Special Key Handling:
- Enter: Commit composing text
- Backspace: Delete last composing character
- Space: Auto-select first candidate or commit
- Arrow keys: Navigate candidate list
- Selection keys (1-9, 0): Directly pick candidate by number
Cache Strategy:
- First query (cold): ~10-50ms (database query)
- Subsequent queries (warm): ~1-5ms (cache hit)
- Cache key:
tablename + keyboardType + code - Cache invalidated on: table change, database update
Database Optimization:
- Index on
codecolumn for fast lookup - Sort by
score DESCto prioritize learned words - Parameterized queries to prevent SQL injection
The learning path represents how user selections are learned and stored in the database. This improves future candidate suggestions.
User selects candidate from list
└─> LIMEService.pickCandidateManually(index)
├─> Get selected Mapping from candidate list
├─> Commit text to InputConnection
│ └─> ic.commitText(mapping.word, 1)
├─> Add to learning scorelist
│ └─> scorelist.add(mapping)
└─> Update score (asynchronous)
└─> SearchServer.learnRelatedPhraseAndUpdateScore(mapping)
├─> Add mapping to internal scorelist
└─> Spawn background thread: updateScoreCache()
└─> For each mapping in cache queue:
├─> Get current score from database
├─> Increment score by 1
└─> Update database: UPDATE {table} SET score=? WHERE id=?
Key Characteristics:
- Asynchronous: Score updates happen in background thread (non-blocking)
- Incremental: Each selection increments score by 1
- Persistent: Scores stored in database, survive IME restart
- Cache-aware: Updated scores affect future queries (higher ranking)
User completes input session (types multiple words)
└─> LIMEService.postFinishInput()
├─> Check preference: LIMEPref.getLearnRelatedWord()
├─> [If enabled]
│ └─> SearchServer.learnRelatedPhrase(scorelist)
│ └─> For each consecutive pair (word1, word2) in scorelist:
│ ├─> Skip if word1 or word2 is null/empty
│ ├─> Skip if word2 is composing code or English suggestion
│ ├─> Get unit1 mapping by reverse lookup (if needed)
│ ├─> Get unit2 mapping by reverse lookup (if needed)
│ ├─> Check if related phrase already exists
│ ├─> [If exists]: Increment score
│ │ └─> LimeDB.updateRelatedPhraseScore(unit1.word, unit2.word, score+1)
│ └─> [If new]: Create related phrase record
│ └─> LimeDB.insertRelatedPhrase(unit1.word, unit2.word, score=1)
└─> Clear scorelist for next session
Example:
User types: "你好嗎" (3 words)
Selections: "你" → "好" → "嗎"
Related phrases learned:
- "你" → "好" (score=1)
- "好" → "嗎" (score=1)
Next time user types "你", system suggests "好" as related phrase
Key Characteristics:
- Session-based: Learns from consecutive word selections in same session
- Bi-gram model: Learns pairs of consecutive words
- Preference-controlled: Can be disabled via
LIMEPref.setLearnRelatedWord(false) - Score accumulation: Repeated phrases get higher scores
User completes input session with high-score related phrases
└─> LIMEService.postFinishInput()
├─> Check preference: LIMEPref.getLearnPhrase()
├─> [If enabled and related phrase score > 20]
│ └─> For each high-score related phrase:
│ └─> SearchServer.addLDPhrase(unit, isLastUnit)
│ └─> Add to LDPhraseListArray buffer
└─> [At session end]
└─> SearchServer.learnLDPhrase(LDPhraseListArray)
├─> Validate phrase (2-4 characters only)
├─> Skip if any unit is English (code.equals(word))
├─> Build phrase from consecutive words
├─> For each unit in phrase:
│ ├─> Get base code (from unit or reverse lookup)
│ ├─> Extract first character of code for QP code
│ └─> Concatenate codes: baseCode += unit.code
├─> Build Quick Phrase (QP) code from first chars
├─> Create LD phrase record
│ └─> LimeDB.insertLDPhrase(
│ code=combinedCode,
│ word=combinedWord,
│ QPcode=firstCharsCode,
│ score=1
│ )
└─> Clear LDPhraseListArray
Example:
User frequently types: "電" (ㄉㄧㄢ) → "腦" (ㄋㄠ)
After 20+ selections, related phrase score > 20
LD phrase created:
- word: "電腦"
- code: "ㄉㄧㄢㄋㄠ" (full combined code)
- QPcode: "ㄉㄋ" (Quick Phrase: first char of each)
- score: 1
User can now type:
- "ㄉㄧㄢㄋㄠ" → suggests "電腦" (full code)
- "ㄉㄋ" → suggests "電腦" (Quick Phrase code)
Key Characteristics:
- Threshold-based: Only activates when related phrase score > 20
- Phrase length limit: 2-4 characters (enforced)
- Quick Phrase (QP) code: Shortcut using first character of each word
- Two access methods: Full code or QP code
- Preference-controlled: Can be disabled via
LIMEPref.setLearnPhrase(false) - Requires related learning: LD phrases built from related phrase data
┌─────────────────────────────────────────────────────────────┐
│ User Input Session (Complete Flow) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ User types: │
│ "你好嗎" │
│ (3 selections) │
└─────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
Selection 1 Selection 2 Selection 3
"你" (ㄋㄧ) "好" (ㄏㄠ) "嗎" (ㄇㄚ)
│ │ │
└─────────────────────┴─────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ Immediate: Score Update (Background) │
│ learnRelatedPhraseAndUpdateScore() │
│ - "你" score: 100 → 101 │
│ - "好" score: 85 → 86 │
│ - "嗎" score: 50 → 51 │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ Session End: Related Phrase Learning │
│ learnRelatedPhrase(scorelist) │
│ [If LearnRelatedWord = true] │
│ - Create: "你" → "好" (score=1) │
│ - Create: "好" → "嗎" (score=1) │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ Conditional: LD Phrase Learning │
│ learnLDPhrase(LDPhraseListArray) │
│ [If LearnPhrase = true AND score > 20] │
│ - Create: "你好" (QPcode="ㄋㄏ") │
│ - Create: "好嗎" (QPcode="ㄏㄇ") │
└────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ Database State │
│ Updated │
└─────────────────┘
Learning Preferences:
| Preference | Effect |
|---|---|
LearnRelatedWord = true |
Creates related phrase records (bi-grams) |
LearnRelatedWord = false |
Only score updates, no related phrases |
LearnPhrase = true |
Creates LD phrases from high-score related phrases (requires LearnRelatedWord) |
LearnPhrase = false |
No LD phrases created |
| Both disabled | Only score updates via learnRelatedPhraseAndUpdateScore() |
| Both enabled | Full learning chain: score → related → LD |
┌─────────────────────────────────────────────────────────────┐
│ Android Application │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌─────────────────┐
│ LIMEService │ │ UI Components │ │ DBServer │
│ (IME Layer) │ │ (UI Layer) │ │ (File Ops) │
└──────────────┘ └──────────────┘ └─────────────────┘
│ │ │
│ │ │
└─────────────────────┼──────────────────────┘
│
▼
┌─────────────────┐
│ SearchServer │
│ (DB Interface) │
└─────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ LimeDB │ │ LimeHanConverter│ │ EmojiConverter │
│ (SQL Layer) │ │ (hanconvertv2.db)│ │ (emoji.db) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ SQLiteDatabase │ │ SQLiteDatabase │ │ SQLiteDatabase │
│ (Main DB) │ │ (hanconvertv2.db)│ │ (emoji.db) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
User types key
└─> LIMEService.onKeyEvent()
└─> SearchServer.getMappingByCode()
├─> [Check cache]
├─> [If cache miss]
│ └─> LimeDB.getMappingByCode()
│ └─> SQLiteDatabase.rawQuery()
└─> [Cache result and return]
└─> LIMEService displays candidates
User selects candidate
└─> LIMEService.pickCandidateManually()
├─> Commit text to InputConnection
├─> SearchServer.learnRelatedPhraseAndUpdateScore()
│ └─> [Background thread] Update score in database
└─> [Session end] SearchServer.learnRelatedPhrase()
├─> Create related phrase records
└─> [If qualified] SearchServer.learnLDPhrase()
└─> Create LD phrase records with QP codes
User changes IM keyboard in UI
└─> ManageImKeyboardDialog.onItemClick()
└─> SearchServer.setIMKeyboard()
└─> LimeDB.setIMKeyboard()
└─> SQLiteDatabase.update()
User imports .limedb file
└─> MainActivity.performLimedbImport()
└─> DBServer.importZippedDb() / DBServer.importZippedDbRelated()
├─> LIMEUtilities.unzip()
├─> LimeDB.importDb() / LimeDB.importDbRelated()
│ ├─> SQLiteDatabase.execSQL("attach database...")
│ ├─> SQLiteDatabase.execSQL("insert into...")
│ └─> SQLiteDatabase.execSQL("detach database...")
└─> SearchServer.resetCache()
└─> [Clear all caches]
User imports text file (.lime, .cin)
└─> DBServer.importTxtTable()
└─> LimeDB.importTxtTable()
├─> [Read file line by line]
├─> [Parse delimiter and fields]
├─> [Insert records in transaction]
└─> [Update IM information]
User exports database
└─> ShareDialog (UI)
└─> DBServer.exportZippedDb() / DBServer.exportZippedDbRelated()
├─> LIMEUtilities.copyRAWFile() [blank template]
├─> LimeDB.prepareBackup()
│ ├─> SQLiteDatabase.execSQL("attach database...")
│ ├─> SQLiteDatabase.execSQL("insert into sourceDB.custom...")
│ └─> SQLiteDatabase.execSQL("detach database...")
├─> LIMEUtilities.zip()
└─> [Save to user-selected location]
User exports text file
└─> ShareTxtRunnable / ShareRelatedTxtRunnable
└─> SearchServer.exportTxtTable()
└─> LimeDB.exportTxtTable()
├─> [Query all records]
├─> [Write IM info header]
└─> [Write records to file]
User reloads mapping file
└─> SetupImLoadDialog
├─> SearchServer.backupUserRecords()
│ └─> LimeDB.backupUserRecords()
│ └─> SQLiteDatabase.execSQL("create table {table}_user as...")
├─> [Import new mapping file]
└─> SearchServer.restoreUserRecords()
└─> LimeDB.restoreUserRecords()
├─> LimeDB.getBackupTableRecords()
└─> SQLiteDatabase.insert() [restore user records]
User triggers full backup
└─> SetupImFragment (UI)
└─> DBServer.backupDatabase()
├─> LimeDB.holdDBConnection()
├─> LIMEUtilities.zip() [database files]
├─> DBServer.backupDefaultSharedPreference()
└─> [Save to user-selected location]
User triggers restore
└─> DBServer.restoreDatabase()
├─> LIMEUtilities.unzip()
├─> [Restore database files]
└─> DBServer.restoreDefaultSharedPreference()
User requests Traditional/Simplified conversion
└─> LIMEService or UI Component
└─> SearchServer.hanConvert()
└─> LimeHanConverter.convert() [via LimeDB or directly]
└─> SQLiteDatabase (hanconvertv2.db)
Note: SearchServer can access LimeHanConverter directly or through LimeDB. The converter manages its own separate database.
User requests emoji suggestions
└─> LIMEService or UI Component
└─> SearchServer.emojiConvert()
├─> [Check emoji cache]
├─> [If cache miss]
│ └─> EmojiConverter.convert() [via LimeDB or directly]
│ └─> SQLiteDatabase (emoji.db)
└─> [Cache result and return]
Note: SearchServer can access EmojiConverter directly or through LimeDB. The converter manages its own separate database.
| Component | Primary Responsibility | Key Operations | Dependencies |
|---|---|---|---|
| LIMEService | IME functionality | Handle input, display candidates, learning flow | SearchServer, LIMEPreferenceManager |
| SearchServer | Database interface | Search, query, configuration, conversions, learning | LimeDB, LIMEPreferenceManager |
| DBServer | File operations | Import, export, backup, restore | LimeDB, LIMEUtilities, SearchServer |
| LimeDB | SQL operations | All database queries, imports, exports, backups, restores | SQLiteDatabase (main), LimeHanConverter, EmojiConverter |
| LimeHanConverter | Chinese conversion | Traditional/Simplified conversion | SQLiteDatabase (hanconvertv2.db) |
| EmojiConverter | Emoji conversion | Emoji suggestions | SQLiteDatabase (emoji.db) |
| UI Components | User interface | Display, configuration, management | SearchServer, DBServer |
// LIMEService accessing database
SearchServer searchSrv = new SearchServer(this);
List<Mapping> results = searchSrv.getMappingByCode(code, softKeyboard, getAllRecords);
// UI Component accessing database
SearchServer searchServer = new SearchServer(getActivity());
List<Im> imConfigList = searchServer.getIm(null, LIME.IM_TYPE_NAME);
// UI Component accessing file operations
DBServer dbServer = DBServer.getInstance(activity);
dbServer.importZippedDb(file, tableName);
dbServer.importZippedDbRelated(file);
// LIMEService or UI Component accessing conversions
SearchServer searchServer = new SearchServer(context);
String converted = searchServer.hanConvert(input); // Chinese conversion
List<Mapping> emojis = searchServer.emojiConvert(code, type); // Emoji conversion// ❌ Direct LimeDB access from UI
LimeDB datasource = new LimeDB(getActivity());
List<Im> imConfigList = datasource.getIm(null, LIME.IM_TYPE_NAME); // WRONG
// ❌ Direct LimeDB access from LIMEService
LimeDB db = new LimeDB(this);
List<Mapping> results = db.getMappingByCode(...); // WRONGWhy avoid direct access:
- Bypasses caching in SearchServer
- Inconsistent access patterns
- Harder to add validation/transformation
- Duplicates database connection management
Static State (Shared across instances):
private static String tablename = ""; // Current active IM table
private static boolean hasNumberMapping; // IM supports number keys
private static boolean hasSymbolMapping; // IM supports symbol keys
private static LimeDB dbadapter = null; // Shared LimeDB instanceInstance State:
private final LIMEPreferenceManager mLIMEPref; // Preferences
private final Context mContext; // Application contextCache State:
private static ConcurrentHashMap<String, List<Mapping>> cache; // Mapping cache
private static ConcurrentHashMap<String, List<Mapping>> engcache; // English cache
private static ConcurrentHashMap<String, List<Mapping>> emojicache; // Emoji cache
private static ConcurrentHashMap<String, String> keynamecache; // Keyname cache
private final HashMap<String, String> selKeyMap; // Selkey cacheLearning State:
private ArrayList<Mapping> scorelist; // Session learning scorelist
private ArrayList<Mapping> LDPhraseListArray; // LD phrase bufferSingleton State:
private static volatile DBServer instance = null; // Singleton instance
private final Context appContext; // Application context
protected LimeDB datasource = null; // Shared LimeDB instance// Created per-instance (not singleton)
SearchServer searchServer = new SearchServer(context);
├─> Creates/gets shared LimeDB instance
├─> Initializes LIMEPreferenceManager
└─> Initializes caches
// Used throughout component lifetime
searchServer.getMappingByCode(...);
searchServer.setTablename(...);
// Caches persist until reset
SearchServer.resetCache(true); // Clears all caches// Singleton - get instance
DBServer dbServer = DBServer.getInstance(context);
├─> Creates singleton instance (first call only)
├─> Creates/gets shared LimeDB instance
└─> Stores ApplicationContext
// Used for file operations
dbServer.importZippedDb(...);
dbServer.importZippedDbRelated(...);
dbServer.exportZippedDb(...);
// Instance persists for app lifetime// Created by Android system
LIMEService.onCreate()
└─> new SearchServer(this)
└─> [SearchServer initialized]
// Used during IME operation
LIMEService.onKeyEvent()
└─> searchSrv.getMappingByCode(...)
// Learning at session end
LIMEService.postFinishInput()
├─> searchSrv.learnRelatedPhrase(scorelist)
└─> searchSrv.learnLDPhrase(LDPhraseListArray)
// Destroyed by Android system
LIMEService.onDestroy()
└─> [SearchServer instance can be garbage collected]Cache Types:
-
Mapping Cache - Caches
getMappingByCode()results- Key:
tablename + keyboardType + code - Invalidated when: Table changes, database updated
- Key:
-
Emoji Cache - Caches
emojiConvert()results- Key:
code + type - Persists until reset
- Key:
-
Keyname Cache - Caches
keyToKeyname()results- Key:
tablename + keyboardType + code - Persists until reset
- Key:
-
Selkey Cache - Caches
getSelkey()results- Key:
tablename - Persists until reset
- Key:
Cache Invalidation:
// When database is updated
DBServer.importTxtTable()
└─> SearchServer.resetCache(true)
└─> [All caches cleared]
// When IM table changes
SearchServer.setTablename()
└─> [Cache keys will use new tablename]Thread Safety Considerations:
- Static caches use
ConcurrentHashMap(thread-safe) - Static state (
tablename, flags) - potential race conditions - Instance methods - not thread-safe (create per-thread if needed)
Current Usage:
LIMEServicecreates one instance (main thread)- UI components create instances as needed (main thread)
- Search operations may be called from background threads
Recommendation:
- Most operations are called from main/UI thread
- If background threads are used, ensure proper synchronization
Thread Safety:
- Singleton uses double-checked locking (thread-safe)
- File operations should be on background threads
- Database operations coordinate with LimeDB connection management
// Methods throw RemoteException for compatibility
public List<Mapping> getMappingByCode(...) throws RemoteException {
try {
// ... operation
} catch (Exception e) {
Log.e(TAG, "Error in search operation", e);
return null; // or empty list
}
}// Methods return boolean/null for error indication
public void importZippedDb(File file, String tableName) {
try {
// ... operation
} catch (Exception e) {
Log.e(TAG, "Error importing file", e);
return false;
}
}✅ Already Using SearchServer:
LIMEService- All operations through SearchServer- Some UI components - Partial usage
❌ Still Using Direct LimeDB:
SetupImFragment- Usesdatasource.getIm()ManageImFragment- Usesdatasource.getIm(),datasource.getKeyboard()ManageImKeyboardDialog- Usesdatasource.getKeyboard(),datasource.setImKeyboard()ImportDialog- Usesdatasource.getIm()ShareDialog- Usesdatasource.getIm()SetupImLoadRunnable- Usesdatasource.setImInfo(),datasource.setIMKeyboard()
All components should use:
SearchServerfor all database operationsDBServerfor all file operations- No direct
LimeDBaccess
// ✅ Correct
SearchServer searchServer = new SearchServer(context);
List<Im> imConfigList = searchServer.getIm(null, LIME.IM_TYPE_NAME);
// ❌ Incorrect
LimeDB datasource = new LimeDB(context);
List<Im> imConfigList = datasource.getIm(null, LIME.IM_TYPE_NAME);// ✅ Correct
DBServer dbServer = DBServer.getInstance(context);
dbServer.importZippedDb(file, tableName);
dbServer.importZippedDbRelated(file);
// ❌ Incorrect
// Don't implement file operations in UI components// ✅ Correct - Each component creates its own instance
public class SetupImFragment extends Fragment {
private SearchServer searchServer;
public void onCreateView(...) {
searchServer = new SearchServer(getActivity());
}
}// ✅ Correct - Use singleton
DBServer dbServer = DBServer.getInstance(context);
// ❌ Incorrect - Don't create new instances
DBServer dbServer = new DBServer(context); // Constructor is private- Presentation Layer: UI Components, LIMEService
- Service Layer: SearchServer (database), DBServer (files)
- Data Access Layer: LimeDB (SQL operations)
- Storage Layer:
- SQLiteDatabase (main database)
- LimeHanConverter → SQLiteDatabase (hanconvertv2.db)
- EmojiConverter → SQLiteDatabase (emoji.db)
- SearchServer is the single interface for all database operations
- DBServer handles all file operations
- No direct LimeDB access from UI or LIMEService
- Clear separation of concerns between components
- Centralized caching in SearchServer
- Consistent access patterns across all components
- Learning flow enhances user experience through score updates, related phrases, and LD phrases
- ✅ Single interface for database operations
- ✅ Centralized caching and performance optimization
- ✅ Consistent access patterns
- ✅ Easier to add validation/transformation
- ✅ Better maintainability
- ✅ Clear component responsibilities
- ✅ Adaptive learning improves suggestion accuracy over time
- ✅ Multi-level learning (score, related, LD) provides comprehensive personalization
This architecture provides a clean, maintainable, and scalable foundation for the LimeIME application, with intelligent learning capabilities that adapt to user behavior.