20 KiB
20 KiB
Android Implementation Details - Nilai Clock Client
Overview
This document provides comprehensive details about the current Android implementation of the Nilai Clock Client, including native plugins, services, permissions, and platform-specific configurations.
Android Project Structure
Capacitor Android Project Layout
android/
├── app/
│ ├── src/main/
│ │ ├── java/com/ouji/factory/myapp/
│ │ │ ├── MainActivity.java
│ │ │ └── AppSecurity.java
│ │ ├── res/
│ │ │ ├── values/
│ │ │ ├── xml/
│ │ │ └── drawable/
│ │ └── AndroidManifest.xml
│ ├── build.gradle
│ └── capacitor.settings.gradle
├── build.gradle
├── gradle.properties
└── settings.gradle
Key Android Files
MainActivity.java
package com.ouji.factory.myapp;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
// Register custom AppSecurity plugin
registerPlugin(AppSecurity.class);
super.onCreate(savedInstanceState);
// Configure native window behavior
setupNativeWindow();
}
private void setupNativeWindow() {
// Hide system UI for immersive experience
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
);
// Configure status bar
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
WindowInsetsControllerCompat controller =
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
if (controller != null) {
controller.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
);
}
}
}
AppSecurity.java (Custom Plugin)
package com.ouji.factory.myapp;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import org.json.JSONArray;
import java.util.List;
@CapacitorPlugin(name = "AppSecurity")
public class AppSecurity extends Plugin {
@PluginMethod
public void getInstalledApps(PluginCall call) {
PackageManager pm = getContext().getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
JSONArray appPackages = new JSONArray();
for (ApplicationInfo packageInfo : packages) {
appPackages.put(packageInfo.packageName);
}
JSObject ret = new JSObject();
ret.put("packages", appPackages);
call.resolve(ret);
}
@PluginMethod
public void checkRootStatus(PluginCall call) {
boolean isRooted = detectRoot();
JSObject ret = new JSObject();
ret.put("isRooted", isRooted);
ret.put("riskLevel", isRooted ? "high" : "low");
call.resolve(ret);
}
private boolean detectRoot() {
// Check for common root indicators
String[] rootPaths = {
"/system/app/Superuser.apk",
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/system/sd/xbin/su",
"/system/bin/failsafe/su",
"/data/local/su"
};
for (String path : rootPaths) {
if (new java.io.File(path).exists()) {
return true;
}
}
return false;
}
}
Android Permissions
AndroidManifest.xml Permissions
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Background location for Android 10+ -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Prevent app from being killed -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
Permission Handling Strategy
Location Permissions
- ACCESS_FINE_LOCATION: Required for precise location
- ACCESS_BACKGROUND_LOCATION: Required for background tracking
- Runtime Permission Flow:
- Request fine location first
- If granted, request background location
- Handle permission denial gracefully
Camera Permission
- CAMERA: Required for QR code scanning
- Runtime Request: Requested when QR scanner is first used
- Fallback: File upload option if camera denied
Background Location Implementation
Android Background Geolocation Service
Service Configuration
// Background geolocation watcher configuration
const androidConfig = {
requestPermissions: true,
stale: false,
distanceFilter: 50, // Minimum 50 meters movement
backgroundMessage: "Tracking work location for attendance verification",
backgroundTitle: "Nilai Clock - Location Tracking",
// Android-specific options
enableHighAccuracy: true,
interval: 30000, // 30 seconds
fastestInterval: 15000, // 15 seconds minimum
maxWaitTime: 60000 // 1 minute maximum wait
}
Foreground Service Notification
<!-- Required for Android 8+ background location -->
<service android:name="com.transistorsoft.capacitor.backgroundgeolocation.BackgroundGeolocationService"
android:foregroundServiceType="location"
android:enabled="true"
android:exported="false" />
Location Update Handling
// Handle location updates from background service
const handleLocationUpdate = (location) => {
console.log('Android location update:', {
latitude: location.coords.latitude,
longitude: location.coords.longitude,
accuracy: location.coords.accuracy,
timestamp: location.timestamp,
source: 'background'
})
// Send to server if user is clocked in
if (this.isClockedIn) {
this.sendLocationToServer(location, { source: 'background' })
}
}
Battery Optimization Handling
Request Battery Optimization Exemption
// In MainActivity.java - Request to ignore battery optimization
private void requestBatteryOptimizationExemption() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent intent = new Intent();
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
}
}
}
Network Security Configuration
network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Allow cleartext traffic for development -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">10.0.2.2</domain>
<domain includeSubdomains="true">198.18.0.1</domain>
<domain includeSubdomains="true">192.168.36.54</domain>
<trust-anchors>
<certificates src="user" />
</trust-anchors>
</domain-config>
<!-- Debug overrides for development -->
<debug-overrides>
<trust-anchors>
<certificates src="user"/>
<certificates src="system"/>
</trust-anchors>
</debug-overrides>
<!-- Production configuration -->
<domain-config>
<domain includeSubdomains="true">myapp.ouji.com</domain>
<trust-anchors>
<certificates src="system"/>
</trust-anchors>
</domain-config>
</network-security-config>
Application Security Configuration
<!-- In AndroidManifest.xml -->
<application
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:allowBackup="false"
android:extractNativeLibs="false">
Device UUID Generation
Android-Specific Implementation
// Android device UUID generation strategy
async generateAndroidDeviceUuid() {
try {
const deviceInfo = await Device.getInfo()
const deviceId = await Device.getId()
// Create unique fingerprint from device characteristics
const baseString = [
deviceInfo.platform, // "android"
deviceInfo.model, // "Pixel 6"
deviceInfo.manufacturer, // "Google"
deviceId.identifier, // Android ID or UUID
deviceInfo.osVersion, // "13"
await this.getAndroidSpecificId()
].join('-')
// Hash the base string for consistent UUID
return await this.hashString(baseString)
} catch (error) {
console.error('Android UUID generation failed:', error)
return this.generateFallbackUuid()
}
}
async getAndroidSpecificId() {
try {
// Try to get Android ID or generate persistent identifier
const { value } = await Preferences.get({ key: 'android_persistent_id' })
if (value) return value
// Generate new persistent ID for this installation
const persistentId = this.generateRandomId(32)
await Preferences.set({
key: 'android_persistent_id',
value: persistentId
})
return persistentId
} catch (error) {
return `android-fallback-${Date.now()}`
}
}
Anti-Spoofing Implementation
GPS Spoofing Detection
// Android-specific anti-spoofing checks
async performAndroidSecurityCheck() {
try {
// Get installed applications
const installedApps = await AppSecurity.getInstalledApps()
// Check against blacklist
const blacklist = await this.fetchBlacklistFromServer()
const suspiciousApps = installedApps.packages.filter(pkg =>
blacklist.includes(pkg)
)
// Check for root access
const rootStatus = await AppSecurity.checkRootStatus()
// Determine risk level
let riskLevel = 'low'
let riskFactors = []
if (suspiciousApps.length > 0) {
riskLevel = 'high'
riskFactors.push('gps_spoofing_apps_detected')
}
if (rootStatus.isRooted) {
riskLevel = 'high'
riskFactors.push('device_rooted')
}
const result = {
platform: 'android',
riskLevel,
riskFactors,
suspiciousApps,
isRooted: rootStatus.isRooted,
timestamp: new Date().toISOString()
}
// Send to server
await this.sendSecurityCheckToServer(result)
return result
} catch (error) {
console.error('Android security check failed:', error)
return { riskLevel: 'unknown', error: error.message }
}
}
Known GPS Spoofing Apps Blacklist
const gpsSpooferBlacklist = [
// Popular GPS spoofing apps
'com.lexa.fakegps',
'com.incorporateapps.fakegps.fre',
'com.blogspot.newapphorizons.fakegps',
'com.theappninjas.gpsjoystick',
'com.fakegps.mock',
'com.mock.location.app',
'com.gpsemulator',
'com.locationspoofer',
'com.fakegps.location',
'com.mock.gps.location',
// Developer tools that can spoof location
'com.android.development',
'com.android.development_settings',
'com.mock.location.developer',
// Xposed modules
'de.robv.android.xposed.installer',
'com.solohsu.android.edxp.manager',
// Root management apps
'com.noshufou.android.su',
'com.noshufou.android.su.elite',
'eu.chainfire.supersu',
'com.koushikdutta.superuser',
'com.thirdparty.superuser',
'com.yellowes.su'
]
QR Code Scanning Implementation
Camera Integration
// Android camera configuration for QR scanning
const androidCameraConfig = {
fps: 10,
qrbox: { width: 250, height: 250 },
aspectRatio: 1.0,
disableFlip: false,
videoConstraints: {
facingMode: 'environment', // Back camera
advanced: [{
focusMode: 'continuous',
zoom: 1.0
}]
}
}
// Initialize QR scanner
const initializeAndroidQRScanner = () => {
html5QrCode = new Html5Qrcode('qr-reader')
html5QrCode.start(
{ facingMode: 'environment' },
androidCameraConfig,
onScanSuccess,
onScanFailure
).catch(err => {
console.error('Android QR scanner failed to start:', err)
// Fallback to file upload
showFileUploadOption()
})
}
Camera Permission Handling
// Check and request camera permission
const checkAndroidCameraPermission = async () => {
try {
const permissions = await Camera.checkPermissions()
if (permissions.camera === 'granted') {
return true
}
if (permissions.camera === 'denied') {
// Guide user to settings
showPermissionDeniedAlert()
return false
}
// Request permission
const result = await Camera.requestPermissions()
return result.camera === 'granted'
} catch (error) {
console.error('Camera permission check failed:', error)
return false
}
}
Local Storage and Preferences
Android Secure Storage
// Android uses EncryptedSharedPreferences
class AndroidSecureStorage {
async setItem(key, value) {
try {
await Preferences.set({
key: key,
value: value
})
return true
} catch (error) {
console.error('Android secure storage set failed:', error)
return false
}
}
async getItem(key) {
try {
const { value } = await Preferences.get({ key })
return value
} catch (error) {
console.error('Android secure storage get failed:', error)
return null
}
}
async removeItem(key) {
try {
await Preferences.remove({ key })
return true
} catch (error) {
console.error('Android secure storage remove failed:', error)
return false
}
}
}
Performance Optimizations
Memory Management
// Android-specific memory optimization
const optimizeAndroidPerformance = () => {
// Limit location update frequency
const locationThrottle = 30000 // 30 seconds
// Clean up unused resources
const cleanupResources = () => {
if (html5QrCode && !html5QrCode.isScanning) {
html5QrCode.clear()
}
// Clear old location data
if (locationHistory.length > 100) {
locationHistory.splice(0, locationHistory.length - 50)
}
}
// Set up periodic cleanup
setInterval(cleanupResources, 300000) // 5 minutes
}
Battery Usage Optimization
// Optimize for Android battery usage
const optimizeAndroidBattery = () => {
// Reduce location accuracy when not critical
const adaptiveLocationConfig = {
enableHighAccuracy: false, // Use network location when possible
timeout: 30000,
maximumAge: 60000 // Accept 1-minute old location
}
// Pause non-critical background tasks
const pauseNonCriticalTasks = () => {
// Stop security checks when app is backgrounded
if (document.hidden) {
clearInterval(securityCheckInterval)
}
}
document.addEventListener('visibilitychange', pauseNonCriticalTasks)
}
Build Configuration
Android Build Variants
// app/build.gradle
android {
buildTypes {
debug {
debuggable true
minifyEnabled false
applicationIdSuffix ".debug"
}
release {
debuggable false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
flavorDimensions "environment"
productFlavors {
development {
dimension "environment"
applicationIdSuffix ".dev"
buildConfigField "String", "API_BASE_URL", '"http://192.168.36.54:3000"'
}
production {
dimension "environment"
buildConfigField "String", "API_BASE_URL", '"https://myapp.ouji.com/nilai_clock_api"'
}
}
}
ProGuard Configuration
# proguard-rules.pro
-keep class com.ouji.factory.myapp.** { *; }
-keep class com.getcapacitor.** { *; }
-keep class com.transistorsoft.** { *; }
# Keep location service classes
-keep class * extends android.app.Service
-keep class * implements android.location.LocationListener
# Keep JSON serialization
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.google.gson.** { *; }
Debugging and Logging
Android Debug Configuration
// Android-specific debug logging
const AndroidDebugger = {
enableLocationLogging: () => {
console.log('Android location debugging enabled')
// Log all location updates
window.addEventListener('location-update', (event) => {
console.log('Android location:', event.detail)
})
},
enableSecurityLogging: () => {
console.log('Android security debugging enabled')
// Log security check results
window.addEventListener('security-check', (event) => {
console.log('Android security:', event.detail)
})
},
logDeviceInfo: async () => {
const deviceInfo = await Device.getInfo()
console.log('Android device info:', deviceInfo)
}
}
Performance Monitoring
// Monitor Android app performance
const AndroidPerformanceMonitor = {
trackMemoryUsage: () => {
if (performance.memory) {
console.log('Memory usage:', {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit
})
}
},
trackLocationPerformance: () => {
const startTime = performance.now()
return {
end: () => {
const endTime = performance.now()
console.log(`Location operation took ${endTime - startTime} ms`)
}
}
}
}
Troubleshooting Common Android Issues
Location Issues
- Background location stops: Check battery optimization settings
- Inaccurate location: Verify GPS is enabled, check for spoofing apps
- Permission denied: Guide user through settings, explain necessity
Camera Issues
- Camera won't open: Check permission, verify camera availability
- QR scan fails: Improve lighting, check QR code quality
- App crashes on scan: Handle camera resource conflicts
Performance Issues
- High battery usage: Optimize location frequency, reduce background tasks
- Memory leaks: Properly dispose of resources, limit data retention
- Slow startup: Optimize initialization sequence, lazy load components
Security Issues
- False positives: Refine blacklist, improve detection algorithms
- Bypass attempts: Enhance root detection, monitor for new spoofing methods
- Data security: Ensure proper encryption, secure API communication