- Tổng Quan
- Tính Năng Chính
- Kiến Trúc Hệ Thống
- Công Nghệ Sử Dụng
- Cấu Trúc Dự Án
- Hướng Dẫn Cài Đặt
- Hướng Dẫn Sử Dụng
- Cấu Trúc Database
- API & Services
- Bảo Mật
- Testing
- Troubleshooting
TimeMaster là ứng dụng Android quản lý điểm danh thông minh sử dụng công nghệ nhận diện khuôn mặt AI (Face Recognition). Ứng dụng hỗ trợ hai vai trò chính: User (nhân viên) và Admin (quản lý), giúp tự động hóa quy trình điểm danh và theo dõi giờ làm việc.
- Tự động hóa quy trình điểm danh bằng AI
- Giảm thiểu gian lận và điểm danh thay
- Theo dõi thời gian làm việc chính xác
- Quản lý nhân viên hiệu quả
- Tạo báo cáo thống kê trực quan
- Điểm danh bằng khuôn mặt
- Xem trạng thái điểm danh hôm nay
- Theo dõi lịch sử làm việc theo tuần/tháng
- Xem thống kê giờ làm việc
- Quản lý thông tin cá nhân
- Quản lý danh sách nhân viên
- Xem báo cáo điểm danh tổng thể
- Phê duyệt yêu cầu đăng ký
- Thống kê theo nhân viên/phòng ban
- Xuất báo cáo chi tiết
- Email/Password: Đăng ký truyền thống với Firebase Authentication
- Đăng ký khuôn mặt: Quét và lưu embedding khuôn mặt vào Firestore
- Phê duyệt: Tài khoản mới cần admin phê duyệt trước khi kích hoạt
- Email/Password: Đăng nhập thông thường
- Google Sign-In: Đăng nhập nhanh bằng tài khoản Google
- Face Recognition: Đăng nhập bằng nhận diện khuôn mặt (dành cho user đã đăng ký)
- Gửi email reset password qua Firebase Auth
- Link reset có thời hạn
- Hiển thị:
fullName,email,role,avatar - Chỉnh sửa profile: cập nhật tên, ảnh đại diện
- Đồng bộ với Firebase Firestore
- Danh sách nhân viên với tìm kiếm, lọc
- Xem chi tiết thông tin nhân viên
- Chỉnh sửa/Xóa nhân viên
- Phê duyệt yêu cầu đăng ký mới
- ML Kit Face Detection: Phát hiện khuôn mặt trong ảnh/video
- MobileFaceNet (TensorFlow Lite): Trích xuất embedding vector (512 chiều)
- L2 Distance: So sánh độ tương đồng giữa các embedding
- Threshold: 1.0 (điều chỉnh được)
1. Camera Capture (CameraX)
↓
2. ML Kit Face Detection (phát hiện khuôn mặt)
↓
3. Crop Face Region (cắt vùng khuôn mặt)
↓
4. Resize to 160x160 (chuẩn hóa)
↓
5. MobileFaceNet Model (trích xuất embedding)
↓
6. Compare with Firestore Embeddings (L2 distance)
↓
7. Match Found? → Điểm danh thành công
- Real-time Detection: Phát hiện liên tục từ camera
- Face Tracking: Theo dõi khuôn mặt di chuyển
- Mirror Correction: Tự động xử lý ảnh gương
- Multi-face Detection: Phát hiện nhiều khuôn mặt (chỉ xử lý face lớn nhất)
// Logic điểm danh chẵn/lẻ
Lần 1 (lẻ) → CHECK_IN
Lần 2 (chẵn) → CHECK_OUT
Lần 3 (lẻ) → CHECK_IN (lại)
Lần 4 (chẵn) → CHECK_OUT
...| Trạng thái | Điều kiện | Màu sắc | Icon |
|---|---|---|---|
| Đúng giờ | Check-in trước 8:00 AM | 🟢 Xanh lá (#059669) | ✓ |
| Đi muộn | Check-in từ 8:01 - 16:59 | 🟡 Vàng cam (#D97706) | ✓ |
| Vắng mặt | Check-in sau 17:00 hoặc không check-in | 🔴 Đỏ (#DC2626) | ✗ |
| Chưa điểm danh | Chưa check-in (trước 17:00) | ⚪ Xám (#6B7280) | ⏰ |
Collection: attendanceRecords
{
"uid": "userId", // ID người dùng
"userEmail": "user@example.com", // Email
"timestamp": 1701234567890, // Milliseconds
"date": "2025-11-30", // yyyy-MM-dd
"time": "08:30:00", // HH:mm:ss
"checkType": "CHECK_IN", // CHECK_IN | CHECK_OUT
"method": "FACE_RECOGNITION", // Phương thức điểm danh
"confidence": 0.85, // Độ tin cậy (L2 distance)
"status": "PRESENT", // PRESENT | ABSENT
"deviceInfo": "Pixel 6" // Thông tin thiết bị
}- Real-time Status: Hiển thị trạng thái điểm danh hôm nay
- Check-in/Check-out Time: Hiển thị giờ vào/ra mới nhất
- Total Hours: Tự động tính tổng giờ làm (check-out - check-in)
- Progress Bar: Hiển thị tiến độ làm việc (8 giờ = 100%)
- Auto Update: Cập nhật real-time qua Firestore listener
- Trạng thái hôm nay: Đúng giờ/Muộn/Vắng
- Thời gian làm việc: Giờ vào, giờ ra, tổng giờ
- Lời chào: Theo thời gian trong ngày
- Ngày tháng: Định dạng tiếng Việt
- Biểu đồ cột: Số giờ làm việc mỗi ngày
- Lịch sử điểm danh: Danh sách chi tiết theo ngày
- Tổng kết: Tổng giờ làm, số ngày đi làm, tỷ lệ đúng giờ
- Tổng số nhân viên: Tổng số user trong hệ thống
- Điểm danh hôm nay: Số người đã/chưa điểm danh
- Tỷ lệ đúng giờ: Phần trăm nhân viên đến đúng giờ
- Top nhân viên: Xếp hạng theo giờ làm
- Theo nhân viên: Lịch sử từng người
- Theo ngày: Danh sách điểm danh trong ngày
- Biểu đồ: Xu hướng theo thời gian
- Export: Xuất báo cáo Excel/PDF (tính năng mở rộng)
- Xem/Chỉnh sửa profile
- Đổi avatar
- Cập nhật email, tên
- Ngôn ngữ: Tiếng Việt/English
- Thông báo: Bật/tắt
- Chế độ tối: Dark/Light mode (future)
- Xem danh sách khuôn mặt đã đăng ký
- Thêm/Xóa khuôn mặt
- Cập nhật embedding mới
- Đổi mật khẩu
- Xem lịch sử đăng nhập
- Đăng xuất khỏi tất cả thiết bị
┌─────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Activity │ │ Fragment │ │ Adapter │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼─────────┘
│ │ │
┌─────────┼──────────────────┼──────────────────┼─────────┐
│ ▼ DOMAIN LAYER ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ ViewModel │ │ Repository │ │ Use Cases │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼─────────┘
│ │ │
┌─────────┼──────────────────┼──────────────────┼─────────┐
│ ▼ DATA LAYER ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Firebase │ │ TensorFlow │ │ Local DB │ │
│ │ Firestore │ │ Lite │ │ (Future) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
View (Activity/Fragment)
↓ observe
ViewModel (LiveData)
↓ call
Repository (Data Source)
↓ fetch
Firebase/Local DB
- Interface: Định nghĩa contract
- Implementation: Firebase/Local implementation
- Dependency Injection: Constructor injection
- LiveData: Quan sát dữ liệu thay đổi
- Firestore Listeners: Real-time updates
- Callbacks: Xử lý async operations
- Activities: Màn hình chính
- Fragments: Module UI nhỏ
- Adapters: RecyclerView adapters
- ViewModels: Quản lý UI state
- Models: Data classes
- Repositories: Data access
- Use Cases: Business logic
- Firebase: Cloud storage
- TensorFlow Lite: ML model
- SharedPreferences: Local cache
| Công nghệ | Phiên bản | Mục đích |
|---|---|---|
| Android SDK | 26+ (Oreo) | Platform |
| Java | 8 | Ngôn ngữ lập trình |
| Gradle | 8.13 | Build tool |
| Material Design | 1.11.0 | UI Components |
| Service | Mục đích |
|---|---|
| Firebase Authentication | Xác thực người dùng |
| Cloud Firestore | NoSQL database |
| Firebase Storage | Lưu trữ file/ảnh |
| Firebase Functions | Serverless backend (future) |
| Firebase Analytics | Phân tích hành vi |
| Library | Phiên bản | Mục đích |
|---|---|---|
| ML Kit Face Detection | 16.1.6 | Phát hiện khuôn mặt |
| TensorFlow Lite | 2.13.0 | Runtime cho ML model |
| TensorFlow Lite GPU | 2.13.0 | GPU acceleration |
| MobileFaceNet | Custom | Face recognition model |
| Library | Phiên bản | Mục đích |
|---|---|---|
| CameraX | 1.3.1 | Camera API modern |
| Glide | 4.16.0 | Image loading/caching |
| CircleImageView | 3.1.0 | Avatar tròn |
| Library | Phiên bản | Mục đích |
|---|---|---|
| MPAndroidChart | 3.1.0 | Biểu đồ thống kê |
| Library | Phiên bản | Mục đích |
|---|---|---|
| JUnit | 4.13.2 | Unit testing |
| Espresso | 3.5.1 | UI testing |
TimeMaster/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/example/timemaster/
│ │ │ │ ├── ui/ # Presentation Layer
│ │ │ │ │ ├── auth/ # Xác thực
│ │ │ │ │ │ ├── login/
│ │ │ │ │ │ │ ├── LoginActivity.java
│ │ │ │ │ │ │ └── FaceRecognitionActivity.java
│ │ │ │ │ │ ├── register/
│ │ │ │ │ │ │ └── RegisterActivity.java
│ │ │ │ │ │ └── forgotpassword/
│ │ │ │ │ │ └── ForgotPassword.java
│ │ │ │ │ │
│ │ │ │ │ ├── dashboard/ # Dashboard chính
│ │ │ │ │ │ ├── DashboardHostActivity.java
│ │ │ │ │ │ ├── DashboardFragment.java
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── user/ # User features
│ │ │ │ │ │ │ └── UserStatusFragment.java
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── admin/ # Admin features
│ │ │ │ │ │ │ ├── ManagementFragment.java
│ │ │ │ │ │ │ ├── EmployeeAdapter.java
│ │ │ │ │ │ │ └── EmployeeDetailActivity.java
│ │ │ │ │ │ │
│ │ │ │ │ │ ├── stats/ # Statistics
│ │ │ │ │ │ │ ├── UserStatsFragment.java
│ │ │ │ │ │ │ ├── AdminStatsFragment.java
│ │ │ │ │ │ │ └── AdminStatsViewModel.java
│ │ │ │ │ │ │
│ │ │ │ │ │ └── settings/ # Settings
│ │ │ │ │ │ └── SettingsFragment.java
│ │ │ │ │ │
│ │ │ │ │ ├── checkin/ # Check-in feature
│ │ │ │ │ │ ├── CheckInActivity.java
│ │ │ │ │ │ ├── TimeViewModel.java
│ │ │ │ │ │ └── TimeViewModelFactory.java
│ │ │ │ │ │
│ │ │ │ │ └── widget/ # Custom widgets
│ │ │ │ │ └── SyncedHorizontalScrollView.java
│ │ │ │ │
│ │ │ │ ├── data/ # Data Layer
│ │ │ │ │ ├── model/ # Data models
│ │ │ │ │ │ ├── Employee.java
│ │ │ │ │ │ ├── CheckIn.java
│ │ │ │ │ │ ├── UserAttendance.java
│ │ │ │ │ │ ├── DayAttendance.java
│ │ │ │ │ │ ├── WeekAttendance.java
│ │ │ │ │ │ ├── FaceEmbedding.java
│ │ │ │ │ │ ├── FaceHistory.java
│ │ │ │ │ │ └── StatusType.java
│ │ │ │ │ │
│ │ │ │ │ └── repository/ # Data access
│ │ │ │ │ ├── AttendanceRepository.java
│ │ │ │ │ ├── AttendanceRepositoryFirestore.java
│ │ │ │ │ ├── UserAttendanceRepository.java
│ │ │ │ │ ├── FaceRecognitionRepository.java
│ │ │ │ │ ├── TimeRepository.java
│ │ │ │ │ └── TimeRepositoryImpl.java
│ │ │ │ │
│ │ │ │ ├── ml/ # Machine Learning
│ │ │ │ │ ├── FaceNetModel.java # TFLite model wrapper
│ │ │ │ │ ├── FaceRecognitionProcessor.java
│ │ │ │ │ ├── FaceRecognitionConstants.java
│ │ │ │ │ ├── SimilarityClassifier.java
│ │ │ │ │ └── EmbeddingsUtils.java
│ │ │ │ │
│ │ │ │ └── utils/ # Utilities
│ │ │ │ └── camerax/ # CameraX helpers
│ │ │ │ ├── FaceAnalyzer.java
│ │ │ │ ├── FaceGraphic.java
│ │ │ │ └── GraphicOverlay.java
│ │ │ │
│ │ │ ├── res/ # Resources
│ │ │ │ ├── layout/ # XML layouts
│ │ │ │ ├── drawable/ # Icons, shapes
│ │ │ │ ├── values/ # Strings, colors, styles
│ │ │ │ ├── menu/ # Navigation menus
│ │ │ │ └── xml/ # Network config, backup
│ │ │ │
│ │ │ ├── assets/ # ML models
│ │ │ │ └── mobile_face_net.tflite # FaceNet model
│ │ │ │
│ │ │ └── AndroidManifest.xml # App manifest
│ │ │
│ │ ├── test/ # Unit tests
│ │ └── androidTest/ # Instrumented tests
│ │
│ ├── build.gradle.kts # App build config
│ └── google-services.json # Firebase config
│
├── gradle/ # Gradle wrapper
├── build.gradle.kts # Project build config
├── settings.gradle.kts # Project settings
├── firestore.rules # Firestore security rules
├── firebase.json # Firebase config
└── DOCUMENTATION.md # This file
- Android Studio: Arctic Fox trở lên (khuyến nghị: Hedgehog 2023.1.1+)
- JDK: Java 8 trở lên
- Gradle: 8.0+
- Android SDK: API 26+ (Android 8.0 Oreo)
- Android: 8.0 (API 26) trở lên
- RAM: Tối thiểu 2GB
- Camera: Front camera (nhận diện khuôn mặt)
- Internet: Kết nối ổn định
git clone https://github.com/your-repo/TimeMaster.git
cd TimeMaster- Truy cập Firebase Console
- Tạo project mới: "TimeMaster"
- Thêm Android app với package name:
com.example.timemaster
- Download
google-services.json - Copy vào
app/folder
Trong Firebase Console, kích hoạt:
- ✅ Authentication: Email/Password, Google Sign-In
- ✅ Cloud Firestore: Database
- ✅ Storage: File storage (optional)
- ✅ Analytics: Tracking
Copy nội dung từ firestore.rules và deploy:
firebase deploy --only firestore:rules- Download model từ: MobileFaceNet TFLite
- Đặt file
mobile_face_net.tflitevàoapp/src/main/assets/
- Open project trong Android Studio
- Sync Gradle:
File > Sync Project with Gradle Files - Build APK:
Build > Build Bundle(s) / APK(s) > Build APK(s) - Run: Click nút
▶️ Run hoặcShift + F10
# Build debug APK
./gradlew assembleDebug
# Install on connected device
./gradlew installDebug
# Build & Install
./gradlew installDebug- Run app lần đầu
- Register tài khoản admin thủ công
- Vào Firebase Console > Firestore
- Tìm document trong
users/{userId} - Sửa field
rolethành"admin"
// Firebase Console > Firestore > Import
// Hoặc sử dụng Firebase CLI
firebase firestore:import ./sample-dataBước 1: Mở ứng dụng → Chọn "Đăng ký"
Bước 2: Điền thông tin
- Họ tên đầy đủ
- Email công ty
- Mật khẩu (tối thiểu 6 ký tự)
- Xác nhận mật khẩu
Bước 3: Đăng ký khuôn mặt
- Cho phép quyền camera
- Đối diện camera, giữ khuôn mặt trong khung
- Đợi hệ thống quét và lưu embedding
- Nhận thông báo "Đăng ký thành công"
Bước 4: Chờ phê duyệt
- Admin sẽ xét duyệt tài khoản
- Nhận email thông báo khi được duyệt
Phương thức 1: Email/Password
- Nhập email và mật khẩu
- Click "Đăng nhập"
Phương thức 2: Google Sign-In
- Click biểu tượng Google
- Chọn tài khoản Google
Phương thức 3: Face Recognition (Khuyến nghị)
- Click "Đăng nhập bằng khuôn mặt"
- Đối diện camera
- Hệ thống tự động nhận diện và đăng nhập
Check-In (Vào làm)
- Mở app khi đến công ty
- Vào màn hình "Trạng thái"
- Click nút "Điểm danh"
- Đối diện camera
- Nhận thông báo "Check-in thành công"
Check-Out (Tan làm)
- Mở app khi về
- Làm tương tự check-in
- Hệ thống tự động nhận biết là check-out (lần chẵn)
Lưu ý:
- ✅ Check-in trước 8:00 AM → Đúng giờ
⚠️ Check-in từ 8:01 - 16:59 → Muộn- ❌ Check-in sau 17:00 → Vắng
Trạng Thái Hôm Nay
- Tab "Trạng thái" hiển thị:
- Thời gian check-in/check-out
- Tổng giờ làm việc
- Trạng thái (Đúng giờ/Muộn/Vắng)
- Progress bar giờ làm
Lịch Sử Làm Việc
- Tab "Thống kê"
- Chọn "Tuần này" hoặc "Tháng này"
- Xem biểu đồ số giờ làm mỗi ngày
- Chi tiết từng ngày: giờ vào/ra, tổng giờ
Chỉnh Sửa Profile
- Tab "Cài đặt"
- Click "Chỉnh sửa hồ sơ"
- Cập nhật: Tên, ảnh đại diện
- Click "Lưu"
Đổi Mật Khẩu
- Tab "Cài đặt"
- "Đổi mật khẩu"
- Nhập mật khẩu cũ và mới
- Xác nhận
Đăng Xuất
- Tab "Cài đặt"
- Click "Đăng xuất"
- Xác nhận
Xem Danh Sách
- Tab "Quản lý"
- Danh sách tất cả nhân viên
- Tìm kiếm theo tên, email
- Lọc theo trạng thái: Đang làm/Nghỉ
Thêm Nhân Viên
- Click nút "+" (hoặc "Thêm")
- Điền thông tin nhân viên
- Gửi email mời tham gia
Xem Chi Tiết
- Click vào nhân viên
- Xem: Thông tin cá nhân, lịch sử điểm danh
- Chỉnh sửa thông tin
- Xóa nhân viên (nếu cần)
Phê Duyệt Đăng Ký
- Vào "Yêu cầu chờ duyệt"
- Xem danh sách pending users
- Click "Phê duyệt" hoặc "Từ chối"
Dashboard Tổng Quan
- Tab "Thống kê"
- Hiển thị:
- Tổng số nhân viên
- Số người đã điểm danh hôm nay
- Tỷ lệ đúng giờ
- Biểu đồ xu hướng
Báo Cáo Chi Tiết
- Chọn "Báo cáo chi tiết"
- Lọc theo:
- Ngày/Tuần/Tháng
- Nhân viên cụ thể
- Phòng ban (future)
- Xuất báo cáo (Excel/PDF)
Cấu Hình Giờ Làm
- Giờ bắt đầu: 8:00 AM (mặc định)
- Giờ kết thúc: 17:00 PM
- Giờ nghỉ trưa (future)
Quản Lý Phòng Ban
- Tạo/Sửa/Xóa phòng ban
- Gán nhân viên vào phòng ban
Backup & Restore
- Sao lưu dữ liệu định kỳ
- Khôi phục từ backup
users/{userId}
{
"uid": "firebase_auth_uid", // String (Document ID)
"email": "user@example.com", // String
"fullName": "Nguyễn Văn A", // String
"role": "user", // String: "admin" | "user"
"status": "approved", // String: "pending" | "approved" | "rejected"
"photoUrl": "https://...", // String (optional)
"department": "IT", // String (optional)
"position": "Developer", // String (optional)
"phone": "+84912345678", // String (optional)
"createdAt": Timestamp, // Timestamp
"updatedAt": Timestamp // Timestamp
}Indexes:
email(ASC)role(ASC)status(ASC)
face_embeddings/{userId}
{
"userId": "firebase_auth_uid", // String (matches Document ID)
"userEmail": "user@example.com", // String
"faceEmbedding": [0.123, -0.456, ...], // Array<Double> (512 elements)
"timestamp": 1701234567890, // Long (milliseconds)
"deviceInfo": "Pixel 6", // String
"registrationMethod": "CAMERA", // String
"confidence": 0.95 // Double (optional)
}Indexes:
userEmail(ASC)timestamp(DESC)
Security:
- Read: Public (cần cho attendance)
- Write: Chỉ user owner
attendanceRecords/{recordId}
{
"uid": "user_firebase_uid", // String
"userEmail": "user@example.com", // String
"timestamp": 1701234567890, // Long (milliseconds)
"date": "2025-11-30", // String (yyyy-MM-dd)
"time": "08:30:00", // String (HH:mm:ss)
"checkType": "CHECK_IN", // String: "CHECK_IN" | "CHECK_OUT"
"method": "FACE_RECOGNITION", // String
"confidence": 0.85, // Double (L2 distance)
"status": "PRESENT", // String: "PRESENT" | "ABSENT"
"deviceInfo": "Pixel 6", // String
"location": { // Object (optional, future)
"lat": 10.762622,
"lng": 106.660172
}
}Indexes (Composite):
uid(ASC) +date(ASC) +timestamp(DESC)date(ASC) +checkType(ASC)uid(ASC) +timestamp(DESC)
Query Examples:
// Lấy điểm danh hôm nay của user
db.collection("attendanceRecords")
.whereEqualTo("uid", currentUser.getUid())
.whereEqualTo("date", "2025-11-30")
.orderBy("timestamp", Query.Direction.DESCENDING)
.get();
// Lấy tất cả check-in trong ngày
db.collection("attendanceRecords")
.whereEqualTo("date", "2025-11-30")
.whereEqualTo("checkType", "CHECK_IN")
.get();notifications/{notificationId}
{
"userId": "user_id",
"title": "Nhắc nhở",
"message": "Bạn chưa điểm danh hôm nay",
"type": "REMINDER",
"read": false,
"timestamp": Timestamp
}FirebaseAuth.getInstance()
.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener(result -> {
FirebaseUser user = result.getUser();
// Save to Firestore
});FirebaseAuth.getInstance()
.signInWithEmailAndPassword(email, password)
.addOnSuccessListener(result -> {
// Navigate to dashboard
});GoogleSignInOptions gso = new GoogleSignInOptions.Builder()
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build();
GoogleSignInClient googleSignInClient = GoogleSignIn.getClient(this, gso);
// Launch sign-in intent...Map<String, Object> data = new HashMap<>();
data.put("field", "value");
db.collection("collectionName")
.document(documentId)
.set(data)
.addOnSuccessListener(aVoid -> {
// Success
});db.collection("users")
.document(userId)
.get()
.addOnSuccessListener(doc -> {
String name = doc.getString("fullName");
});ListenerRegistration listener = db.collection("attendanceRecords")
.whereEqualTo("uid", userId)
.addSnapshotListener((snapshots, error) -> {
// Data changed
});
// Don't forget to remove listener
listener.remove();db.collection("attendanceRecords")
.whereEqualTo("uid", userId)
.whereGreaterThanOrEqualTo("timestamp", startTime)
.whereLessThan("timestamp", endTime)
.orderBy("timestamp", Query.Direction.DESCENDING)
.limit(10)
.get();FaceNetModel faceNetModel = new FaceNetModel(context);
// Model loaded from assets/mobile_face_net.tflitefloat[] embedding = faceNetModel.getFaceEmbedding(bitmap, faceRect);
// Returns 512-dimensional float arrayfloat distance = EmbeddingsUtils.calculateL2Distance(embedding1, embedding2);
boolean isMatch = distance < THRESHOLD; // THRESHOLD = 1.0fFaceDetectorOptions options = new FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_NONE)
.setMinFaceSize(0.15f)
.build();
FaceDetector detector = FaceDetection.getClient(options);
detector.process(inputImage)
.addOnSuccessListener(faces -> {
for (Face face : faces) {
RectF boundingBox = face.getBoundingBox();
// Process face
}
});// users collection
match /users/{userId} {
allow read: if isSignedIn() &&
(userId == currentUid() || isAdmin());
allow create: if isSignedIn() && userId == currentUid();
allow update: if isSignedIn() &&
(userId == currentUid() || isAdmin());
allow delete: if isAdmin();
}
// face_embeddings collection
match /face_embeddings/{userId} {
allow read: if true; // Public read for attendance
allow write: if isSignedIn() && userId == currentUid();
}
// attendanceRecords collection
match /attendanceRecords/{recordId} {
allow read: if isSignedIn();
allow create: if true; // Allow face recognition without login
allow update, delete: if isAdmin();
}service firebase.storage {
match /b/{bucket}/o {
match /avatars/{userId}/{fileName} {
allow read: if true;
allow write: if request.auth.uid == userId &&
request.resource.size < 5 * 1024 * 1024 &&
request.resource.contentType.matches('image/.*');
}
}
}- Storage: Float array (512 chiều)
- Transport: HTTPS (TLS 1.3)
- At Rest: Firestore encryption
- Hash: Firebase Auth (bcrypt)
- Salt: Tự động bởi Firebase
- Minimum Length: 6 ký tự
<!-- Required -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android:permission.INTERNET" />
<!-- Optional -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />// Camera permission
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CODE_CAMERA);
}- ✅ Email verification (optional)
- ✅ Strong password policy
- ✅ Session timeout
- ✅ Refresh tokens
- ✅ Liveness detection (future)
- ✅ Anti-spoofing (future)
- ✅ Multiple face registration
- ✅ Embedding versioning
- ✅ GDPR compliance
- ✅ User consent
- ✅ Data retention policy
- ✅ Right to delete
@Test
public void statusType_toText_returnsCorrectString() {
assertEquals("Đúng giờ", StatusType.toText(StatusType.PRESENT));
assertEquals("Đi trễ", StatusType.toText(StatusType.LATE));
assertEquals("Vắng", StatusType.toText(StatusType.ABSENT));
}./gradlew test@Test
public void firestore_saveAndRetrieveUser_success() {
FirebaseFirestore db = FirebaseFirestore.getInstance();
Map<String, Object> user = new HashMap<>();
user.put("fullName", "Test User");
db.collection("users").document("testId")
.set(user)
.addOnSuccessListener(aVoid -> {
// Verify save success
});
}./gradlew connectedAndroidTest@Test
public void loginActivity_validCredentials_success() {
onView(withId(R.id.editTextEmail))
.perform(typeText("test@example.com"));
onView(withId(R.id.editTextPassword))
.perform(typeText("password123"));
onView(withId(R.id.buttonLogin))
.perform(click());
onView(withId(R.id.dashboard_layout))
.check(matches(isDisplayed()));
}Problem: Camera không mở hoặc crash
Solutions:
// Check permission
if (checkSelfPermission(Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, 1);
}
// Check camera availability
PackageManager pm = getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
Toast.makeText(this, "No camera available", Toast.LENGTH_SHORT).show();
}Problem: Không nhận diện được khuôn mặt
Solutions:
- ✅ Đảm bảo ánh sáng đủ
- ✅ Khuôn mặt trong khung
- ✅ Không đeo khẩu trang/kính đen
- ✅ Giảm threshold nếu quá strict
// Adjust threshold
private static final float RECOGNITION_THRESHOLD = 1.2f; // Increase if too strictProblem: Lỗi "Permission denied" khi query
Solutions:
// Check Firestore rules
match /attendanceRecords/{recordId} {
allow read: if request.auth != null; // Require authentication
allow write: if request.auth != null;
}Problem: Build error hoặc dependency conflict
Solutions:
# Clean build
./gradlew clean
# Invalidate cache
# Android Studio: File > Invalidate Caches / Restart
# Update Gradle wrapper
./gradlew wrapper --gradle-version=8.2Problem: TFLite model không load được
Solutions:
// Check file exists
try {
AssetFileDescriptor fileDescriptor =
getAssets().openFd("mobile_face_net.tflite");
Log.d(TAG, "Model file size: " + fileDescriptor.getLength());
} catch (IOException e) {
Log.e(TAG, "Model file not found", e);
}
// Ensure correct path in build.gradle
android {
aaptOptions {
noCompress "tflite"
}
}// Enable Firebase debug logging
adb shell setprop log.tag.FirebaseFirestore DEBUG
// Enable ML Kit logging
adb shell setprop log.tag.MLKit DEBUG# Use Charles Proxy or Fiddler
# Configure Android device proxy settings# Android Studio Profiler
# Run > Profile 'app'Optimize Model Inference:
// Use GPU delegate
GpuDelegate delegate = new GpuDelegate();
Interpreter.Options options = new Interpreter.Options();
options.addDelegate(delegate);
interpreter = new Interpreter(modelFile, options);Reduce Image Size:
// Resize before processing
Bitmap scaled = Bitmap.createScaledBitmap(
original, 640, 480, false);Use Indexes:
// Create composite index for common queries
uid ASC, date ASC, timestamp DESCLimit Results:
db.collection("attendanceRecords")
.limit(50) // Don't fetch all records
.get();Cache Data:
FirebaseFirestoreSettings settings =
new FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(true)
.setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
.build();
db.setFirestoreSettings(settings);Use Glide Caching:
Glide.with(context)
.load(imageUrl)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);- Liveness detection (chống giả mạo)
- Export báo cáo Excel/PDF
- Push notifications
- Dark mode
- Geo-fencing (điểm danh theo vị trí)
- QR code check-in (backup method)
- Multi-language support
- Offline mode
- Admin web dashboard
- Advanced analytics
- Integration with payroll systems
- Mobile app for iOS
- Tung - Lead Developer
- Team TimeMaster - Development Team
Copyright (c) 2025 TimeMaster Team
Licensed under the MIT License
- Email: support@timemaster.com
- GitHub: https://github.com/timemaster/issues
- Documentation: https://docs.timemaster.com
- Firebase - Backend infrastructure
- TensorFlow - Machine learning framework
- ML Kit - Face detection
- MobileFaceNet - Face recognition model
- MPAndroidChart - Chart library
Last Updated: November 30, 2025
Version: 1.0.0
Document Version: 1.0