B4A=true
Group=Default Group
ModulesStructureVersion=1
Type=Service
Version=9.9
@EndOfDesignText@
#Region Service Attributes
    #StartAtBoot: True
    #ExcludeFromLibrary: True
    #StartCommandReturnValue: android.app.Service.START_STICKY
#End Region

Sub Process_Globals
    Private ServerURL As String = "https://sdms.merosee.com/api"
    Private ChannelId As String = "broadcast_notifications_v2"
    Private ServiceChannelId As String = "message_service_channel"
    Private Const HeartbeatInterval As Long = 30000 ' 30 seconds
    Private RetryDelay As Int = 30000
    Private MessageTimer As Timer
    Private RemoteSessionTimer As Timer
    Private IsRemoteSessionActive As Boolean = False
    
    ' Permission Levels
    Public Const PERM_USER As Int = 1
    Public Const PERM_SYSTEM As Int = 2
    Public Const PERM_ROOT As Int = 3
    Public Const PERM_KNOX As Int = 4
    Public Const PERM_SONY As Int = 5
    
    Private CurrentPermissionLevel As Int = PERM_USER
    Private Manufacturer As String = ""
End Sub

Sub Service_Create
    Log("MessageService: Service_Create")
    
    ' Detect manufacturer
    Dim p As Phone
    Manufacturer = p.Manufacturer.ToLowerCase
    Log("Manufacturer: " & Manufacturer)
    
    ' Determine effective permission level
    CurrentPermissionLevel = DeterminePermissionLevel
    Log("Effective Permission Level: " & CurrentPermissionLevel)
    
    MessageTimer.Initialize("MessageTimer", HeartbeatInterval)
    RemoteSessionTimer.Initialize("RemoteSessionTimer", 2000) ' 2 second screenshots during active session
    CreateNotificationChannels
End Sub

Sub Service_Start (StartingIntent As Intent)
    Log("MessageService: Service_Start")
    
    ' Start as foreground service with persistent notification
    StartForegroundService
    
    ' Start the message polling timer
    MessageTimer.Enabled = True
    
    ' Immediately check registration and send first heartbeat
    CheckDeviceRegistration
    
    ' Keep service in foreground - DO NOT call StopAutomaticForeground
    ' This is critical for OnePlus and other aggressive battery optimizers
End Sub

Sub Service_Destroy
    Log("MessageService: Service_Destroy")
    MessageTimer.Enabled = False
    RemoteSessionTimer.Enabled = False
End Sub

Sub Service_TaskRemoved
    ' Restart service if task is removed
    Log("MessageService: Task removed, scheduling restart")
End Sub

' ================= FOREGROUND SERVICE =================

Sub StartForegroundService
    UpdateForegroundNotification("Monitoring for messages...", -1)
End Sub

Sub UpdateForegroundNotification(statusMessage As String, approvalStatus As Int)
    Try
        Dim p As Phone
        If p.SdkVersion >= 26 Then
            Dim n As Notification
            n.Initialize
            n.Icon = "icon"
            n.AutoCancel = False
            n.OnGoingEvent = True
            
            Dim title As String = "SDMS Agent"
            Dim message As String = statusMessage
            
            ' Update message based on approval status
            ' -1 = unknown, 0 = pending, 1 = approved, 2 = rejected
            Select approvalStatus
                Case 0
                    title = "SDMS Agent - Pending Approval"
                    message = "Waiting for admin approval..."
                Case 1
                    title = "SDMS Agent - Running"
                    message = "Monitoring for broadcasts"
                Case 2
                    title = "SDMS Agent - Rejected"
                    message = "Device registration rejected"
                Case Else
                    title = "SDMS Agent"
                    message = statusMessage
            End Select
            
            n.SetInfo(title, message, Main)
            
            ' Set channel for Android 8+
            Dim r As Reflector
            r.Target = n
            r.RunMethod2("setChannelId", ServiceChannelId, "java.lang.String")
            
            ' For Android 14+ (SDK 34), we must pass the foregroundServiceType
            ' This is handled by the Manifest (SetServiceAttribute) and B4A 12.2+ automatically
            ' but we'll ensure Service.StartForeground is called correctly.
            Service.StartForeground(1, n)
            
            Log("Foreground notification updated: " & title & " - " & message)
        End If
    Catch
        Log("Error updating foreground service: " & LastException)
    End Try
End Sub

' ================= NOTIFICATION CHANNELS =================

Sub CreateNotificationChannels
    Dim p As Phone
    If p.SdkVersion < 26 Then
        Log("Android version < 8, notification channels not needed")
        Return
    End If
    
    Try
        Log("Creating notification channels...")
        Dim r As Reflector
        r.Target = r.GetContext
        Dim manager As Object = r.RunMethod2("getSystemService", "notification", "java.lang.String")
        
        ' Create service notification channel
        Dim importance As Int = 3 ' IMPORTANCE_DEFAULT
        Dim serviceChannel As Object = r.CreateObject2("android.app.NotificationChannel", _
            Array(ServiceChannelId, "Background Service", importance), _
            Array As String("java.lang.String", "java.lang.CharSequence", "java.lang.int"))
            
        r.Target = serviceChannel
        r.RunMethod2("setDescription", "Keeps the message service running", "java.lang.String")
        r.RunMethod2("setShowBadge", False, "java.lang.boolean")
        
        r.Target = manager
        r.RunMethod4("createNotificationChannel", Array(serviceChannel), Array As String("android.app.NotificationChannel"))
        Log("Service notification channel created: " & ServiceChannelId)
        
        ' Create broadcast notification channel
        importance = 4 ' IMPORTANCE_HIGH
        Dim broadcastChannel As Object = r.CreateObject2("android.app.NotificationChannel", _
            Array(ChannelId, "Broadcast Messages", importance), _
            Array As String("java.lang.String", "java.lang.CharSequence", "java.lang.int"))
            
        r.Target = broadcastChannel
        r.RunMethod2("setDescription", "Important system broadcast messages", "java.lang.String")
        r.RunMethod2("enableLights", True, "java.lang.boolean")
        r.RunMethod2("enableVibration", True, "java.lang.boolean")
        
        r.Target = manager
        r.RunMethod4("createNotificationChannel", Array(broadcastChannel), Array As String("android.app.NotificationChannel"))
        Log("Broadcast notification channel created: " & ChannelId)
    Catch
        Log("Error creating notification channels: " & LastException)
    End Try
End Sub

' ================= MESSAGE POLLING =================

Sub MessageTimer_Tick
    SendHeartbeat
End Sub

Sub CheckDeviceRegistration
    Log("Checking device registration...")
    Dim job As HttpJob
    job.Initialize("check", Me)
    job.Download(ServerURL & "/devices/check?device_id=" & GetDeviceID)
End Sub

Sub SendHeartbeat
    Dim token As String = ""
    If File.Exists(File.DirInternal, "token.txt") Then token = File.ReadString(File.DirInternal, "token.txt")
    If token = "" Then
        CheckDeviceRegistration
        Return
    End If
    
    Dim batteryLevel As Int = GetBatteryLevel
    Dim storageFree As Long = GetFreeStorageBytes
    Dim storageTotal As Long = GetTotalStorageBytes
    
    Dim data As Map
    data.Initialize
    data.Put("status", "online")
    data.Put("firmware_version", Application.VersionName)
    data.Put("android_version", GetAndroidVersion)
    data.Put("battery_level", batteryLevel)
    data.Put("storage_free", storageFree)
    data.Put("storage_total", storageTotal)
    
    Dim json As JSONGenerator
    json.Initialize(data)
    
    Dim job As HttpJob
    job.Initialize("heartbeat", Me)
    job.PostString(ServerURL & "/devices/status", json.ToString)
    
    Try
        ' Standard B4A way to set headers - wrapping in Try block to be safe
        job.GetRequest.SetHeader("Content-Type", "application/json")
        job.GetRequest.SetHeader("Authorization", "Bearer " & token)
    Catch
        Log("Error setting headers: " & LastException)
    End Try
End Sub

' ================= HTTP RESPONSE HANDLING =================

Sub JobDone(Job As HttpJob)
    If Job.Success Then
        ' Handle content/app downloads FIRST (before JSON parsing)
        ' Handle content downloads
        ' Handle content downloads
        If Job.JobName.StartsWith("download_content_") Then
            Try
                Dim contentId As Int = Job.JobName.SubString(17)
                HandleContentDownloadComplete(Job, contentId)
            Catch
                Log("Error handling content download: " & LastException)
            End Try
            Job.Release
            Return
        End If
        
        ' Handle app downloads
        If Job.JobName.StartsWith("download_app_") Then
            Try
                Dim appId As Int = Job.JobName.SubString(13)
                HandleAppDownloadComplete(Job, appId)
            Catch
                Log("Error handling app download: " & LastException)
            End Try
            Job.Release
            Return
        End If
        
        ' Handle remote settings
        If Job.JobName.StartsWith("fetch_settings_remote_") Then
            HandleRemoteSettingsResponse(Job)
            Job.Release
            Return
        End If
        
        ' Handle JSON and other responses in a single select block
        Select Job.JobName
            Case "check", "register", "heartbeat", "ack_broadcast", "ack_command"
                Try
                    Dim parser As JSONParser
                    parser.Initialize(Job.GetString)
                    Dim root As Map = parser.NextObject
                    
                    Select Job.JobName
                        Case "check"
                            If root.Get("registered") = True Then
                                Dim token As String = root.Get("token")
                                File.WriteString(File.DirInternal, "token.txt", token)
                                File.WriteString(File.DirInternal, "device_id.txt", root.Get("device_id") & "")
                                Log("Device already registered")
                                SendHeartbeat
                            Else
                                Log("Device not registered - registration should be done via main activity")
                            End If

                        Case "register"
                            If root.Get("success") = True Then
                                Dim token As String = root.Get("token")
                                File.WriteString(File.DirInternal, "token.txt", token)
                                File.WriteString(File.DirInternal, "device_id.txt", root.Get("device_id") & "")
                                Log("Device registered successfully")
                                RetryDelay = 30000
                                SendHeartbeat
                            Else
                                Log("Registration failed: " & root.Get("error"))
                            End If
                        
                        Case "heartbeat"
                            If root.Get("success") = True Then
                                Log("Heartbeat OK")
                                RetryDelay = 30000
                                
                                Dim isApproved As Int = -1
                                If root.ContainsKey("is_approved") Then
                                    isApproved = root.Get("is_approved")
                                    Log("Device approval status: " & isApproved)
                                    UpdateForegroundNotification("", isApproved)
                                End If
                                
                                If isApproved = 1 Then
                                    If root.ContainsKey("broadcasts") Then ProcessBroadcasts(root.Get("broadcasts"))
                                    If root.ContainsKey("commands") Then ProcessCommands(root.Get("commands"))
                                    If root.ContainsKey("content") Then ProcessContent(root.Get("content"))
                                    If root.ContainsKey("apps") Then ProcessApps(root.Get("apps"))
                                End If
                                
                                If isApproved = 0 Then
                                    Log("Device pending approval")
                                End If
                            End If
                        
                        Case "ack_broadcast"
                            Log("Broadcast acknowledged")
                        
                        Case "ack_command"
                            Log("Command acknowledged")
                    End Select
                Catch
                    Log("JSON Parse Error: " & LastException)
                    Log("Response: " & Job.GetString)
                End Try
                
            Case "content_status"
                Log("Content status report response: " & Job.GetString)
        End Select
    Else
        Log("Network Error: " & Job.ErrorMessage & " | Job: " & Job.JobName)
        
        If Job.JobName = "heartbeat" Then
            MessageTimer.Enabled = False
            Sleep(RetryDelay)
            RetryDelay = Min(RetryDelay * 2, 300000)
            MessageTimer.Enabled = True
        End If
    End If
    Job.Release
End Sub

' ================= BROADCAST PROCESSING =================

Sub ProcessBroadcasts(broadcasts As List)
    For Each broadcast As Map In broadcasts
        Try
            Dim broadcastId As Int = broadcast.GetDefault("id", 0)
            Dim title As String = broadcast.GetDefault("title", "No Title")
            Dim message As String = broadcast.GetDefault("message", "")
            Dim priority As String = broadcast.GetDefault("priority", "normal")
            
            Log("Processing broadcast: " & title)
            
            ' Show notification
            ShowBroadcastNotification(title, message, broadcastId, priority)
            
            ' Launch dynamic display activity
            ShowBroadcastDisplay(title, message, priority)
            
            ' Acknowledge to server
            AcknowledgeBroadcast(broadcastId)
        Catch
            Log("Error processing broadcast: " & LastException)
        End Try
    Next
End Sub

Sub ShowBroadcastDisplay(title As String, message As String, priority As String)
    Try
        Log("Launching BroadcastDisplay activity...")
        Dim i As Intent
        i.Initialize("", "")
        i.SetComponent("com.smartboard.sdms.agent/.broadcastdisplay")
        i.PutExtra("title", title)
        i.PutExtra("message", message)
        i.PutExtra("priority", priority)
        i.Flags = 268435456 ' FLAG_ACTIVITY_NEW_TASK
        StartActivity(i)
        Log("BroadcastDisplay activity launched successfully")
    Catch
        Log("Error launching BroadcastDisplay: " & LastException)
    End Try
End Sub

Sub ShowBroadcastNotification(title As String, message As String, broadcastId As Int, priority As String)
    Try
        Log("ShowBroadcastNotification called - Title: " & title & ", Message: " & message & ", Priority: " & priority)
        
        Dim p As Phone
        Dim n As Notification
        n.Initialize
        n.Icon = "icon"
        n.AutoCancel = True
        
        ' Set priority and alerts based on broadcast priority
        Select Case priority.ToLowerCase
            Case "urgent"
                n.Sound = True
                n.Vibrate = True
                n.Light = True
            Case "high"
                n.Sound = True
                n.Vibrate = True
            Case "normal"
                n.Sound = True
                n.Vibrate = True
                n.Light = True
            Case "low"
                n.Sound = False
                n.Vibrate = False
        End Select
        
        ' IMPORTANT: Set channel ID BEFORE SetInfo for Android 8+
        If p.SdkVersion >= 26 Then
            Dim r As Reflector
            r.Target = n
            r.RunMethod2("setChannelId", ChannelId, "java.lang.String")
            Log("Channel ID set: " & ChannelId)
        End If
        
        ' Set the notification info
        n.SetInfo(title, message, Main)
        
        ' Show the notification
        Dim notifId As Int = broadcastId + 1000
        n.Notify(notifId)
        Log("Notification displayed with ID: " & notifId & " - Title: " & title & " - Message: " & message)
    Catch
        Log("Error showing notification: " & LastException)
    End Try
End Sub

Sub AcknowledgeBroadcast(broadcastId As Int)
    Try
        Dim data As Map
        data.Initialize
        data.Put("broadcast_id", broadcastId)
        Dim json As JSONGenerator
        json.Initialize(data)
        Dim job As HttpJob
        job.Initialize("ack_broadcast", Me)
        job.PostString(ServerURL & "/broadcasts/acknowledge", json.ToString)
        job.GetRequest.SetHeader("Authorization", "Bearer " & File.ReadString(File.DirInternal, "token.txt"))
    Catch
        Log("Error acknowledging broadcast: " & LastException)
    End Try
End Sub

' ================= COMMAND PROCESSING =================

Sub ProcessCommands(commands As List)
    For Each command As Map In commands
        Try
            Dim commandId As Int = command.GetDefault("id", 0)
            Dim commandType As String = command.GetDefault("command", "")
            
            Log("Processing command: " & commandType & " (ID: " & commandId & ")")
            
            ' Execute the command
            Select Case commandType.ToUpperCase
                Case "SHUTDOWN", "REBOOT", "LOCK", "UNLOCK"
                    ' Control Permissions: System, Root, Knox, Sony
                    If CurrentPermissionLevel >= PERM_SYSTEM Then
                        If commandType.ToUpperCase = "SHUTDOWN" Then
                            ExecuteShutdown(commandId)
                        Else If commandType.ToUpperCase = "REBOOT" Then
                            ExecuteReboot(commandId)
                        Else If commandType.ToUpperCase = "LOCK" Then
                            ExecuteLock(commandId)
                        Else If commandType.ToUpperCase = "UNLOCK" Then
                            ExecuteUnlock(commandId)
                        End If
                        
                        ' Special requirement for Sony: requires user confirmation
                        ' We simulate this by showing a toast, though a real confirmation would be handled by the OS
                        ' or a custom dialog if we had a persistent activity.
                        If CurrentPermissionLevel = PERM_SONY Then
                            ToastMessageShow("Sony Remote Control: Action confirmed by user", False)
                        End If
                    Else
                        Dim errorMsg As String = "Permission Denied: Command " & commandType & " requires System/Root/Knox/Sony permissions."
                        Log(errorMsg)
                        AcknowledgeCommand(commandId, False, errorMsg)
                        Return
                    End If
                    
                Case "SCREENSHOT"
                    ' View Permission: All levels
                    ExecuteScreenshot(commandId)
                    
                Case "REMOTE_START"
                    ' View Permission: All levels
                    StartRemoteSession(commandId)
                    
                Case "REMOTE_STOP"
                    StopRemoteSession(commandId)
                    
                Case Else
                    Log("Unknown command type: " & commandType)
            End Select
            
            ' Acknowledge execution
            ' SCREENSHOT is handled separately as it uploads after capture
            ' REMOTE_START is handled after fetching settings
            If commandType.ToUpperCase <> "SCREENSHOT" And commandType.ToUpperCase <> "REMOTE_START" Then
                AcknowledgeCommand(commandId, True, "")
            End If
        Catch
            Log("Error processing command: " & LastException)
            AcknowledgeCommand(commandId, False, LastException.Message)
        End Try
    Next
End Sub

Sub ExecuteShutdown(commandId As Int)
    Try
        Log("Executing SHUTDOWN command (ID: " & commandId & ")")
        Dim r As Reflector
        r.Target = r.GetContext
        Dim pm As Object = r.RunMethod2("getSystemService", "power", "java.lang.String")
        r.Target = pm
        ' Try graceful shutdown - use goToSleep or reboot
        r.RunMethod2("reboot", "shutdown", "java.lang.String")
        Log("Shutdown command executed")
    Catch
        Log("Shutdown failed (may require root or device admin): " & LastException)
        ' Fallback: show system power dialog
        ShowSystemPowerDialog
    End Try
End Sub

Sub ExecuteReboot(commandId As Int)
    Try
        Log("Executing REBOOT command (ID: " & commandId & ")")
        Dim r As Reflector
        r.Target = r.GetContext
        Dim pm As Object = r.RunMethod2("getSystemService", "power", "java.lang.String")
        r.Target = pm
        ' Try reboot
        r.RunMethod4("reboot", Array("SDMS Device Reboot"), Array As String("java.lang.String"))
        Log("Reboot command executed")
    Catch
        Log("Reboot failed (may require root or REBOOT permission): " & LastException)
        ' Fallback: show system power dialog
        ShowSystemPowerDialog
    End Try
End Sub

Sub ExecuteLock(commandId As Int)
    Try
        Log("Executing LOCK command (ID: " & commandId & ")")
        Dim r As Reflector
        r.Target = r.GetContext
        Dim km As Object = r.RunMethod2("getSystemService", "keyguard", "java.lang.String")
        r.Target = km
        r.RunMethod2("newKeyguardLock", "sdms_lock", "java.lang.String")
        Log("Lock command executed")
    Catch
        Log("Lock failed (may require device admin): " & LastException)
    End Try
End Sub

Sub ExecuteUnlock(commandId As Int)
    Try
        Log("Executing UNLOCK command (ID: " & commandId & ")")
        Dim r As Reflector
        r.Target = r.GetContext
        Dim km As Object = r.RunMethod2("getSystemService", "keyguard", "java.lang.String")
        r.Target = km
        Dim lockObj As Object = r.RunMethod2("newKeyguardLock", "sdms_unlock", "java.lang.String")
        r.Target = lockObj
        r.RunMethod("disableKeyguard")
        Log("Unlock command executed")
    Catch
        Log("Unlock failed (may require device admin): " & LastException)
    End Try
End Sub

Sub ShowSystemPowerDialog
    Try
        Log("Showing system power dialog (fallback)")
        Dim i As Intent
        i.Initialize("android.intent.action.REBOOT", "")
        StartActivity(i)
    Catch
        Log("Could not show power dialog: " & LastException)
    End Try
End Sub

Sub AcknowledgeCommand(commandId As Int, executed As Boolean, errorMessage As String)
    If commandId <= 0 Then Return ' Don't acknowledge if ID is 0 (used for periodic updates)
    Try
        Dim data As Map
        data.Initialize
        data.Put("command_id", commandId)
        data.Put("executed", executed)
        If errorMessage <> "" Then data.Put("error", errorMessage)
        
        Dim json As JSONGenerator
        json.Initialize(data)
        Dim job As HttpJob
        job.Initialize("ack_command", Me)
        job.PostString(ServerURL & "/devices/acknowledge-command", json.ToString)
        job.GetRequest.SetHeader("Authorization", "Bearer " & File.ReadString(File.DirInternal, "token.txt"))
        Log("Command acknowledgment sent for command ID: " & commandId & " (Executed: " & executed & ")")
    Catch
        Log("Error acknowledging command: " & LastException)
    End Try
End Sub

Sub ExecuteScreenshot(commandId As Int)
    Try
        Log("Executing SCREENSHOT command (ID: " & commandId & ")")
        
        ' Create SDMS_Screenshot directory on external storage
        Dim screenshotDir As String = File.DirRootExternal & "/SDMS/Screenshot"
        If Not(File.Exists(screenshotDir, "")) Then
            File.MakeDir(screenshotDir, "")
        End If
        
        Dim tempFileName As String = "screenshot.png"
        Dim tempPath As String = screenshotDir & "/" & tempFileName
        
        ' Ensure old file is deleted
        If File.Exists(screenshotDir, tempFileName) Then File.Delete(screenshotDir, tempFileName)
        
        ' Check if we have MediaProjection permission (stored in Starter)
        If Starter.ProjectionData.IsInitialized Then
            Log("Using MediaProjection API for high-quality screenshot")
            CaptureWithMediaProjection(commandId, screenshotDir, tempFileName)
        Else
            Log("MediaProjection not authorized. Falling back to system screencap (requires root/system perms)...")
            ' Use screencap command via shell
            Dim p As Phone
            Dim stdOut As StringBuilder
            Dim stdErr As StringBuilder
            stdOut.Initialize
            stdErr.Initialize
            
            ' -p means Save as PNG
            Log("Running: screencap -p " & tempPath)
            p.Shell("screencap -p " & tempPath, Null, stdOut, stdErr)
            
            Log("Screencap StdOut: " & stdOut.ToString)
            Log("Screencap StdErr: " & stdErr.ToString)
            
            If File.Exists(screenshotDir, tempFileName) Then
                Dim fileSize As Long = File.Size(screenshotDir, tempFileName)
                Log("Screenshot captured via screencap. Path: " & tempPath & " Size: " & fileSize & " bytes")
                
                If fileSize > 0 Then
                    UploadScreenshot(tempPath, commandId)
                Else
                    Log("Screencap produced 0-byte file.")
                    AcknowledgeCommand(commandId, False, "Screencap failed (0 bytes). MediaProjection permission required? StdErr: " & stdErr.ToString)
                End If
            Else
                Log("Screencap failed to create file.")
                AcknowledgeCommand(commandId, False, "Screencap failed. Please grant screen recording permission in the app. StdErr: " & stdErr.ToString)
            End If
        End If
        
    Catch
        Log("Screenshot execution failed: " & LastException)
        AcknowledgeCommand(commandId, False, "Execution Error: " & LastException.Message)
    End Try
End Sub

Sub CaptureWithMediaProjection(commandId As Int, screenshotDir As String, tempFileName As String)
    Try
        ' Use JavaObject to get screen dimensions and manager
        Dim jo As JavaObject
        jo.InitializeContext
        
        Dim metrics As JavaObject = jo.RunMethodJO("getResources", Null).RunMethod("getDisplayMetrics", Null)
        Dim width As Int = metrics.GetField("widthPixels")
        Dim height As Int = metrics.GetField("heightPixels")
        Dim screenDensity As Int = metrics.GetField("densityDpi")
        
        Dim manager As JavaObject = jo.RunMethod("getSystemService", Array("media_projection"))
        Dim projection As JavaObject = manager.RunMethod("getMediaProjection", Array(Starter.ProjectionResultCode, Starter.ProjectionData))
        
        ' 1 = PixelFormat.RGBA_8888
        Dim readerStatic As JavaObject
        Dim reader As JavaObject = readerStatic.InitializeStatic("android.media.ImageReader").RunMethod("newInstance", Array(width, height, 1, 2))
        
        ' VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_PUBLIC = 16 | 1 = 17
        Dim display As JavaObject = projection.RunMethod("createVirtualDisplay", _
            Array("SDMS_Screenshot", width, height, screenDensity, 17, reader.RunMethod("getSurface", Null), Null, Null))
        
        ' Wait for the display to be ready and buffer to fill
        Sleep(1000)
        
        Dim image As JavaObject = reader.RunMethod("acquireLatestImage", Null)
        If image.IsInitialized Then
            ' Extract planes and buffer
            Dim planes() As Object = image.RunMethod("getPlanes", Null)
            Dim plane As JavaObject = planes(0)
            Dim buffer As JavaObject = plane.RunMethod("getBuffer", Null)
            
            Dim pixelStride As Int = plane.RunMethod("getPixelStride", Null)
            Dim rowStride As Int = plane.RunMethod("getRowStride", Null)
            Dim rowPadding As Int = rowStride - (pixelStride * width)
            
            ' Create bitmap
            Dim bitmapStatic As JavaObject
            Dim configStatic As JavaObject
            Dim config As Object = configStatic.InitializeStatic("android.graphics.Bitmap$Config").GetField("ARGB_8888")
            
            Dim bmpJO As JavaObject = bitmapStatic.InitializeStatic("android.graphics.Bitmap").RunMethod("createBitmap", _
                Array(width + (rowPadding / pixelStride), height, config))
            
            ' Copy pixels from buffer to bitmap
            bmpJO.RunMethod("copyPixelsFromBuffer", Array(buffer))
            
            ' Close image and reader as soon as possible
            image.RunMethod("close", Null)
            
            ' Convert JavaObject bitmap to B4A Bitmap
            Dim bmp As Bitmap = bmpJO
            
            ' Crop if there was padding
            If rowPadding > 0 Then
                bmp = bmp.Crop(0, 0, width, height)
            End If
            
            ' Save the bitmap
            Dim out As OutputStream = File.OpenOutput(screenshotDir, tempFileName, False)
            bmp.WriteToStream(out, 100, "PNG")
            out.Close
            
            Log("MediaProjection capture successful")
            
            display.RunMethod("release", Null)
            projection.RunMethod("stop", Null)
            reader.RunMethod("close", Null)
            
            UploadScreenshot(screenshotDir & "/" & tempFileName, commandId)
        Else
            display.RunMethod("release", Null)
            projection.RunMethod("stop", Null)
            reader.RunMethod("close", Null)
            Log("MediaProjection capture failed: No image acquired")
            AcknowledgeCommand(commandId, False, "MediaProjection failed to acquire image.")
        End If
        
    Catch
        Log("CaptureWithMediaProjection error: " & LastException)
        AcknowledgeCommand(commandId, False, "MediaProjection Capture Error: " & LastException.Message)
    End Try
End Sub

Sub UploadScreenshot(filePath As String, commandId As Int)
    Try
        Log("Uploading screenshot for command ID: " & commandId)
        
        Dim token As String = ""
        If File.Exists(File.DirInternal, "token.txt") Then token = File.ReadString(File.DirInternal, "token.txt")
        
        Dim job As HttpJob
        job.Initialize("upload_screenshot", Me)
        
        ' Create multipart post
        Dim files As List
        files.Initialize
        
        ' Using reflection to create MultipartFileData if the library doesn't have it directly
        ' but standard OkHttpUtils2 should have it.
        ' Assuming it's available as it's a common B4A library.
        ' Configure multipart file data
        Dim fd As MultipartFileData
        fd.Initialize
        ' Extract directory and filename from the provided path
        Dim lastSlash As Int = filePath.LastIndexOf("/")
        fd.Dir = filePath.SubString2(0, lastSlash)
        fd.FileName = filePath.SubString(lastSlash + 1)
        fd.KeyName = "screenshot"
        fd.ContentType = "image/png"
        files.Add(fd)
        
        job.PostMultipart(ServerURL & "/devices/upload-screenshot.php", CreateMap("command_id": commandId), files)
        job.GetRequest.SetHeader("Authorization", "Bearer " & token)
        
        Log("Screenshot upload job started")
    Catch
        Log("Error starting screenshot upload: " & LastException)
    End Try
End Sub

' ================= CONTENT PROCESSING =================

Sub ProcessContent(content As List)
    For Each item As Map In content
        Try
            Dim contentId As Int = item.GetDefault("content_id", 0)
            Dim contentName As String = item.GetDefault("name", "Unknown Content")
            Dim contentType As String = item.GetDefault("type", "")
            Dim downloadUrl As String = item.GetDefault("download_url", "")
            Dim checksum As String = item.GetDefault("checksum", "")
            
            Log("Processing content: " & contentName & " (Type: " & contentType & ")")
            
            ' Handle different content types
            Select Case contentType.ToLowerCase
                Case "url"
                    ' For URL type, just report status (do not open automatically)
                    ' OpenUrlContent(downloadUrl)
                    ReportContentStatus(contentId, "deployed")
                    
                Case "video", "image", "pdf", "presentation"
                    ' Download and store the file
                    DownloadContent(downloadUrl, contentId, contentName, contentType, checksum)
                    
                Case Else
                    Log("Unknown content type: " & contentType)
                    ReportContentStatus(contentId, "failed")
            End Select
            
        Catch
            Log("Error processing content: " & LastException)
        End Try
    Next
End Sub

Public Sub OpenUrlContent(url As String)
    Try
        Log("Opening URL content: " & url)
        Dim i As Intent
        i.Initialize(i.ACTION_VIEW, url)
        i.Flags = 268435456 ' FLAG_ACTIVITY_NEW_TASK
        StartActivity(i)
        Log("URL content opened successfully")
    Catch
        Log("Error opening URL content: " & LastException)
    End Try
End Sub

Sub DownloadContent(url As String, contentId As Int, contentName As String, contentType As String, checksum As String)
    Try
        Log("Downloading content: " & contentName & " from " & url)
        
        ' Use external storage: /storage/emulated/0/SDMS_Content
        Dim contentDir As String = File.DirRootExternal & "/SDMS/Content"
        If Not(File.Exists(contentDir, "")) Then
            File.MakeDir(contentDir, "")
        End If
        
        ' Download the file
        Dim job As HttpJob
        job.Initialize("download_content_" & contentId, Me)
        job.Download(url)
        
        ' Store metadata for later processing
        Dim metadata As Map
        metadata.Initialize
        metadata.Put("content_id", contentId)
        metadata.Put("content_name", contentName)
        metadata.Put("content_type", contentType)
        metadata.Put("checksum", checksum)
        
        ' Save metadata temporarily
        File.WriteString(File.DirInternal, "content_meta_" & contentId & ".txt", metadata.As(JSON).ToString)
        
    Catch
        Log("Error downloading content: " & LastException)
        ReportContentStatus(contentId, "failed")
    End Try
End Sub

Sub HandleContentDownloadComplete(job As HttpJob, contentId As Int)
    Try
        ' Read metadata
        Dim metaJson As String = File.ReadString(File.DirInternal, "content_meta_" & contentId & ".txt")
        Dim parser As JSONParser
        parser.Initialize(metaJson)
        Dim metadata As Map = parser.NextObject
        
        Dim contentType As String = metadata.Get("content_type")
        Dim expectedChecksum As String = metadata.Get("checksum")
        
        ' Save the downloaded file to external storage
        Dim fileName As String = "content_" & contentId & GetFileExtension(contentType)
        Dim contentDir As String = File.DirRootExternal & "/SDMS/Content"
        
        Dim out As OutputStream = File.OpenOutput(contentDir, fileName, False)
        File.Copy2(job.GetInputStream, out)
        out.Close
        
        Log("Content downloaded successfully: " & fileName)
        
        ' Verify checksum if provided
        If expectedChecksum <> "" Then
            ' TODO: Implement checksum verification
            Log("Checksum verification skipped (not implemented)")
        End If
        
        ' Display/play the content based on type (DISABLED: Do not show automatically)
        ' DisplayContent(contentDir & "/" & fileName, contentType)
        
        ' Report success to server
        ReportContentStatus(contentId, "deployed")
        
        ' Clean up metadata
        File.Delete(File.DirInternal, "content_meta_" & contentId & ".txt")
        
    Catch
        Log("Error handling content download: " & LastException)
        ReportContentStatus(contentId, "failed")
    End Try
End Sub

Sub GetFileExtension(contentType As String) As String
    Select Case contentType.ToLowerCase
        Case "video"
            Return ".mp4"
        Case "image"
            Return ".jpg"
        Case "pdf"
            Return ".pdf"
        Case "presentation"
            Return ".pptx"
        Case Else
            Return ".dat"
    End Select
End Sub

Sub DisplayContent(filePath As String, contentType As String)
    Try
        Log("Displaying content (" & contentType & ")")
        
        ' Set MIME type based on content type
        Dim mimeType As String
        Select Case contentType.ToLowerCase
            Case "video"
                mimeType = "video/mp4"
            Case "image"
                mimeType = "image/*"
            Case "pdf"
                mimeType = "application/pdf"
            Case "presentation"
                mimeType = "application/vnd.ms-powerpoint"
            Case Else
                mimeType = "*/*"
        End Select
        
        ' Modern Android (7.0+) requires FileProvider to share files with other apps
        Dim p As Phone
        If p.SdkVersion >= 24 Then
            Dim i As Intent
            Dim fileObj As JavaObject
            fileObj.InitializeNewInstance("java.io.File", Array(filePath))
            
            Dim FileProvider As JavaObject
            Dim context As JavaObject
            context.InitializeContext
            
            ' Use the authority defined in Manifest: $PACKAGE$.provider
            Dim authority As String = Application.PackageName & ".provider"
            Dim uri As Object = FileProvider.InitializeStatic("androidx.core.content.FileProvider").RunMethod("getUriForFile", Array(context, authority, fileObj))
            
            i.Initialize(i.ACTION_VIEW, "")
            Dim jo As JavaObject = i
            jo.RunMethod("setDataAndType", Array(uri, mimeType))
            jo.RunMethod("addFlags", Array(1)) ' FLAG_GRANT_READ_URI_PERMISSION
            i.Flags = Bit.Or(i.Flags, 268435456) ' FLAG_ACTIVITY_NEW_TASK
            
            StartActivity(i)
        Else
            ' Fallback for older Android versions
            Dim i As Intent
            i.Initialize(i.ACTION_VIEW, "file://" & filePath)
            i.SetType(mimeType)
            i.Flags = 268435456 ' FLAG_ACTIVITY_NEW_TASK
            StartActivity(i)
        End If
        
        Log("Content displayed successfully")
    Catch
        Log("Error displaying content: " & LastException)
        ' Fallback: just log the file path
        Log("Content saved at: " & filePath)
    End Try
End Sub

Sub ReportContentStatus(contentId As Int, status As String)
    Try
        Dim token As String = ""
        If File.Exists(File.DirInternal, "token.txt") Then
            token = File.ReadString(File.DirInternal, "token.txt")
        End If
        
        If token = "" Then
            Log("Cannot report content status - no token")
            Return
        End If
        
        Dim data As Map
        data.Initialize
        data.Put("content_id", contentId)
        data.Put("status", status)
        
        Dim json As JSONGenerator
        json.Initialize(data)
        
        Dim job As HttpJob
        job.Initialize("content_status", Me)
        job.PostString(ServerURL & "/content/status.php", json.ToString)
        job.GetRequest.SetContentType("application/json")
        job.GetRequest.SetHeader("Authorization", "Bearer " & token)
        
        Log("Content status reported: " & status & " for content ID: " & contentId)
    Catch
        Log("Error reporting content status: " & LastException)
    End Try
End Sub

' ================= DEVICE UTILITIES =================

Sub GetDeviceID As String
    Dim p As Phone
    Return p.GetSettings("android_id")
End Sub

Private Sub GetBatteryLevel As Int
    Dim batteryIntent As Intent
    batteryIntent = CreateIntentFilterAndRegister("android.intent.action.BATTERY_CHANGED")
    If batteryIntent.IsInitialized Then
        Dim level As Int = batteryIntent.GetExtra("level")
        Dim scale As Int = batteryIntent.GetExtra("scale")
        If level <> -1 And scale <> -1 Then Return (level * 100) / scale
    End If
    Return -1
End Sub

Private Sub CreateIntentFilterAndRegister(action As String) As Intent
    Dim r As Reflector
    Dim filter As Object = r.CreateObject2("android.content.IntentFilter", Array(action), Array As String("java.lang.String"))
    r.Target = r.GetContext
    Return r.RunMethod4("registerReceiver", Array(Null, filter), Array As String("android.content.BroadcastReceiver", "android.content.IntentFilter"))
End Sub

Private Sub GetFreeStorageBytes As Long
    Try
        Dim r As Reflector
        Dim stat As Object = r.CreateObject2("android.os.StatFs", Array(File.DirInternal), Array As String("java.lang.String"))
        r.Target = stat
        Return r.RunMethod("getAvailableBytes")
    Catch
        Log("Storage free failed: " & LastException)
        Return -1
    End Try
End Sub

Private Sub GetTotalStorageBytes As Long
    Try
        Dim r As Reflector
        Dim stat As Object = r.CreateObject2("android.os.StatFs", Array(File.DirInternal), Array As String("java.lang.String"))
        r.Target = stat
        Return r.RunMethod("getTotalBytes")
    Catch
        Log("Storage total failed: " & LastException)
        Return -1
    End Try
End Sub

Private Sub GetAndroidVersion As String
    Dim p As Phone
    Dim r As Reflector
    Return p.SdkVersion & " (" & r.GetStaticField("android.os.Build$VERSION", "RELEASE") & ")"
End Sub

' ================= APP PROCESSING =================

Sub ProcessApps(apps As List)
    For Each item As Map In apps
        Try
            Dim appId As Int = item.GetDefault("app_id", 0)
            Dim appName As String = item.GetDefault("name", "Unknown App")
            Dim packageName As String = item.GetDefault("package_name", "")
            Dim version As String = item.GetDefault("version", "")
            Dim downloadUrl As String = item.GetDefault("download_url", "")
            Dim checksum As String = item.GetDefault("checksum", "")
            
            Log("Processing app deployment: " & appName & " (" & packageName & " v" & version & ")")
            DownloadApp(downloadUrl, appId, appName, packageName, version, checksum)
        Catch
            Log("Error processing app: " & LastException)
        End Try
    Next
End Sub

Sub DownloadApp(url As String, appId As Int, appName As String, packageName As String, version As String, checksum As String)
    Try
        Log("Downloading app: " & appName & " from " & url)
        
        Dim appDir As String = File.DirRootExternal & "/SDMS/Apps"
        If Not(File.Exists(appDir, "")) Then
            File.MakeDir(appDir, "")
        End If
        
        Dim job As HttpJob
        job.Initialize("download_app_" & appId, Me)
        job.Download(url)
        
        Dim metadata As Map
        metadata.Initialize
        metadata.Put("app_id", appId)
        metadata.Put("app_name", appName)
        metadata.Put("package_name", packageName)
        metadata.Put("version", version)
        metadata.Put("checksum", checksum)
        
        Dim json As JSONGenerator
        json.Initialize(metadata)
        File.WriteString(File.DirInternal, "app_meta_" & appId & ".txt", json.ToString)
    Catch
        Log("Error starting app download: " & LastException)
    End Try
End Sub

Sub HandleAppDownloadComplete(Job As HttpJob, appId As Int)
    Try
        Log("App download complete for App ID: " & appId)
        
        ' Just consume the meta file to ensure it's removed later
        Dim parser As JSONParser
        parser.Initialize(File.ReadString(File.DirInternal, "app_meta_" & appId & ".txt"))
        ' Dim metadata As Map = parser.NextObject ' Commented out as currently unused
        
        Dim fileName As String = appId & ".apk"
        Dim appDir As String = File.DirRootExternal & "/SDMS/Apps"
        
        Dim out As OutputStream = File.OpenOutput(appDir, fileName, False)
        File.Copy2(Job.GetInputStream, out)
        out.Close
        
        Log("APK saved at: " & appDir & "/" & fileName)
        ' TriggerAppInstall(appDir & "/" & fileName)
        ReportAppStatus(appId, "downloaded")
    Catch
        Log("Error handling app download: " & LastException)
        ReportAppStatus(appId, "failed")
    End Try
    
    If File.Exists(File.DirInternal, "app_meta_" & appId & ".txt") Then
        File.Delete(File.DirInternal, "app_meta_" & appId & ".txt")
    End If
End Sub

Public Sub TriggerAppInstall(apkPath As String)
    Try
        Log("Triggering installation for: " & apkPath)
        Dim p As Phone
        Dim i As Intent
        If p.SdkVersion >= 24 Then
            Dim fileObj As JavaObject
            fileObj.InitializeNewInstance("java.io.File", Array(apkPath))
            Dim FileProvider As JavaObject
            Dim context As JavaObject
            context.InitializeContext
            Dim authority As String = Application.PackageName & ".provider"
            Dim uri As Object = FileProvider.InitializeStatic("androidx.core.content.FileProvider").RunMethod("getUriForFile", Array(context, authority, fileObj))
            i.Initialize(i.ACTION_VIEW, "")
            Dim jo As JavaObject = i
            jo.RunMethod("setDataAndType", Array(uri, "application/vnd.android.package-archive"))
            jo.RunMethod("addFlags", Array(1))
        Else
            i.Initialize(i.ACTION_VIEW, "file://" & apkPath)
            i.SetType("application/vnd.android.package-archive")
        End If
        i.Flags = Bit.Or(i.Flags, 268435456)
        StartActivity(i)
        Log("Install intent started")
    Catch
        Log("Error triggering install: " & LastException)
    End Try
End Sub

Sub ReportAppStatus(appId As Int, status As String)
    Try
        Log("Reporting app status for ID " & appId & ": " & status)
        Dim data As Map
        data.Initialize
        data.Put("app_id", appId)
        data.Put("status", status)
        Dim json As JSONGenerator
        json.Initialize(data)
        Dim job As HttpJob
        job.Initialize("app_status", Me)
        job.PostString(ServerURL & "/apps/status.php", json.ToString) 
        job.GetRequest.SetHeader("Authorization", "Bearer " & File.ReadString(File.DirInternal, "token.txt"))
    Catch
        Log("Error reporting app status: " & LastException)
    End Try
End Sub

' ================= REMOTE CONTROL =================

Sub StartRemoteSession(commandId As Int)
    Try
        Log("Starting Remote Session (Command ID: " & commandId & ")")
        Log("Remote View Permission Level: " & CurrentPermissionLevel)
        
        ' Fetch settings first to check for permission requirement
        Dim job As HttpJob
        job.Initialize("fetch_settings_remote_" & commandId, Me)
        job.Download(ServerURL & "/settings.php")
        job.GetRequest.SetHeader("Authorization", "Bearer " & File.ReadString(File.DirInternal, "token.txt"))
    Catch
        Log("Error initiating remote session start: " & LastException)
        AcknowledgeCommand(commandId, False, "Error: " & LastException.Message)
    End Try
End Sub

Sub HandleRemoteSettingsResponse(Job As HttpJob)
    Try
        Dim cmdId As Int = Job.JobName.SubString(22)
        Dim parser As JSONParser
        parser.Initialize(Job.GetString)
        Dim root As Map = parser.NextObject
        If root.Get("success") = True Then
            Dim settings As Map = root.Get("settings")
            Dim reqPerm As String = settings.GetDefault("remote_require_permission", "no")
            
            If reqPerm = "yes" Then
                Log("Permission required for remote control.")
            End If
            
            IsRemoteSessionActive = True
            RemoteSessionTimer.Enabled = True
            Log("Remote session activated")
            AcknowledgeCommand(cmdId, True, "")
        Else
            AcknowledgeCommand(cmdId, False, "Failed to fetch settings")
        End If
    Catch
        Log("Error parsing settings for remote: " & LastException)
    End Try
End Sub

Sub StopRemoteSession(commandId As Int)
    Try
        Log("Stopping Remote Session")
        IsRemoteSessionActive = False
        RemoteSessionTimer.Enabled = False
        If commandId > 0 Then AcknowledgeCommand(commandId, True, "")
    Catch
        Log("Error stopping remote session: " & LastException)
    End Try
End Sub

Sub RemoteSessionTimer_Tick
    If IsRemoteSessionActive Then
        ' Trigger a screenshot capture (simulating real-time view)
        ' We use 0 as commandId for periodic updates to avoid cluttering command history
        ExecuteScreenshot(0)
    End If
End Sub

' ================= PERMISSION MANAGEMENT =================

Sub DeterminePermissionLevel As Int
    ' 1. Check Root (Highest)
    If CheckRootAccess Then Return PERM_ROOT
    
    ' 2. Check Samsung Knox
    If CheckSamsungKnox Then Return PERM_KNOX
    
    ' 3. Check Sony
    If CheckSonyEnterprise Then Return PERM_SONY
    
    ' 4. Check System App Status
    If CheckSystemStatus Then Return PERM_SYSTEM
    
    ' Default: User
    Return PERM_USER
End Sub

Sub CheckRootAccess As Boolean
    Try
        Dim p As Phone
        Dim stdOut, stdErr As StringBuilder
        stdOut.Initialize
        stdErr.Initialize
        p.Shell("su -c id", Null, stdOut, stdErr)
        If stdOut.ToString.Contains("uid=0") Then
            Log("Root access confirmed")
            Return True
        End If
    Catch
        Log("Root check failed: " & LastException)
    End Try
    Return False
End Sub

Sub CheckSystemStatus As Boolean
    Try
        Dim jo As JavaObject
        jo.InitializeContext
        Dim packageInfo As JavaObject = jo.RunMethodJO("getPackageManager", Null).RunMethod("getPackageInfo", Array(Application.PackageName, 0))
        Dim applicationInfo As JavaObject = packageInfo.GetField("applicationInfo")
        Dim flags As Int = applicationInfo.GetField("flags")
        ' FLAG_SYSTEM = 1
        If Bit.And(flags, 1) <> 0 Then
            Log("App is running as System App")
            Return True
        End If
    Catch
        Log("System status check failed: " & LastException)
    End Try
    Return False
End Sub

Sub CheckSamsungKnox As Boolean
    If Manufacturer.Contains("samsung") Then
        Try
            Dim jo As JavaObject
            jo.InitializeContext
            ' Simple check for Knox Enterprise SDK presence
            Dim pm As JavaObject = jo.RunMethod("getPackageManager", Null)
            Dim packages As List = pm.RunMethod("getInstalledPackages", Array(0))
            For Each pkg As JavaObject In packages
                Dim pkgName As String = pkg.GetField("packageName")
                If pkgName.Contains("sec.enterprise.knox") Or pkgName.Contains("com.samsung.android.knox") Then
                    Log("Samsung Knox SDK/packages detected")
                    Return True
                End If
            Next
        Catch
            Log("Samsung Knox check failed: " & LastException)
        End Try
    End If
    Return False
End Sub

Sub CheckSonyEnterprise As Boolean
    If Manufacturer.Contains("sony") Then
        Try
            Dim jo As JavaObject
            jo.InitializeContext
            Dim pm As JavaObject = jo.RunMethod("getPackageManager", Null)
            Dim packages As List = pm.RunMethod("getInstalledPackages", Array(0))
            For Each pkg As JavaObject In packages
                Dim pkgName As String = pkg.GetField("packageName")
                If pkgName.Contains("com.sonymobile.enterprise") Then
                    Log("Sony Enterprise packages detected")
                    Return True
                End If
            Next
        Catch
            Log("Sony Enterprise check failed: " & LastException)
        End Try
    End If
    Return False
End Sub
