You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HMG_QLine/PROJECT_CONTEXT.md

54 KiB

HMG_QLine - Project Context Documentation

Last Updated: July 30, 2026
Project Type: Flutter Healthcare Queue Management System
Target Platform: Android AC-Powered Kiosk Displays
Status: Production (Running 24/7 in HMG Hospitals, Saudi Arabia)


📋 Table of Contents

  1. Project Overview
  2. System Architecture
  3. Technology Stack
  4. Directory Structure
  5. Core Features
  6. Data Flow
  7. Key Components
  8. Android Native Layer
  9. API Integration
  10. Logging System
  11. Deployment
  12. Recent Critical Fixes
  13. Configuration System
  14. Known Issues & Solutions
  15. Testing & Monitoring
  16. Development Guidelines

📖 Project Overview

Purpose

HMG_QLine is an enterprise-grade hospital queue management system designed for continuous 24/7 operation on AC-powered Android LED kiosk displays across multiple HMG (Habib Medical Group) hospital branches in Saudi Arabia.

Key Objectives

  • Real-time Patient Queue Management: Display and manage patient queues across waiting areas
  • Multi-modal Patient Calling: Audio tones + Text-to-Speech voice announcements
  • Self-Service Kiosks: Allow patients to generate tickets via QR scan or ID entry
  • 24/7 Reliability: Run continuously without manual intervention
  • Multi-Branch Support: Centralized configuration for multiple hospital locations
  • Multi-Language: Full English/Arabic support with RTL

Supported Branches

Branch Project ID Location
Takhasusi Main 16 Riyadh (uses legacy orientation detection)
Suwaidi 17 Riyadh
Khobar 60 Eastern Province
Takhasusi Women 140 Riyadh

🏗️ System Architecture

Architecture Pattern

MVVM (Model-View-ViewModel) with Provider for state management

┌─────────────────────────────────────────────────────────────┐
│                         Views (UI)                           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │ Splash       │  │ Main Queue   │  │ Kiosk        │     │
│  │ Screen       │  │ Screen       │  │ Screen       │     │
│  └──────────────┘  └──────────────┘  └──────────────┘     │
└────────────────────────────┬─────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                    ViewModels (State)                        │
│  ┌────────────────────────┐  ┌────────────────────────┐    │
│  │ ScreenConfigViewModel  │  │ QueuingViewModel       │    │
│  │ - Configuration        │  │ - Queue Management     │    │
│  │ - Lifecycle            │  │ - SignalR Hub          │    │
│  │ - Health Checks        │  │ - Audio/TTS            │    │
│  └────────────────────────┘  └────────────────────────┘    │
└────────────────────────────┬─────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                  Services (Business Logic)                   │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐      │
│  │ Logger   │ │ Audio    │ │ TTS      │ │ Network  │      │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘      │
└────────────────────────────┬─────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│              Repositories (Data Layer)                       │
│  ┌────────────────────────┐  ┌────────────────────────┐    │
│  │ SignalR Repository     │  │ Screen Details Repo    │    │
│  │ - Real-time Hub        │  │ - REST API Calls       │    │
│  └────────────────────────┘  └────────────────────────┘    │
└────────────────────────────┬─────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────┐
│                   Data Sources                               │
│  ┌────────────────────────┐  ┌────────────────────────┐    │
│  │ SignalR Hub (WSS)      │  │ REST API (HTTPS)       │    │
│  │ - Live updates         │  │ - Configuration        │    │
│  │ - Patient calls        │  │ - Ticket generation    │    │
│  └────────────────────────┘  └────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

Dependency Injection

GetIt is used for service location and dependency injection:

// All singletons registered in lib/config/dependency_injection.dart
- LoggerService
- CrashHandlerService
- ApiClient
- SignalrRepo
- ScreenDetailsRepo
- NativeMethodChannelService
- ConnectivityService
- CacheService
- AudioService
- TextToSpeechService
- ScreenConfigViewModel
- QueuingViewModel

🛠️ Technology Stack

Flutter/Dart

  • Flutter SDK: >=3.5.4
  • Dart SDK: ^3.5.4
  • State Management: Provider 6.1.2
  • DI Container: GetIt 8.0.2

Key Packages

Package Version Purpose
signalr_core 1.1.1 Real-time WebSocket communication
provider 6.1.2 State management
get_it 8.0.2 Dependency injection
connectivity_plus 6.1.0 Network status monitoring
wakelock_plus 1.2.10 Keep device awake
flutter_tts 4.2.0 Text-to-speech announcements
just_audio 0.9.42 Audio tone playback
shared_preferences 2.3.5 Local key-value storage
path_provider 2.1.5 Access to file system
qr_code_scanner_plus 2.0.10+1 QR code scanning
logger 2.4.0 Logging framework
intl 0.19.0 Internationalization
flutter_svg 2.0.14 SVG rendering
marquee 2.2.3 Scrolling text animation

Android Native (Kotlin)

  • Kotlin: JVM Target 1.8
  • Compile SDK: 35 (Android 15)
  • Min SDK: Flutter default (likely 21+)
  • Dependencies:
    • androidx.lifecycle:lifecycle-service:2.8.7
    • androidx.core:core-ktx:1.13.1

📁 Directory Structure

HMG_QLine/
├── lib/
│   ├── main.dart                          # Entry point with crash handling
│   ├── api/
│   │   └── api_client.dart                # HTTP client with logging
│   ├── config/
│   │   ├── dependency_injection.dart      # GetIt DI setup
│   │   └── routes.dart                    # App routes configuration
│   ├── constants/
│   │   └── app_constants.dart             # All constants (API, colors, assets)
│   ├── models/
│   │   ├── global_config_model.dart       # Main configuration model
│   │   ├── ticket_model.dart              # Patient ticket data
│   │   ├── kiosk_*.dart                   # Kiosk-specific models
│   │   ├── prayers_widget_model.dart      # Islamic prayer times
│   │   ├── weathers_widget_model.dart     # Weather data
│   │   └── rss_feed_model.dart            # News feed
│   ├── repositories/
│   │   ├── signalR_repo.dart              # SignalR hub management
│   │   └── screen_details_repo.dart       # REST API calls
│   ├── services/
│   │   ├── logger_service.dart            # File logging with rotation
│   │   ├── crash_handler_service.dart     # Global error handling
│   │   ├── connectivity_service.dart      # Network monitoring
│   │   ├── audio_service.dart             # Audio tone playback
│   │   ├── text_to_speech_service.dart    # TTS announcements
│   │   └── cache_service.dart             # SharedPreferences wrapper
│   ├── utilities/
│   │   ├── enums.dart                     # All enums
│   │   ├── extensions.dart                # Dart extensions
│   │   ├── native_method_handler.dart     # Platform channel
│   │   ├── foreground_task_handler.dart   # Background service
│   │   └── lifecycle_handler.dart         # App lifecycle
│   ├── view_models/
│   │   ├── screen_config_view_model.dart  # Configuration & lifecycle VM
│   │   └── queuing_view_model.dart        # Queue management VM
│   └── views/
│       ├── splash_screen/                 # Loading screen
│       ├── main_queue_screen/             # Main display (waiting area)
│       ├── kiosk_screens/                 # Self-service kiosk UI
│       ├── common_widgets/                # Reusable components
│       └── view_helpers/                  # UI utilities
├── android/
│   ├── app/
│   │   ├── build.gradle                   # Android build config
│   │   └── src/main/
│   │       ├── AndroidManifest.xml        # Permissions & components
│   │       └── kotlin/com/example/hmg_qline/hmg_qline/
│   │           ├── MainActivity.kt        # Main activity with kiosk flags
│   │           ├── AlarmScheduler.kt      # Daily restart scheduler
│   │           ├── BootReceiver.kt        # Auto-start on boot
│   │           ├── RestartAlarmReceiver.kt # Scheduled restart handler
│   │           └── BootForegroundService.kt # Boot service
│   ├── build.gradle                       # Project-level build
│   └── gradle.properties                  # Gradle properties
├── assets/
│   ├── fonts/                             # Poppins, Cairo, GE_SS_Two
│   ├── images/                            # Logos, icons (SVG/PNG)
│   ├── new_design_icons/                  # Updated design assets
│   │   └── weather_icons/                 # Weather SVG icons
│   └── tones/
│       └── call_tone.mp3                  # Patient call tone
├── qline_scripts/                         # Deployment automation
│   ├── deploy_qline.sh                    # Deploy APK to devices
│   ├── restart_qline.sh                   # Restart app
│   ├── extract_logs.sh                    # Pull logs
│   ├── clear_logs.sh                      # Clear device logs
│   ├── connect_devices.sh                 # Connect via ADB
│   ├── kill_qline.sh                      # Force stop app
│   ├── device_ips_*.txt                   # IP address lists per branch
│   └── apks/                              # APK storage (gitignored)
├── pubspec.yaml                           # Flutter dependencies
├── README.md                              # Basic project info
├── QUICK_SUMMARY.md                       # Quick reference
├── CHANGES_EXPLANATION.md                 # Detailed changelog
├── AC_POWERED_KIOSK_SUMMARY.md           # Kiosk-specific docs
├── DEPLOYMENT_READY.md                    # (empty)
└── PROJECT_CONTEXT.md                     # This file


🎯 Core Features

1. Queue Management System

Patient Call Types (Enums)

enum CallTypeEnum {
  vitalSign,    // 1 - Vital signs measurement room
  doctor,       // 2 - Doctor consultation
  procedure,    // 3 - Medical procedures
  vaccination,  // 4 - Vaccination room
  nebulization, // 5 - Nebulization treatment
  none,         // 6 - No specific type
}

Queue Types

enum QTypeEnum {
  appointment,  // 1 - Scheduled appointments
  lab,          // 2 - Laboratory services
  rad,          // 3 - Radiology services
  general,      // 4 - General queue
}

Screen Types

enum ScreenTypeEnum {
  waitingAreaScreen, // 1 - Main waiting area display
  roomLevelScreen,   // 2 - Specific counter/room
  receptionScreen,   // 3 - Reception desk
  dashboardScreen,   // 4 - Admin dashboard
  kioskScreen,       // 5 - Self-service kiosk
}

2. Real-Time Communication (SignalR)

Hub Connection Features

  • Base URL: {baseUrl}/PatientCallingHub?IPAddress={deviceIp}
  • Auto-reconnect: Custom delays [0, 2000, 5000, 10000, 30000] ms
  • Keep-alive: 15-second heartbeat
  • Timeout: 2 minutes
  • Manual retry: 10 attempts with 5-second delay

Hub Events

// Receive patient call
connection.on("SendQLinePatientCall", (message) => {
  // Adds ticket to queue
  // Plays tone + TTS announcement
  // Updates display
});

// Receive configuration update
connection.on("SendQLineConfig", (message) => {
  // Updates global configuration
  // Refreshes widgets (weather, prayer, RSS)
});

3. Audio & Voice System

Audio Service (Just Audio)

  • Plays call tone: assets/tones/call_tone.mp3
  • Mute support based on configuration
  • Completion callback triggers TTS

Text-to-Speech Service

  • Multi-language: English + Arabic
  • Voice speed configuration
  • Dynamic message construction based on call type
  • Completion callback triggers next ticket in queue

Call Flow

1. Ticket received via SignalR
2. Add to queue (currentTickets or queueTickets)
3. Play audio tone (if enabled)
4. On tone complete → Speak TTS message (if enabled)
5. On TTS complete → Wait {concurrentCallDelaySec} seconds
6. Process next ticket in queue

4. Kiosk Self-Service

Kiosk States

enum KioskScreenStateEnums {
  languageState,       // Select language
  queueSelectionState, // Select service queue
  askPrescriptionState,// Ask for prescription (if applicable)
  ticketNumState,      // Display generated ticket
  busyState,           // Loading
}

Kiosk Input Methods

  1. QR Code Scanner: Scan patient ID barcode
  2. Manual Entry: Type patient ID on virtual keyboard

Ticket Generation Flow

1. Select language (English/Arabic)
2. Select queue (from kioskQueueList)
3. Scan QR / Enter patient ID
4. API call: /api/Gen/GEN_PatientCallNo_Get
5. Display ticket with queue number
6. Auto-return to language selection after timeout

5. Information Widgets

Weather Widget

  • API: /api/PatientCall/WeatherForecast_GetBy5Days
  • Data: 5-day forecast with min/max temp
  • Icons: SVG weather icons (sunny, rainy, cloudy, etc.)
  • Update: Every midnight or manual refresh

Prayer Times Widget

  • API: /api/PatientCall/PrayerTime_Today
  • Data: Fajr, Dhuhr, Asr, Maghrib, Isha
  • Display: Next upcoming prayer with countdown
  • Source: Based on project lat/long coordinates

RSS Feed Widget

  • API: /api/PatientCall/RssFeed_Get
  • Display: Marquee scrolling text
  • Update: Every 2 hours
  • Fallback: Dummy news text if API fails

6. Health Monitoring System

Health Check Timer (Every 5 Minutes)

_startHealthCheckTimer() {
  Timer.periodic(Duration(minutes: 5), (timer) {
    // 1. Calculate actual uptime
    final uptime = DateTime.now().difference(appStartTime);
    
    // 2. Log health status
    loggerService.logToFile(
      "Health check - App uptime: ${uptime.inHours}h ${uptime.inMinutes % 60}m"
    );
    
    // 3. Check SignalR connection
    syncHubConnectionState();
    
    // 4. Track consecutive failures
    if (failure) {
      healthCheckFailures++;
      if (healthCheckFailures >= 3) {
        // Auto-restart SignalR connection
      }
    }
    
    // 5. Ensure wakelock still enabled
    WakelockPlus.enable();
    
    // 6. Log connection status
    logToFile("Hub connected: $isHubConnected, Internet: $isInternetConnected");
    
    // 7. Warn if running >60 hours
    if (uptime.inHours > 60) {
      logToFile("WARNING: App running for ${uptime.inHours} hours");
    }
  });
}

🔄 Data Flow

Ticket Call Flow

graph TD
    A[Backend sends ticket via SignalR] --> B[onHubTicketCall in QueuingViewModel]
    B --> C{Is calling in progress?}
    C -->|No| D[Add to currentTickets, show on screen]
    C -->|Yes| E[Add to queueTickets for later]
    D --> F[Acknowledge ticket via API]
    F --> G[Play audio tone]
    G --> H[Speak TTS message]
    H --> I{More tickets in queue?}
    I -->|Yes| J[Wait concurrentCallDelaySec]
    J --> K[Move next ticket from queueTickets to currentTickets]
    K --> F
    I -->|No| L[Set isCallingInProgress = false]

Configuration Update Flow

graph TD
    A[App starts] --> B[Get device IP address]
    B --> C[Call /api/PatientCall/Common_Config_GetByIP]
    C --> D[Parse GlobalConfigurationsModel]
    D --> E[Set screen type, queue type, orientation]
    E --> F{Widgets required?}
    F -->|Weather| G[Fetch weather data]
    F -->|Prayers| H[Fetch prayer times]
    F -->|RSS| I[Fetch RSS feed]
    G --> J[Start periodic update timer]
    H --> J
    I --> J
    J --> K[Update every 10 minutes / midnight]

Lifecycle & Recovery Flow

graph TD
    A[App initialized] --> B[Record appStartTime]
    B --> C[Start health check timer: 5 min]
    C --> D[Start midnight check timer: 10 min]
    D --> E[Listen to network changes]
    E --> F{Network disconnected?}
    F -->|Yes| G[Set isInternetConnected = false]
    G --> H[Set isHubConnected = false]
    F -->|No| I{Network reconnected?}
    I -->|Yes| J[Set isInternetConnected = true]
    J --> K[Restart SignalR connection]
    K --> L[Health check validates connection]
    L --> M{3 consecutive failures?}
    M -->|Yes| N[Force restart SignalR]
    M -->|No| O[Continue monitoring]

🔑 Key Components

1. ScreenConfigViewModel

Responsibilities:

  • Device IP detection
  • Global configuration management
  • Lifecycle management (onAppPaused, onAppResumed, onAppDetached)
  • Health monitoring (5-minute timer)
  • Widget data fetching (weather, prayers, RSS)
  • Kiosk queue selection
  • QR scanner management
  • Ticket generation for kiosk

Key Properties:

String currentScreenIP              // Device IP address
GlobalConfigurationsModel globalConfigurationsModel
ScreenTypeEnum currentScreenTypeEnum
QTypeEnum currentQTypeEnum
bool isInternetConnected
bool isHubConnected
DateTime appStartTime               // For uptime tracking
int healthCheckFailures             // Consecutive failure count

Key Methods:

initializeScreenConfigVM()          // Initialize on app start
waitForIPAndInitializeConfigVM()    // Wait for IP then init
getGlobalConfigurationsByIP()       // Fetch config from API
listenNetworkConnectivity()         // Monitor network changes
_startHealthCheckTimer()            // Health monitoring every 5 min
syncHubConnectionState()            // Check SignalR status
onAppPaused()                       // Log when app backgrounded
onAppResumed()                      // Re-enable wakelock
onAppDetached()                     // Log when app killed

2. QueuingViewModel

Responsibilities:

  • SignalR hub connection management
  • Patient ticket queue management
  • Audio tone playback coordination
  • TTS voice announcement coordination
  • Ticket acknowledgment to backend

Key Properties:

List<TicketDetailsModel> currentTickets  // Currently displayed
List<TicketDetailsModel> queueTickets    // Waiting queue
bool isCallingInProgress                 // Calling state lock

Key Methods:

initializeQueueingVM()              // Initialize services
startHubConnection()                // Connect to SignalR hub
stopHubConnection()                 // Disconnect from hub
onHubTicketCall()                   // Handle incoming ticket
onHubConfigCall()                   // Handle config update
onHubReconnected()                  // Handle reconnection
onHubDisconnected()                 // Handle disconnection
addNewTicket()                      // Add ticket to queue
callTicketOnScreen()                // Display & announce ticket
onToneCompleted()                   // Trigger TTS after tone
onVoiceCompleted()                  // Process next ticket
waitAndCallNextTicketIfAvailable()  // Queue management

3. SignalrRepo

Implementation:

class SignalrRepoImp implements SignalrRepo {
  HubConnection? connection;
  
  // Check if connected
  bool get isConnected => connection?.state == HubConnectionState.connected;
  
  // Start connection with auto-reconnect
  startHubConnection({
    required String deviceIp,
    required Function(List<Object?>?) onHubTicketCall,
    required Function(dynamic) onHubConfigCall,
    required Function(dynamic) onHubReconnected,
    required Function(dynamic) onHubDisconnected,
  }) {
    connection = HubConnectionBuilder()
      .withUrl("$baseUrl?IPAddress=$deviceIp")
      .withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
      .build();
      
    connection!.serverTimeoutInMilliseconds = 120000;
    connection!.keepAliveIntervalInMilliseconds = 15000;
    
    // Register event handlers
    connection!.on("SendQLinePatientCall", onHubTicketCall);
    connection!.on("SendQLineConfig", onHubConfigCall);
    
    // Handle reconnection
    connection!.onreconnected(onHubReconnected);
    connection!.onclose((exception) {
      // Auto-retry up to 10 times
    });
    
    await connection!.start();
  }
}

4. LoggerService

Features:

  • File-based logging to external storage
  • Three log types: Data, Error, Connectivity
  • Log rotation based on time (48h) or size (2MB)
  • IP-based log file naming

Log File Structure:

/storage/emulated/0/Android/data/com.example.hmg_qline.hmg_qline/files/logs/
├── 12.4.5.1_data_logs.txt
├── 12.4.5.1_error_logs.txt
└── 12.4.5.1_connectivity_logs.txt

Log Format:

[2026-07-30 02:30:15 PM] [SOURCE: _startHealthCheckTimer -> screen_config_view_model.dart] DATA: Health check performed - App uptime: 14h 23m (started: 2026-07-30 00:07:00)

5. CrashHandlerService

Global Error Handling:

setupGlobalErrorHandlers() {
  // Flutter framework errors
  FlutterError.onError = (FlutterErrorDetails details) {
    handleCrash(error: details.exception, stackTrace: details.stack);
  };
  
  // Platform dispatcher errors
  PlatformDispatcher.instance.onError = (error, stack) {
    handleCrash(error: error, stackTrace: stack);
    return true;
  };
  
  // Zone errors handled by runZonedGuarded in main.dart
}

6. GlobalConfigurationsModel

Key Configuration Fields:

class GlobalConfigurationsModel {
  int? id;
  int? projectID;                     // Branch identifier
  ScreenTypeEnum screenTypeEnum;      // Screen type
  QTypeEnum qTypeEnum;                // Queue type
  ScreenOrientationEnum orientationTypeEnum;
  
  // Display settings
  int screenMaxDisplayPatients = 16;
  int concurrentCallDelaySec = 1;
  
  // Audio/Voice
  bool isToneReq = false;
  bool isVoiceReq = false;
  LanguageEnum voiceLanguageEnum;
  LanguageEnum screenLanguageEnum;
  
  // Widget visibility
  bool isWeatherReq = false;
  bool isPrayerTimeReq = false;
  bool isRssFeedReq = false;
  
  // Location data
  double? projectLatitude;
  double? projectLongitude;
  int? cityKey;
  
  // Clinic prefix validation
  bool globalClinicPrefixReq = false;
  bool clinicPrefixReq = true;
  
  // Kiosk configuration
  List<KioskQueueModel>? kioskQueueList;
  List<KioskLanguageConfigModel>? kioskLanguageConfigList;
  
  // Legacy support flag
  bool isFromTakhasusiMain = false;  // projectID == 16
  
  // Clinic prefix validation method
  bool isClinicPrefixAdded(String ticketNo) {
    return RegExp(r'^[A-Za-z]{3} W-').hasMatch(ticketNo);
    // Returns true for: "ABC W-123" format
  }
}

🤖 Android Native Layer

MainActivity.kt

Kiosk Display Mode Flags:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    // Keep screen on at all times (AC-powered kiosk)
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
    window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
    window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
    window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
    window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
    
    // Initialize daily restart alarm at 00:15
    initializeRestartAlarm()
}

Method Channel Support:

configureFlutterEngine(flutterEngine: FlutterEngine) {
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        .setMethodCallHandler { call, result ->
            when (call.method) {
                "reopenApp" -> bringAppToForeground()
                "restartApp" -> restartApplication()
                "restartDevice" -> restartDevice()  // Requires root
                "clearAudioCache" -> clearAudioResources()
                "clearAllResources" -> clearAllNativeResources()
                "scheduleRestartAlarm" -> scheduleRestartAlarm(hour, minute)
                "cancelRestartAlarm" -> cancelRestartAlarm()
                "canScheduleExactAlarms" -> checkAlarmPermission()
                "requestExactAlarmPermission" -> requestAlarmPermission()
            }
        }
}

AlarmScheduler.kt

Android Version Compatibility:

fun scheduleRestartAlarm(context: Context, hour: Int = 0, minute: Int = 15) {
    when {
        // Android 14+ (API 34+)
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> {
            if (alarmManager.canScheduleExactAlarms()) {
                alarmManager.setExactAndAllowWhileIdle(...)
            } else {
                alarmManager.setAndAllowWhileIdle(...)  // Fallback
            }
        }
        // Android 12-13 (API 31-33)
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            if (alarmManager.canScheduleExactAlarms()) {
                alarmManager.setExactAndAllowWhileIdle(...)
            }
        }
        // Android 6-11 (API 23-30)
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
            alarmManager.setExactAndAllowWhileIdle(...)
        }
        // Android < 6
        else -> {
            alarmManager.setExact(...)
        }
    }
}

BootBroadcastReceiver.kt

Auto-Start on Device Boot:

override fun onReceive(context: Context, intent: Intent) {
    if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
        // 1. Schedule daily restart alarm
        AlarmScheduler.scheduleRestartAlarm(context, 0, 15)
        
        // 2. Start foreground service to launch app
        val serviceIntent = Intent(context, BootForegroundService::class.java)
        context.startForegroundService(serviceIntent)
    }
}

AndroidManifest.xml

Critical Permissions:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

Activity Configuration:

<activity
    android:name=".MainActivity"
    android:screenOrientation="portrait"
    android:keepScreenOn="true"
    android:launchMode="singleTop"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|...">
</activity>

🌐 API Integration

Base URLs

static String baseUrlLive = 'https://qline.hmg.com';        // LIVE
static String baseUrlUat = 'https://ms.hmg.com/nscapi';     // UAT
static String baseUrlDev = 'https://ms.hmg.com/nscapi2';    // DEV

static String baseUrl = baseUrlUat;  // Current environment

API Endpoints

Configuration APIs

// Get device configuration by IP
GET /api/PatientCall/Common_Config_GetByIP
Parameters: { ipAddress: string, apiKey: string }
Returns: GlobalConfigurationsModel

// Get waiting area screen config
GET /api/PatientCall/WaitingAreaScreen_Config_Get
Parameters: { ipAddress: string, apiKey: string }
Returns: Screen configuration

// Get RSS feed
GET /api/PatientCall/RssFeed_Get
Parameters: { languageId: int }
Returns: RssFeedModel

// Get weather forecast
GET /api/PatientCall/WeatherForecast_GetBy5Days
Parameters: { cityKey: string }
Returns: WeathersWidgetModel (5-day forecast)

// Get prayer times
GET /api/PatientCall/PrayerTime_Today
Parameters: { latitude: double, longitude: double }
Returns: PrayersWidgetModel

Ticket APIs

// Create ticket for kiosk
POST /api/Gen/GEN_PatientCallNo_Get
Body: {
  projectID: int,
  queueID: int,
  patientID: int,
  apiKey: string
}
Returns: KioskPatientTicket

// Acknowledge ticket viewed
POST /api/Common/TicketQueueAck_Insert
Body: {
  ticketQueueID: int,
  ipAddress: string,
  qType: int,
  apiKey: string
}

// Update call request (appointment only)
POST /api/PatientCall/CallRequest_QueueUpdate
Body: {
  ticketId: int,
  ipAddress: string,
  callType: int,
  apiKey: string
}

SignalR Hub

// Connect to hub
WSS /PatientCallingHub?IPAddress={deviceIp}

// Subscribe to events
Hub.on("SendQLinePatientCall", callback)  // Patient call notification
Hub.on("SendQLineConfig", callback)       // Configuration update

API Constants

class ApiConstants {
  static String apiKey = 'EE17D21C7943485D9780223CCE55DCE5';
  
  // Hub events
  static String sendQLinePatientCall = "SendQLinePatientCall";
  static String sendQLineConfig = "SendQLineConfig";
}

📝 Logging System

Log Types

enum LogTypeEnum {
  data,         // Health checks, events, lifecycle
  error,        // Errors and crashes
  connectivity, // Network status, SignalR events
}

Log File Locations

# External storage path
/storage/emulated/0/Android/data/com.example.hmg_qline.hmg_qline/files/logs/

# Files (named by device IP)
{IP}_data_logs.txt          # General data and health checks
{IP}_error_logs.txt         # Errors and crashes
{IP}_connectivity_logs.txt  # Network and SignalR events

Log Rotation Rules

  1. Time-based: Clear logs after 48 hours
  2. Size-based: Clear when file exceeds 2 MB
  3. Timestamp: Last cleared time stored in SharedPreferences

Log Format

[YYYY-MM-DD HH:MM:SS AM/PM] [SOURCE: method -> file.dart] TYPE: message

Examples:

[2026-07-30 02:30:15 PM] [SOURCE: _startHealthCheckTimer -> screen_config_view_model.dart] DATA: Health check performed - App uptime: 14h 23m (started: 2026-07-30 00:07:00)

[2026-07-30 02:30:16 PM] [SOURCE: _startHealthCheckTimer -> screen_config_view_model.dart] CONNECTIVITY: Health check - Hub connected: true, Internet: true

[2026-07-30 02:35:20 PM] [SOURCE: onHubDisconnected -> queueing_view_model.dart] CONNECTIVITY: Hub disconnected: Connection lost

==================== CRASH ====================
[2026-07-30 02:40:00 PM]
CONTEXT: QueuingViewModel.triggerAsyncCrash
ERROR: Exception: Test async crash
STACKTRACE:
#0      QueuingViewModel.triggerAsyncCrash
#1      ...
==================================================

Logging Methods

// Data logging
loggerService.logToFile(
  message: "Health check performed",
  source: "method -> file.dart",
  type: LogTypeEnum.data,
);

// Error logging
loggerService.logError("Error message");

// Crash logging
loggerService.logCrash(
  error: exception,
  stackTrace: stack,
  context: "method context",
);

🚀 Deployment

Build Process

# Clean build
cd /Users/faiz-hashmi/StudioProjects/HMG_QLine
flutter clean
flutter pub get
flutter build apk --release

# Output location
build/app/outputs/flutter-apk/app-release.apk

Shell Scripts (qline_scripts/)

1. Connect Devices

sh connect_devices.sh -ipfile device_ips_all.txt

# Connects to all devices via ADB over TCP/IP
# Uses format: adb connect {ip}:5555

2. Deploy APK

sh deploy_qline.sh -ipfile device_ips_all.txt -version 9.4

# Steps:
# 1. Connect to all devices
# 2. Install APK: adb -s {ip}:5555 install -r apks/app-release-9.4.apk
# 3. Launch app: adb -s {ip}:5555 shell am start -n {package}/{activity}
# 4. Log installation in qline_installation_records.txt

3. Restart App

sh restart_qline.sh -ipfile device_ips_all.txt

# Steps:
# 1. Force stop: adb shell am force-stop {package}
# 2. Launch: adb shell am start -n {package}/{activity}

4. Extract Logs

# Single device
sh extract_logs.sh -ip 10.70.6.143

# All devices
sh extract_all_logs.sh -ipfile device_ips_all.txt

# Pulls files from:
# /storage/emulated/0/Android/data/{package}/files/logs/
# To: logs/{ip}_{timestamp}/

5. Clear Logs

# Single device
sh clear_logs.sh -ip 10.70.6.143

# All devices
sh clear_logs_all.sh -ipfile device_ips_all.txt

# Removes all log files from device

6. Kill App

sh kill_qline.sh -ipfile device_ips_all.txt

# Force stop on all devices
# adb shell am force-stop {package}

Device IP Lists

device_ips_all.txt          # All hospital devices
device_ips_tak.txt          # Takhasusi Main (projectID: 16)
device_ips_tak_women.txt    # Takhasusi Women (projectID: 140)
device_ips_swd.txt          # Suwaidi (projectID: 17)
device_ips_khobar.txt       # Khobar (projectID: 60)
device_ips_cs_test.txt      # Cloud Solutions test devices
one_device_ip.txt           # Single device for testing

Format (one IP per line):

10.70.6.143
10.70.6.144
10.70.6.145

🔥 Recent Critical Fixes (March 2026)

Problem: App Dying After 14 Hours

Symptoms:

  • App ran successfully for ~14 hours
  • Complete log silence after 8:36 AM
  • No crash logs, no lifecycle events
  • Required manual restart by user

Root Causes:

  1. Android OS killed app due to "idle" detection (even on AC power)
  2. Incorrect uptime tracking (calculated time since midnight, not app start)
  3. No foreground service protection
  4. No automatic failure recovery
  5. Missing kiosk display mode flags

Solutions Implemented

1. Fixed Uptime Tracking Bug

Before (WRONG):

// Calculated time since last midnight check
DateTime.now().difference(lastChecked).inHours
// At 6:31 AM, showed "6 hours" but app ran since midnight (6.5 hours)

After (CORRECT):

DateTime appStartTime = DateTime.now(); // Track at initialization

// Calculate actual uptime
final uptimeHours = DateTime.now().difference(appStartTime).inHours;
final uptimeMinutes = DateTime.now().difference(appStartTime).inMinutes % 60;

logToFile("Health check - App uptime: ${uptimeHours}h ${uptimeMinutes}m (started: $appStartTime)");

2. Android Kiosk Display Mode

File: android/app/src/main/kotlin/.../MainActivity.kt

Added:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    // Prevent Android from treating app as "idle"
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
    window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
    window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
    window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
    window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
    
    Log.d(TAG, "MainActivity created - Kiosk display mode active")
}

File: android/app/src/main/AndroidManifest.xml

Added:

<activity
    android:name=".MainActivity"
    android:keepScreenOn="true"
    android:screenOrientation="portrait">
</activity>

3. Health Check System

File: lib/view_models/screen_config_view_model.dart

Features:

Timer? _healthCheckTimer;
int healthCheckFailures = 0;

void _startHealthCheckTimer() {
  _healthCheckTimer = Timer.periodic(Duration(minutes: 5), (timer) {
    try {
      // 1. Calculate actual uptime
      final uptime = DateTime.now().difference(appStartTime);
      
      // 2. Log health check
      logToFile("Health check - App uptime: ${uptime.inHours}h ${uptime.inMinutes % 60}m");
      
      // 3. Check SignalR connection
      try {
        syncHubConnectionState();
        healthCheckFailures = 0; // Reset on success
      } catch (e) {
        healthCheckFailures++;
        
        // Auto-recover after 3 failures
        if (healthCheckFailures >= 3) {
          logToFile("CRITICAL: 3 consecutive failures - attempting recovery");
          await queuingViewModel.stopHubConnection();
          await Future.delayed(Duration(seconds: 2));
          await queuingViewModel.startHubConnection();
        }
      }
      
      // 4. Ensure wakelock enabled
      await WakelockPlus.enable();
      
      // 5. Log connection status
      logToFile("Hub connected: $isHubConnected, Internet: $isInternetConnected");
      
      // 6. Warn if running >60 hours
      if (uptime.inHours > 60) {
        logToFile("WARNING: App running for ${uptime.inHours} hours");
      }
      
      // 7. Keep UI thread active
      notifyListeners();
      
    } catch (e) {
      healthCheckFailures++;
      logError("Health check failed: $e");
    }
  });
}

4. Enhanced Lifecycle Tracking

File: lib/view_models/screen_config_view_model.dart

Added:

Future<void> onAppPaused() async {
  final uptimeHours = DateTime.now().difference(appStartTime).inHours;
  logToFile("[onAppPaused] - App uptime: ${uptimeHours}h - WARNING: App going to background!");
}

Future<void> onAppResumed() async {
  final uptimeHours = DateTime.now().difference(appStartTime).inHours;
  logToFile("[onAppResumed] - App uptime: ${uptimeHours}h");
  
  // Re-enable wakelock
  await WakelockPlus.enable();
  
  // Verify connections
  syncHubConnectionState();
}

Future<void> onAppDetached() async {
  final uptimeHours = DateTime.now().difference(appStartTime).inHours;
  logToFile("[onAppDetached] - App uptime: ${uptimeHours}h - CRITICAL: App being killed!");
}

Impact:

  • Now see EXACTLY when app goes to background or gets killed
  • If app stays alive correctly, should NEVER see these logs

5. SignalR Connection Improvements

File: lib/repositories/signalR_repo.dart

Enhanced:

connection = HubConnectionBuilder()
  .withUrl("$hubBaseURL?IPAddress=$deviceIp")
  .withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) // Custom retry delays
  .build();

connection!.serverTimeoutInMilliseconds = 120000; // 2 minutes
connection!.keepAliveIntervalInMilliseconds = 15000; // 15 seconds

// Manual reconnection on close
connection!.onclose((exception) async {
  reconnectAttempts++;
  if (reconnectAttempts < maxReconnectAttempts) {
    logToFile("SignalR reconnect attempt #$reconnectAttempts");
    await Future.delayed(Duration(seconds: 5));
    try {
      await connection!.start();
      logToFile("SignalR reconnected after disconnect");
      reconnectAttempts = 0;
    } catch (e) {
      logError("Reconnect failed: $e");
    }
  }
});

6. Clinic Prefix Feature

File: lib/models/global_config_model.dart

Added:

bool globalClinicPrefixReq = false;
bool clinicPrefixReq = true;

bool isClinicPrefixAdded(String ticketNo) {
  // Check for format: "XXX W-XX" (3 letters + space + W-)
  final hasClinicPrefix = RegExp(r'^[A-Za-z]{3} W-').hasMatch(ticketNo);
  return hasClinicPrefix;
}

// Returns true for: "ABC W-123", "FMC W-T-4"
// Returns false for: "W-123", "A W-123", "ABCD W-123"

Files Modified

  1. lib/view_models/screen_config_view_model.dart - Uptime, health checks, lifecycle
  2. lib/models/global_config_model.dart - Clinic prefix feature
  3. lib/repositories/signalR_repo.dart - Connection improvements
  4. android/app/src/main/kotlin/.../MainActivity.kt - Kiosk display mode
  5. android/app/src/main/AndroidManifest.xml - Activity flags

⚙️ Configuration System

IP-Based Configuration

Each device is identified by its IP address and receives specific configuration:

// Device IP detection (automatic or test)
if (useTestIP) {
  currentScreenIP = AppConstants.testIP; // "12.4.5.1"
} else {
  currentScreenIP = await connectivityService.getCurrentScreenIP();
}

// Fetch configuration
GlobalConfigurationsModel config = await screenDetailsRepo.getGlobalScreenConfigurations(
  ipAddress: currentScreenIP
);

Project ID Detection

// Takhasusi Main uses legacy orientation detection
value.isFromTakhasusiMain = value.projectID == AppConstants.projectIdTakhasusiMain; // 16

if (value.isFromTakhasusiMain) {
  log("✅ This device will use getTurnsByOrientationForOlderVersions()");
} else {
  log("✅ This device will use getTurnsByOrientationForNewVersions()");
}

Configuration Hierarchy

1. Device IP → API call → GlobalConfigurationsModel
2. Screen Type (waiting/room/kiosk/dashboard)
3. Queue Type (appointment/lab/rad/general)
4. Orientation (portrait/landscape)
5. Display Settings (max patients, delay)
6. Audio/Voice Settings (tone, voice, language)
7. Widget Visibility (weather, prayers, RSS)
8. Kiosk Queues & Languages

Dynamic Configuration Updates

Configuration can be updated via SignalR:

// Backend sends new config
Hub  SendQLineConfig  onHubConfigCall()

// Updates applied
globalConfigurationsModel = GlobalConfigurationsModel.fromJson(data);
updateGlobalConfigurationsModel(value: globalConfigurationsModel, needNotify: true);
getInfoWidgetsDetailsFromServer(); // Refresh widgets

⚠️ Known Issues & Solutions

Issue 1: App Dies After 14 Hours

Status: FIXED (March 2026)

Solution:

  • Added kiosk display mode flags
  • Implemented health check system
  • Fixed uptime tracking
  • Added lifecycle logging

Issue 2: Android Doze Mode

Status: ⚠️ REQUIRES DEVICE CONFIGURATION

Solution: Some Android devices (Xiaomi, Oppo, Vivo) have aggressive task killers:

Manual Steps:

  1. Settings > Apps > QLine
  2. Enable "Auto-start"
  3. Set Battery to "No restrictions"
  4. Disable "Battery optimization"

ADB Command:

adb shell dumpsys deviceidle whitelist +com.example.hmg_qline.hmg_qline

Issue 3: Memory Leaks (48+ Hour Uptime)

Status: MITIGATED

Solution:

  • Daily restart scheduled at 00:15 AM
  • Resource cleanup on lifecycle events
  • System.gc() calls at strategic points

Issue 4: TTS Stopping on Some Devices

Status: ⚠️ DEVICE-SPECIFIC

Solution:

  • Clear audio cache via clearAudioCache() method channel
  • Force stop TTS service: am force-stop com.google.android.tts
  • Restart app if TTS completely fails

Issue 5: SignalR Connection Drops

Status: MITIGATED

Solution:

  • Auto-reconnect with exponential backoff
  • Health check monitors connection every 5 min
  • Auto-restart after 3 consecutive failures

Issue 6: Exact Alarm Permission (Android 14+)

Status: ⚠️ REQUIRES USER PERMISSION

Solution:

// Check permission
if (!alarmManager.canScheduleExactAlarms()) {
  // Open settings
  Intent intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
  startActivity(intent)
}

📊 Testing & Monitoring

Testing Checklist

Deployment Testing

  • Build APK successfully
  • Deploy to test device via ADB
  • App starts automatically
  • Check log: "MainActivity created - Kiosk display mode active"
  • Verify health checks appear every 5 minutes

Short-Term Testing (1 Hour)

  • Uptime increments correctly (0h 5m → 0h 10m → 0h 15m → 1h 0m)
  • SignalR connection established
  • Hub connected: true in logs
  • Internet connected: true in logs
  • No [onAppPaused] or [onAppDetached] logs

Medium-Term Testing (24 Hours)

  • Health check logs continuous (no gaps)
  • App uptime reaches 24h
  • Hub connection stable
  • No lifecycle events (paused/detached)
  • Midnight refresh occurs (if widgets enabled)

Long-Term Testing (48+ Hours)

  • App runs past 14-hour checkpoint (critical)
  • App reaches 48h uptime
  • Check for >60h warning log
  • Scheduled restart at 00:15 works correctly
  • Auto-start on boot works (if device rebooted)

Network Resilience Testing

  • Disconnect WiFi for 10 minutes
  • Check log: "Hub disconnected"
  • Reconnect WiFi
  • Check log: "SignalR reconnect attempt #X"
  • Check log: "SignalR reconnected after disconnect"
  • Verify auto-recovery after 3 failures

Audio/Voice Testing

  • Audio tone plays when ticket called
  • TTS voice announcement speaks (if enabled)
  • Language switching works (English/Arabic)
  • Mute settings respected
  • Sequential calls work with delay

Kiosk Testing

  • Language selection screen appears
  • Queue selection shows all options
  • QR scanner works
  • Manual ID entry works
  • Ticket generation successful
  • Thank you screen displays
  • Auto-return to language selection after timeout

Monitoring Commands

Check if App Running

adb shell ps | grep hmg_qline

View Logcat (MainActivity)

adb logcat | grep MainActivity

View Logcat (SignalR)

adb logcat | grep SignalR

Check Device Idle State

adb shell dumpsys deviceidle

Check Battery Optimization Status

adb shell dumpsys deviceidle whitelist | grep hmg_qline

View Last 100 App Logs

adb logcat -t 100 | grep hmg_qline

Pull All Logs from Device

cd qline_scripts
sh extract_logs.sh -ip 10.70.6.143

🔧 Development Guidelines

Code Style

Naming Conventions

  • Classes: PascalCase (ScreenConfigViewModel)
  • Methods: camelCase (startHubConnection())
  • Variables: camelCase (currentScreenIP)
  • Constants: camelCase (static String baseUrl)
  • Enums: PascalCase (ScreenTypeEnum)
  • Files: snake_case (screen_config_view_model.dart)

File Organization

// 1. Imports
import 'dart:async';
import 'package:flutter/material.dart';

// 2. Class declaration
class MyClass extends ChangeNotifier {
  
  // 3. Private constants
  static const String _CONSTANT = "value";
  
  // 4. Dependencies
  final MyService myService;
  
  // 5. Constructor
  MyClass({required this.myService});
  
  // 6. Public properties
  String publicProperty = "";
  
  // 7. Private properties
  String _privateProperty = "";
  
  // 8. Public methods
  void publicMethod() {}
  
  // 9. Private methods
  void _privateMethod() {}
  
  // 10. Overrides
  @override
  void dispose() {
    super.dispose();
  }
}

Logging Best Practices

// Always include source file in logs
loggerService.logToFile(
  message: "Meaningful message with data: $value",
  source: "methodName -> file_name.dart",
  type: LogTypeEnum.data,
);

// Use appropriate log types
LogTypeEnum.data         // Health checks, state changes, events
LogTypeEnum.error        // Errors, exceptions
LogTypeEnum.connectivity // Network, SignalR events

// Include context in error logs
try {
  // code
} catch (e, stackTrace) {
  loggerService.logCrash(
    error: e,
    stackTrace: stackTrace,
    context: "methodName -> file_name.dart",
  );
}

Error Handling

// Always use try-catch for async operations
Future<void> myAsyncMethod() async {
  try {
    final result = await somethingRisky();
    // handle success
  } catch (e, stackTrace) {
    loggerService.logCrash(
      error: e,
      stackTrace: stackTrace,
      context: "myAsyncMethod -> my_file.dart",
    );
    // handle error gracefully
  }
}

// Provide fallbacks
final value = config.someValue ?? defaultValue;

// Validate before use
if (data != null && data.isNotEmpty) {
  processData(data);
}

State Management

// Always notifyListeners after state change
void updateValue(String newValue) {
  myValue = newValue;
  notifyListeners(); // Trigger UI rebuild
}

// Use getIt for singleton access
final viewModel = getIt.get<ScreenConfigViewModel>();

// Consumer for reactive UI
Consumer<ScreenConfigViewModel>(
  builder: (context, viewModel, child) {
    return Text(viewModel.currentScreenIP);
  },
)

API Calls

// Always check response for null
final response = await screenDetailsRepo.getConfig(ip: currentScreenIP);

if (response == null) {
  loggerService.logError("Config API returned null");
  updateViewState(ViewState.error);
  return;
}

// Use response
updateConfig(response);

Testing Additions

When adding new features:

  1. Add health check logs for monitoring
  2. Add error handling with proper logging
  3. Add null checks for all external data
  4. Test with network disconnection scenarios
  5. Test with long uptime (24+ hours)
  6. Update this documentation with new features

📚 Additional Documentation Files

  • README.md - Basic project information
  • QUICK_SUMMARY.md - Quick reference for March 2026 changes
  • CHANGES_EXPLANATION.md - Detailed explanation of fixes
  • AC_POWERED_KIOSK_SUMMARY.md - Kiosk-specific documentation
  • PROJECT_CONTEXT.md - This file (comprehensive context)

🔄 Version History

Version Date Changes
9.4 July 2026 Current production version
9.2 March 2026 Critical fixes: uptime tracking, health checks, kiosk flags
9.0 February 2026 SignalR improvements, lifecycle tracking
8.x 2025 Previous stable versions

📞 Emergency Contacts & Procedures

If App Dies Again

Collect Information:

  1. Last 200 lines of logs before app stopped
  2. Android version and device model
  3. Screenshot of Apps > QLine > Battery settings
  4. Output of: adb shell dumpsys deviceidle whitelist

Immediate Actions:

  1. Check device battery optimization settings
  2. Verify app is whitelisted from doze mode
  3. Check if exact alarm permission granted (Android 12+)
  4. Review logs for [onAppPaused] or [onAppDetached]
  5. Check for lifecycle events before silence

Advanced Troubleshooting:

# Force whitelist from doze
adb shell dumpsys deviceidle whitelist +com.example.hmg_qline.hmg_qline

# Check app status
adb shell ps | grep hmg_qline

# Check if app is optimized
adb shell dumpsys battery | grep hmg_qline

# Force prevent background kill
adb shell cmd appops set com.example.hmg_qline.hmg_qline RUN_IN_BACKGROUND allow

🎯 Future Improvements

Planned Enhancements

  1. Remote Monitoring: Send health status to backend server
  2. Watchdog Service: External process that restarts app if killed
  3. Scheduled Restart: Auto-restart at 3 AM daily (already implemented at 00:15)
  4. Memory Profiling: Track and log memory usage trends
  5. Firebase Crashlytics: Cloud-based crash analytics
  6. Network Quality Metrics: Log ping times and bandwidth

Technical Debt

  1. Clean up commented code in MainActivity.kt
  2. Add unit tests for ViewModels
  3. Add integration tests for SignalR connection
  4. Implement proper i18n instead of hardcoded strings
  5. Refactor large ViewModels into smaller components

📖 Glossary

  • AC-Powered Kiosk: Android device permanently plugged into power, not battery-powered
  • App Standby Buckets: Android 9+ feature that restricts background apps
  • Doze Mode: Android 6+ battery optimization that restricts background processing
  • Health Check: Periodic verification that app is functioning correctly
  • Hub: SignalR WebSocket server for real-time communication
  • Kiosk Mode: Display mode where app runs continuously without user intervention
  • QType: Queue type (appointment, lab, rad, general)
  • SignalR: Real-time communication library using WebSockets
  • TTS: Text-to-Speech for voice announcements
  • Uptime: Duration the app has been running since last start
  • Wakelock: Android API to prevent device from sleeping

END OF CONTEXT DOCUMENTATION

This document should be updated whenever significant changes are made to the project. Last updated by: AI Assistant Last update date: July 30, 2026