Android Question How to save file from datatables export

Sofian

Member
I have create php report with datatables script and button export to excel, in normal windows web browser it working to create excel from table on page. But on webview, I got nothing from button export click.
webview url:
WebView1_OverrideUrl
got nothing change on log.

How to get/save file create from datatables button script from webview ?
 

Attachments

  • download export.png
    download export.png
    30.8 KB · Views: 152

Sofian

Member
I try sample code on post #12 and here log file with no sub trigger
logstr:
Progress: 10%
Progress: 21%
Progress: 21%
Progress: 26%
Progress: 33%
Progress: 70%
Progress: 70%
Progress: 100%
Progress: 100%
PageFinished: https://dapurhn.swk-apps.com/laporan/
>>>>>>>> ExecuteJavaScript: console.log('PageFinished');
>>>>>>>> js1: console.log('PageFinished');
CONSOLE LOG: PageFinished                                                      In  (Line: 1)
Progress: 100%
CONSOLE ERROR: Uncaught ReferenceError: validate is not defined                                                      In https://dapurhn.swk-apps.com/laporan/ (Line: 144)
Progress: 10%
Progress: 70%
Progress: 100%
PageFinished: https://dapurhn.swk-apps.com/laporan/
>>>>>>>> ExecuteJavaScript: console.log('PageFinished');
>>>>>>>> js1: console.log('PageFinished');
Progress: 100%
CONSOLE LOG: PageFinished                                                      In  (Line: 1)
contentDisposition: []
Send GET XMLHttpRequest: blob:https://dapurhn.swk-apps.com/d772a808-a4ef-473f-a9c2-08acb900b216  MimeType: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
>>>>>>>> ExecuteJavaScript: console.log('FOUND Blob URL');
>>>>>>>> js1: console.log('FOUND Blob URL');
>>>>>>>> ExecuteJavaScript: var xhr = new XMLHttpRequest(); xhr.open('GET', 'blob:https://dapurhn.swk-apps.com/d772a808-a4ef-473f-a9c2-08acb900b216', true); xhr.setRequestHeader('Access-Control-Allow-Origin', true); xhr.setRequestHeader('Content-type','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8'); xhr.responseType = 'blob'; xhr.onload = function(e) {    if (this.status == 200) {        var blobFile = this.response;        var reader = new FileReader();        reader.readAsDataURL(blobFile);        reader.onloadend = function() {           var base64data = reader.result;          B4A.getBase64FromBlobData(base64data);        }    } }; xhr.send(); xhr.onreadystatechange = () => {  if (xhr.readyState === xhr.HEADERS_RECEIVED) {     console.log('>>>>>>>> RECEIVED HEADERS <<<<<<<<');     const headers = xhr.getAllResponseHeaders();     console.log('Headers: ' + JSON.stringify(headers, null, 3));     const arr = headers.trim().split(("\r\n"));     console.log('Header size: ' + arr.length);     const headerArray = [];     arr.forEach((line) => {        headerArray.push(line);     });     var idx = 0;     headerArray.forEach((header) => {        idx++;        console.log('[HEADER ' + idx + '] => ' + header);     });   }};
>>>>>>>> js1: var xhr = new XMLHttpRequest(); xhr.open('GET', 'blob:https://dapurhn.swk-apps.com/d772a808-a4ef-473f-a9c2-08acb900b216', true); xhr.setRequestHeader('Access-Control-Allow-Origin', true); xhr.setRequestHeader('Content-type','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8'); xhr.responseType = 'blob'; xhr.onload = function(e) {    if (this.status == 200) {        var blobFile = this.response;        var reader = new FileReader();        reader.readAsDataURL(blobFile);        reader.onloadend = function() {           var base64data = reader.result;          B4A.getBase64FromBlobData(base64data);        }    } }; xhr.send(); xhr.onreadystatechange = () => {  if (xhr.readyState === xhr.HEADERS_RECEIVED) {     console.log('>>>>>>>> RECEIVED HEADERS <<<<<<<<');     const headers = xhr.getAllResponseHeaders();     console.log('Headers: ' + JSON.stringify(headers, null, 3));     const arr = headers.trim().split(("\r\n"));     console.log('Header size: ' + arr.length);     const headerArray = [];     arr.forEach((line) => {        headerArray.push(line);     });     var idx = 0;     headerArray.forEach((header) => {        idx++;        console.log('[HEADER ' + idx + '] => ' + header);     });   }};
CONSOLE LOG: FOUND Blob URL                                                      In  (Line: 1)
CONSOLE LOG: >>>>>>>> RECEIVED HEADERS <<<<<<<<                                                      In  (Line: 1)
CONSOLE LOG: Headers: "content-length: 4460\r\ncontent-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n"                                                      In  (Line: 1)
CONSOLE LOG: Header size: 2                                                      In  (Line: 1)
CONSOLE LOG: [HEADER 1] => content-length: 4460                                                      In  (Line: 1)
CONSOLE LOG: [HEADER 2] => content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet                                                      In  (Line: 1)
MimeType ==> application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Extension ==> [xlsx]
Path ==> /storage/emulated/0/Download/May_5_2025_17-38-47.xlsx
regex ==> ^data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,
BASE64 DATA: UEsDBAoAAAAAANdUpVoAAAAAAAAAAA ... and more ...
look like the file on path string, but I dont know how to get it
 
Upvote 0

Sofian

Member
And when I move all of code to another b4xpages, webview not show url, nothing happen on sub create, hope someone can help
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
You may get quicker answer if you posted your modified project.
 
Upvote 0

Sofian

Member
You may get quicker answer if you posted your modified project.
I'm not modified, only move all code (ctrl+A ..... ctrl+V) on a new blank b4apage from sample code, only load my layout name, it include same webview1 name without button1 on the sample. On sample, I can click report button and get repply on java log bellow, on My apps, it now even load my URL, blank white page.
 
Upvote 0

Sofian

Member
Here my web link : https://dapurhn.swk-apps.com/laporan/
I m not yet lock it with login , for test data only
I want to get excel file generate from datatables button. If it open with chrome or else on PC / android, file can be downloaded, My problem is it cant get from webview and I dont know how , please help.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
It's fine if you don't expect people to help you, without helping people that want to help you see the code in front of you that they can't see your screen.
 
Upvote 0

Sofian

Member
Sorry.. I don't get it what you mean, couse sample code already Erel point on post #2. However this is my code from that sample :
I only move it all code from B4XMainpages to new name of B4Xpage :
code1:
#Region  Activity Attributes
    #FullScreen: True
    #IncludeTitle: False
#End Region

Sub Process_Globals
    Private ActivityParent As JavaObject
    Private xui As XUI
    Private strUrl As String
    Private cc As ContentChooser
    Private OldIntent As Intent
    Dim rp As RuntimePermissions
    Dim NoPause As Boolean
    Private Bundle As JavaObject
    Private Native As JavaObject
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Private IME As IME
    Dim r As Reflector
    Private WebView1 As WebView
    Private ActivityParent As JavaObject
    Private Native As JavaObject
End Sub

Public Sub Initialize
'    B4XPages.GetManager.LogEvents = True
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity_WindowFocusChanged(True)
    Dim lv As LayoutValues = GetRealSize
    Dim jo As JavaObject = Activity
    jo.RunMethod("setBottom", Array(lv.Height))
    jo.RunMethod("setRight", Array(lv.Width))
    Activity.Height =100%y' lv.Height
    Activity.Width = 100%x'lv.Width
    
    IME.Initialize("ime")
    Activity.LoadLayout("lyt_webview")
    IME.AddHeightChangedEvent
    'Dim client As JavaObject
    'client.InitializeNewInstance(Application.PackageName & ".hnlaporan$MyChromeClient", Null)
    'Dim jo As JavaObject = WebView1
    'jo.RunMethod("setWebChromeClient", Array(client))
    
    Native = Me
    Native.RunMethod("Initialize", Null)
    'Native.RunMethod("Initialize", Array (WebView1)) ' Initialize and pass context

    If Bundle.IsInitialized And  Starter.bukawebnew=False Then
        Dim jo As JavaObject = WebView1
        jo.RunMethod("restoreState", Array(Bundle))
    End If
    
    
    Dim jo As JavaObject = Activity
    jo.RunMethodJO("getContext", Null).RunMethodJO("getWindow", Null).RunMethod("setSoftInputMode", _
     Array As Object(0x20))
    ActivityParent = jo.RunMethodJO("getParent", Null)
    
    'Dim r As Reflector
    r.Target = WebView1
    r.Target = r.RunMethod("getSettings")
    r.RunMethod2("setBuiltInZoomControls", True, "java.lang.boolean")
    r.RunMethod2("setDisplayZoomControls", False, "java.lang.boolean")
    
    SetupJavaScriptInterface
    SetupDownloadListener
    SetupWebChromeClient
'    SetupWebViewClient
    SetupWebViewSettings(WebView1)
    'WebView1.LoadUrl("https://dapurhn.swk-apps.com/laporan/")
    BukaWeb("https://dapurhn.swk-apps.com/laporan/")
End Sub

Sub IME1_HeightChanged (NewHeight As Int, OldHeight As Int)
    CallSubDelayed(Me, "AfterChange")
End Sub

Sub AfterChange
    r.Target = WebView1
    r.Target = r.RunMethod("getSettings")
    r.RunMethod2("setBuiltInZoomControls", True, "java.lang.boolean")
    r.RunMethod2("setDisplayZoomControls", False, "java.lang.boolean")
    
    Dim ajo As Panel = Activity
    Dim width As Int = ActivityParent.RunMethod("getMeasuredWidth", Null)
    Dim height As Int = ActivityParent.RunMethod("getMeasuredHeight", Null)
    If width = 0 Or height = 0 Then Return
    ajo.Width = width 'update the "activity" width and height
    ajo.Height = height
    WebView1.Width = width
    WebView1.Height = height
    
End Sub

Sub IME_HeightChanged (NewHeight As Int, OldHeight As Int)
    'CallSubDelayed(Me, "AfterChange")
    'WebView1.Height=WebView1.Top-NewHeight
    CallSubDelayed(Me, "AfterChange")
End Sub
Sub BukaWeb(xStr As String)
    'If Bundle.IsInitialized And  Starter.bukawebnew=False Then
    '    Dim jo As JavaObject = WebView1
    '    jo.RunMethod("restoreState", Array(Bundle))
    'Else
    ProgressDialogShow("Loading data ...")
    strUrl = xStr
    'WebView1.LoadUrl(strUrl)
    Dim job2 As HttpJob
    job2.Initialize("Job2", Me)
    job2.Download(strUrl)
        
    wait for (job2) JobDone(job2 As HttpJob)
    If job2.Success = True Then
        Select job2.JobName
            Case "Job2"
                WebView1.JavaScriptEnabled=True
                WebView1.LoadUrl(strUrl)
                'show the downloaded image
        End Select
    Else
        Msgbox2Async(job2.ErrorMessage , "Error","Stop","","",LoadBitmap(File.DirAssets,"Warning2.png"),True)
        Wait For Msgbox_Result (Result As Int)
        Activity.Finish
    End If
    job2.Release
    ProgressDialogHide
    Starter.bukawebnew=False
    'End If
End Sub

Sub JobDone (Job As HttpJob)
    
End Sub

Sub WebView1_OverrideUrl(Url As String) As Boolean
    'ToastMessageShow(Url,False)
    Log(Url)
    If Url.EndsWith(".pdf") Then
        DownloadFile(Url)
        
        Return True
    else If Url.EndsWith("tutup/") Then
        Activity.Finish
    Else
        Return False
    End If
                
End Sub
Sub GetRealSize As LayoutValues
    Dim lv As LayoutValues
    Dim p As Phone
    If p.SdkVersion >= 17 Then
        Dim ctxt As JavaObject
        ctxt.InitializeContext
        Dim display As JavaObject = ctxt.RunMethodJO("getSystemService", Array("window")).RunMethod("getDefaultDisplay", Null)
        Dim point As JavaObject
        point.InitializeNewInstance("android.graphics.Point", Null)
        display.RunMethod("getRealSize", Array(point))
        lv.Width = point.GetField("x")
        lv.Height = point.GetField("y")
    Else
        lv.Width = 100%x
        lv.Height = 100%y
    End If
    lv.Scale = 100dip / 100
    Return lv
End Sub

Sub Activity_WindowFocusChanged(HasFocus As Boolean)
    If HasFocus Then
        Try
            Dim jo As JavaObject = Activity
            Sleep(300)
            jo.RunMethod("setSystemUiVisibility", Array As Object(5894)) '3846 - non-sticky
        Catch
            'Log(LastException) 'This can cause another error
        End Try 'ignore
        
    End If
End Sub
Sub DownloadFile (link As String)
    strUrl=link
    Dim myPath As String
    myPath =rp.GetSafeDirDefaultExternal("") & "/"
    Dim j As HttpJob
    j.Initialize("", Me)
    j.Download(link)
    Wait For (j) JobDone(j As HttpJob)
    If j.Success Then
        Dim FileName As String = "laporan.xlsx"
        Dim out As OutputStream = File.OpenOutput(myPath,FileName,False)
        File.Copy2(j.GetInputStream, out)
        out.Close
        File.Copy(myPath, FileName, Starter.Provider.SharedFolder, FileName)
        Dim in As Intent
        in.Initialize(in.ACTION_VIEW, "")
        Starter.Provider.SetFileUriAsIntentData(in, FileName)
        in.SetComponent("android/com.android.internal.app.ResolverActivity")
        in.SetType("Application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
        StartActivity(in)
        
    Else
        MsgboxAsync(j.ErrorMessage, "Error")
    End If
    j.Release
End Sub

Sub Activity_Resume
    AfterChange
End Sub

Sub Activity_Pause (UserClosed As Boolean)
    'If UserClosed Then
    '    StateManager.ResetState("viewhtml")
    'Else
    '    StateManager.SaveState(Activity, "viewhtml")
    'End If
    'StateManager.SaveSettings
    
    Bundle.InitializeNewInstance("android.os.Bundle", Null)
    Dim jo As JavaObject = WebView1
    jo.RunMethod("saveState", Array(Bundle))
End Sub

Sub ShowFile_Chooser (FilePathCallback As Object, FileChooserParams As Object)
    cc.Initialize("CC")
    cc.Show("*/*", "Choose File")
    Wait For CC_Result (Success As Boolean, Dir As String, FileName As String)
    Dim jo As JavaObject = Me
    If Success Then
        Dim namafile As String = GetFileInfoByIndex("_display_name", FileName)
        'Log(FileName)
        File.Copy(Dir, FileName, Starter.Provider.SharedFolder, namafile)
        jo.RunMethod("SendResult", Array(Starter.Provider.GetFileUri(namafile),FilePathCallback))
    Else
        jo.RunMethod("SendResult", Array(Null, FilePathCallback))
    End If
End Sub

Sub GetFileInfoByIndex(column As String, uri As String) As String
    
    Dim results As String
    Dim Cur As Cursor
    Dim Uri1 As Uri
    Dim cr As ContentResolver
    cr.Initialize("")

    'if viewing by gallery
    If uri.StartsWith("content://media/") Then
        Dim i As Int = uri.LastIndexOf("/")
        Dim id As String = uri.SubString(i + 1)
        Uri1.Parse(uri)
        Cur = cr.Query(Uri1, Null, "_id = ?", Array As String(id), Null)
        Cur.Position = 0
        If Cur.RowCount <> 0 Then
            For i = 0 To Cur.ColumnCount - 1
                If Cur.GetColumnName(i) <> Null Then
                    If Cur.GetColumnName(i) = column Then
                        results = Cur.GetString2(i)
                        Exit
                    End If
                End If
            Next
        End If
    Else
        Uri1.Parse(uri)
        Cur = cr.Query(Uri1, Null, Null, Null, Null)
        Cur.Position = 0
        If Cur.RowCount <> 0 Then
            For i = 0 To Cur.ColumnCount - 1
                If Cur.GetColumnName(i) <> Null Then
                    If Cur.GetColumnName(i) = column Then
                        results = Cur.GetString2(i)
                        Exit
                    End If
                End If
            Next
        End If
    End If
    
    Cur.Close
    
    Return results
    
End Sub
Private Sub IsRelevantIntent(in As Intent) As Boolean
    If in.IsInitialized And in <> OldIntent And in.Action = in.ACTION_SEND Then
        OldIntent = in
        Return True
    End If
    Return False
End Sub
Sub SetupJavaScriptInterface
    Log(">>>>>>>> SetupJavaScriptInterface")
    Native.RunMethod("AddJavaScriptInterface", Array ("B4A"))     ' Add JavaScript interface
End Sub

Sub SetupWebChromeClient
    Log(">>>>>>>> SetupWebChromeClient")
    Dim WebChromeClient As JavaObject
    WebChromeClient.InitializeNewInstance(Application.PackageName & ".b4xmainpage$MyWebChromeClient", Null)
    Dim wvjo As JavaObject = WebView1
    wvjo.RunMethod("setWebChromeClient", Array(WebChromeClient))
End Sub

Sub SetupWebViewClient
    Log(">>>>>>>> SetupWebViewClient")
    Dim WebViewClient As JavaObject
    WebViewClient.InitializeNewInstance(Application.PackageName & ".b4xmainpage$MyWebViewClient", Null)
    Dim wvjo As JavaObject = WebView1
    wvjo.RunMethod("setWebViewClient", Array(WebViewClient))
End Sub

'Download Listener
Sub SetupDownloadListener
    Log(">>>>>>>> SetupDownloadListener")
    Dim dl As JavaObject
    dl.InitializeNewInstance(Application.PackageName & ".b4xmainpage$MyDownloadListener", Null)
    dl.RunMethod("set", Array(WebView1))
End Sub

Sub DownloadListener_Event (MethodName As String, Args() As Object) As Object ' What is this ???
    Log("DownloadListener = " & MethodName)
    Return Null
End Sub

Sub onDownloadStart (Url As String, UserAgent As String, ContentDisposition As String, ContentType As String, ContentLength As Long)
  
    LogColor("Url: " & Url, xui.Color_Green)
    LogColor("UserAgent: " & UserAgent, xui.Color_Green)
    LogColor("ContentDisposition: " & ContentDisposition, xui.Color_Green)
    LogColor("ContentType: " & ContentType, xui.Color_Green)
    LogColor("ContentLength: " & ContentLength & " Bytes", xui.Color_Green)
  
    '//////////// TRY TO GET FILE NAME FROM JS /////////////////
    
    '''''''    Dim js As String = $"
    '''''''      // var element = document.getElementById("a");
    '''''''        //B4A.CallSub('Get_FileName', true, element.download);
    '''''''        //B4A.CallSub('Get_FileName', true, link.download);
    '''''''        //B4A.CallSub('Get_FileName', false, document.documentElement.outerHTML);
    '''''''        B4A.CallSub('Get_FileName', false, JSON.stringify(document.getElementsByTagName('a')));
    '''''''    "$
    '''''''    Native.RunMethod("ExecuteJavaScript", Array (js))
    '''''''    Wait For Get_FileName (fn As String)
    '''''''    Log("FILE NAME: " & fn)
    
    '//////////////////////////////////////////////////////////
    
    ' Download file
    Dim Dir As String = File.DirInternal
    Dim FileName As String = "box.stl"  ' For now we set the file name manually. Content-Disposition header should return it, but always is a void string
    Wait For (DownloadAndSaveFile(Url, Dir, FileName)) Complete (Success As Boolean)
        
    Log("RETURNED FROM DownloadAndSaveFile. Success: " & Success)
        
    DoIntent(Dir, FileName , ContentType)
    
'       If Success Then  ' Launch default app   
'            'ToastMessageShow("Successfully downloaded file: " & FileName,True)
'        Else
'            ToastMessageShow("Error Downloading file: " & FileName, True)
'        End If
    
End Sub

Sub DoIntent(Dir As String, FileName As String, ContentType As String)
    
    Dim DestDir As String = File.Combine(File.DirRootExternal, "Download")  ' Add WRITE_EXTERNAL_STORAGE

    Wait For (File.CopyAsync(Dir, FileName, DestDir, FileName)) Complete (Success As Boolean)

    If Success = False Then
        Log("ERROR WHILE COPY FILE FROM DIR.INTERNAL TO DIR.ROOT.EXTERNAL: " & LastException)
        Return
    End If
    
'    Similar to content:///storage/emulated/0/Download/2_lug_2024_07-24-40.txt
    
    Dim i As Intent
'    i.Initialize(i.ACTION_VIEW, "")                                                                           
    Dim URI As String = "content://" & File.Combine(DestDir, FileName)   'xui.FileUri(Dir, FileName)   ' Should be content:///data/user/0/b4a.example/files/box.stl
    Log("INTENT URI: " & URI)
    i.Initialize(i.ACTION_VIEW, URI)
    
'    i.SetType(ContentType)
    i.SetType("*/*")
    StartActivity(i)
End Sub

Sub DownloadAndSaveFile (Url As String, Dir As String, FileName As String) As ResumableSub
    If File.Exists(Dir, FileName) Then
         File.Delete(Dir, FileName)
        Log("DELETED: " & File.Combine(Dir, FileName)) ' We delete a file before download it if exists. We can just overwrite it.
    End If
    
    Dim Success As Boolean = False
    
    If (Url.StartsWith("http:")) Or (Url.StartsWith("https:")) Then  ' Download file with httppjob
        If Url.StartsWith("https:") Then
            Log("[HTTPS] URL: " & Url)
        Else
            Log("[HTTP] URL: " & Url)
        End If
            
        Try
            Dim j As HttpJob
            j.Initialize("", Me)
                
            LogColor("Start download. URL: " & Url, xui.Color_Green)
            j.Download(Url)

         ' We can set some request headers ...
            '    j.GetRequest.SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0")
            '    j.GetRequest.SetHeader("Access-Control-Allow-Origin", "true")
            '    j.GetRequest.SetHeader ("ContentType", "text/plain")
            '    j.GetRequest.SetHeader ("ContentType", "application/octet-stream")   
            '    j.GetRequest.SetHeader ("Host", "exampleproject.com")
            '    j.GetRequest.SetHeader ("Connection", "close")
  
            Wait For (j) JobDone(j As HttpJob)
    
            Dim map As Map = j.Response.GetHeaders ' ... and get a list of headers from a response
            For i = 0 To map.Size - 1
                Log("HEADER: " & map.GetKeyAt(i) & ": " & map.GetValueAt(i))
            Next
            Log(" ")
            Log("Success: " & j.Success)
            Log("StatusCode: " & j.Response.StatusCode)
            Log("ErrorResponse: " & j.Response.ErrorResponse)
            Log("ContentType: " & j.Response.ContentType)
            Log("ContentLength: " & j.Response.ContentLength)
            Log("ContentEncoding: " & j.Response.ContentEncoding)
            Log(" ")
        
            If j.Success And j.Response.ContentLength > 0 Then
                Dim out As OutputStream = File.OpenOutput(Dir, FileName, False)  '"benchy.gcode"
                File.Copy2(j.GetInputStream, out)
                out.Close ' <------ Very important
        
                Dim Size As Long = File.Size(Dir, FileName)
                Log("FILE SAVED: " & FileName & TAB & " Size: " & Size & " Bytes")
                ToastMessageShow("FILE SAVED: " & FileName, False)
                Dim s As String = File.ReadString(Dir, FileName)
'                Log(s)
               Log(s.SubString2(0, Min(100, Size))) ' Just log some content
                Log("... and more ...")
                Success = True           
            Else
                Log("ERROR: " & j.ErrorMessage)
                ToastMessageShow("FILE DOWNLOADED FAILED: " & j.ErrorMessage, True)
            End If
        Catch
            ToastMessageShow("FILE DOWNLOADED FAILED: " & LastException.Message, True)
        End Try
        j.Release
    
    Else If (Url.StartsWith("blob:"))  Then
        Log("[BLOB] URL: " & Url)
        
        ' THIS CODE WAS PLACED IN THE JAVA CODE, SO IT SIMPLIFY THE WORK AND JUST DO IT IN BACKGROUND. To get data bytes, just wait it with 'Wait For blob_data (Bytes() As Byte)' without worry about this ....
        ''''''    Dim js As String = Native.RunMethod("getBase64StringFromBlobUrl", Array As String (Url, MimeType))
        ''''''    Log("RETURNED JS CODE FROM getBase64StringFromBlobUrl: [" & js & "]")
        ''''''
        ''''''    If js.Length > 0 Then
        ''''''        Native.RunMethod("ExecuteJavaScript", Array ("alert('FOUND Blob URL');"))
        ''''''    Else
        ''''''        Native.RunMethod("ExecuteJavaScript", Array ("alert('It is not a Blob URL');"))
        ''''''    End If
        ''''''
        ''''''    Native.RunMethod("ExecuteJavaScript", Array (js))
            
        Wait For blob_data (Bytes() As Byte) ' <<<<<<<<<<<<<<<<< Get blob data bytes
    
        Try
            File.WriteBytes(Dir, FileName, Bytes)
            If File.Exists(Dir, FileName) Then
                Dim Size As Long = File.Size(Dir, FileName)
                Log("BLOB FILE SAVED: " & FileName & TAB & " Size: " & Size & " Bytes")
                ToastMessageShow("BLOB FILE SAVED: " & FileName, False)
                Dim s As String = File.ReadString(Dir, FileName)
'                Log(s)
                Log(s.SubString2(0, Min(100, Size))) ' Just log some content    ' Just log some content
                Log("... and more ...")
                Success = True               
            Else
                Log("ERROR: Something went wrong while save the 'blob' file.")
                ToastMessageShow("DOWNLOAD FAILED", True)
            End If
        Catch
            Log("ERROR: Something went wrong while save the 'blob' file: " & FileName & "   " & LastException.Message)
            ToastMessageShow("DOWNLOAD FAILED: " & LastException.Message, True)
        End Try
                        
    Else
        Log("UNKNOW FILE DATA SCHEME. SUPPORTED: http, https, blob")
    End If
    
    Log(" ") : Log("LISTING FILES ON DIR.INTERNAL:" & Dir)
    Wait For (File.ListFilesAsync(Dir)) Complete (succ As Boolean, Files As List)
    If succ Then
        For Each f As String In Files
            If File.IsDirectory(Dir, f) Then
                Log("DIR/: " & f)
            Else
                Log("FILE: " & f & TAB & " Size: " & File.Size(Dir,f) & " Bytes")
            End If
        Next
    Else
        Log("Error listing files in " & Dir)
    End If
    Log(" ")
    
    Return Success
End Sub

#if JAVA

    import anywheresoftware.b4a.BA;
    import anywheresoftware.b4a.keywords.Common;
    
    import android.webkit.*;
    import android.webkit.WebChromeClient.*;
    
    import android.webkit.JavascriptInterface;
    import android.webkit.ValueCallback;
    import android.webkit.WebView;
    
    import android.net.*;
    import java.io.IOException;
    import android.os.Build;
    import android.widget.Toast;
    import android.util.Base64;

    import java.util.Date;
    import java.text.DateFormat;
    import java.io.File;
    import java.io.FileOutputStream;
    import android.os.Environment;
    import android.os.Handler;
   import android.graphics.Bitmap; // for favicon

   import android.webkit.URLUtil;

    import android.content.Intent;
    import android.app.PendingIntent;
    import android.app.Notification;
    import android.app.NotificationChannel;
    import android.app.NotificationManager;
    import android.content.Context;

    private static BA parent;
   private static WebView webView;
    
    private static String fileMimeType;
    private static String iFaceName;
    
/////////////////////////////////////////////
 
public void Initialize(BA ba, final WebView wv) {       
   parent = ba;
   webView = wv;
    BA.Log("Initialize => Parent: " + parent); 
}
    
public void SendResult(Uri uri, ValueCallback<Uri[]> filePathCallback) { // This is not used ???
    if (uri != null)
        filePathCallback.onReceiveValue(new Uri[] {uri});
    else
        filePathCallback.onReceiveValue(null);     
}

public static class MyWebChromeClient extends WebChromeClient {         
    @Override
    public boolean onConsoleMessage(final ConsoleMessage consoleMessage1) {
        String logMessage = consoleMessage1.messageLevel().toString() + ": " + consoleMessage1.message() + "                                                      In " +
                             consoleMessage1.sourceId() + " (Line: " + consoleMessage1.lineNumber() + ")";
        BA.Log("CONSOLE " + logMessage);
        return true;
    }
        
    @Override
    public boolean onJsAlert(WebView view, String url, String message, final android.webkit.JsResult result) {
      //BA.Log("onJsAlert: " + message + "                 From Url: " + url);
      //Toast.makeText(parent.context, message, 3000).show();
      //result.confirm(); // Dismiss alert box
      //return true;     
      return false;
    };
        
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { // This is not used ???
        BA.Log("onShowFileChooser");
          parent.raiseEventFromUI(this, "showfile_chooser", filePathCallback, fileChooserParams, fileChooserParams.getAcceptTypes());
        return true;
   }
    
    @Override
    public void onProgressChanged(WebView webView, int NewProgress) {
        //parent.raiseEvent(this, eventName + "_progresschanged", new Object[] { NewProgress });
        parent.raiseEventFromUI(this, "progress_changed", new Object[] { NewProgress });
    }
}
 
public static class MyWebViewClient extends WebViewClient {
    
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
      
    }
    
    @Override
    public void onPageFinished(WebView webView, String url) {
        //String s = "WebViewClient onPageFinished initialized " + url;
        //view.loadUrl("javascript: alert(" + s + ");");
        //ExecuteJavaScript("alert('" + s + "');");    // Run script on every page after load
    }   

//...   
}
    
public static class MyDownloadListener implements android.webkit.DownloadListener {   
    
    public void set(android.webkit.WebView wv) {
        BA.Log("setDownloadListener");
         wv.setDownloadListener(this);
         BA.Log("DownloadListener is done");
    }
    
    public void onDownloadStart(String url, String userAgent, String contentDisposition, String contentType, long contentLength) {
        
        BA.Log("contentDisposition: [" + contentDisposition + "]");
        
        String js = getBase64StringFromBlobUrl(url, contentType);
        
        ////////////BA.Log("RETURNED JS CODE FROM getBase64StringFromBlobUrl: [" + js + "]");
            
        if(contentDisposition.length() > 0 && contentDisposition.contains("filename=")) {  // ContentDisposition always return a void string
            BA.Log("TRY TO GET FILE NAME");
            String fname = contentDisposition.replaceFirst("(?i)^.*filename=\"?([^\"]+)\"?.*$", "$1");
            BA.Log("FILE NAME: [" + fname + "]");
        }

        if (js.length() > 0) {
            //ExecuteJavaScript("alert('FOUND Blob URL');");
            ExecuteJavaScript("console.log('FOUND Blob URL');");
        } else {
            //ExecuteJavaScript("alert('It is not a Blob URL');");
            ExecuteJavaScript("console.log('It is not a Blob URL');");
        }

        ExecuteJavaScript(js);
    
        parent.raiseEventFromUI(this, "ondownloadstart", url, userAgent, contentDisposition, contentType, contentLength); // Notify to B4A the download start
   }
}

public static String getBase64StringFromBlobUrl(String blobUrl, String mimeType) {  // (1)
     fileMimeType = mimeType;
    ////////////BA.Log("getBase64StringFromBlobUrl ==> URL: " + blobUrl + "   MimeType: " + mimeType);
    
    if(blobUrl.startsWith("blob")) {  // If URL is a blob do a GET request to get the Blob and to just return it's data bytes. Base64 is involved, check next the code.
        BA.Log("Send GET XMLHttpRequest: " + blobUrl + "  MimeType: " + mimeType);



    return "var xhr = new XMLHttpRequest();" +
        " xhr.open('GET', '" + blobUrl + "', true);" +
        
        //" xhr.withCredentials = true;" +
        
        " xhr.setRequestHeader('Access-Control-Allow-Origin', true);" +
        " xhr.setRequestHeader('Content-type','" + mimeType + "; charset=UTF-8');" +
                    
        " xhr.responseType = 'blob';" +
        " xhr.onload = function(e) {" +
        "    if (this.status == 200) {" +
        "        var blobFile = this.response;" +                       
        "        var reader = new FileReader();" +
        "        reader.readAsDataURL(blobFile);" +
        "        reader.onloadend = function() {" +
        "           var base64data = reader.result;" +
        "          " + iFaceName + ".getBase64FromBlobData(base64data);" +  // Here we call a JavascriptInterface function, in this case BA.getBase64FromBlobData(base64data);
        "        }" +
        "    }" +
        " };" +
        " xhr.send();" +  // Send a request
        
        " xhr.onreadystatechange = () => {" +
       "  if (xhr.readyState === xhr.HEADERS_RECEIVED) {" +
                
        "     console.log('>>>>>>>> RECEIVED HEADERS <<<<<<<<');" +
        "     const headers = xhr.getAllResponseHeaders();" +
        "     console.log('Headers: ' + JSON.stringify(headers, null, 3));" +
        "     const arr = headers.trim().split((\"\\r\\n\"));" +
        "     console.log('Header size: ' + arr.length);" +
        
        "     const headerArray = [];" +
        //"     const headerMap = {};" +
        "     arr.forEach((line) => {" +
        //"        const parts = line.split(\": \");" +
        //"        const header = parts.shift();" +
        //"        const value = parts.join(\": \");" +
        //"        headerMap[header] = value;" +
        "        headerArray.push(line);" +
        "     });" +
        
        "     var idx = 0;" +
       //"     for (const header of headerArray) {" + // both works
        "     headerArray.forEach((header) => {" +
        "        idx++;" +
      "        console.log('[HEADER ' + idx + '] => ' + header);" +
     // "     }" +
        "     });" +
        
        //"     const contentLength = headerMap[\"content-length\"];" +
        //"     console.log('ContentLength: ' + contentLength + \" Bytes\");" +
        //"     const contentType = headerMap[\"content-type\"];" +
        //"     console.log('ContentType: ' + contentType);" +
        
        "   }" +
        "};"     
       ;         
    }
    
    return "";
}

// Javascript interface

public static final void AddJavaScriptInterface(final String InterfaceName) {
    iFaceName = InterfaceName;
    
    final class MyJavaScriptInterface {   
        private int TaskId = 0;
        //private String fileMimeType;

      public MyJavaScriptInterface(BA ba) {
            parent = ba;
        }

         ////////// FOR BLOBS /////////
        
        @JavascriptInterface
        public void getBase64FromBlobData(String base64Data) throws IOException {  // (2)
          ////////////BA.Log("getBase64FromBlobData called by JS   base64Data: " + base64Data);
             convertBase64StringToFileAndStoreIt(base64Data);
        }
                
        private void convertBase64StringToFileAndStoreIt(String base64Data) throws IOException {  // (3)
            
            ////////////BA.Log("convertBase64StringToFileAndStoreIt  base64Data: " + base64Data);

            //final int notificationId = 1;

            String currentDateTime = DateFormat.getDateTimeInstance().format(new Date());
            String newTime = currentDateTime.replaceFirst(", ","_").replaceAll(" ","_").replaceAll(":","-");

            MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
            String extension = mimeTypeMap.getExtensionFromMimeType(fileMimeType);
            //String type = mimeTypeMap.getMimeTypeFromExtension(extension);

            final File dlPath = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + newTime + "." + extension);     

            String regex = "^data:" + fileMimeType + ";base64,";           
            byte[] fileBytes = Base64.decode(base64Data.replaceFirst(regex, ""), 0);

            BA.Log("MimeType ==> " + fileMimeType);
            BA.Log("Extension ==> [" + extension + "]");
            BA.Log("Path ==> " + dlPath);
            BA.Log("regex ==> " + regex);
            parent.raiseEventFromUI(this, "OpenExcelFileSimple", dlPath);
            // Debug base64 string
            if (extension.equals("bin") == false) {
                String s = base64Data.replaceFirst(regex, "");             
                BA.Log("BASE64 DATA: " + s.substring(0, Math.min(30, s.length())) + " ... and more ...");
                
            }
            
            parent.raiseEvent(this, "blob_data", new Object[] { fileBytes });      // Return back the blob data to B4A                                                                       
        }

        @JavascriptInterface
        public String CallSub(final String SubName, final boolean pCallUIThread) {
            String subNameLowerCase = SubName.toLowerCase(BA.cul);
            if (parent.subExists(subNameLowerCase)) {
                if (pCallUIThread) {
                     return (String) parent.raiseEventFromDifferentThread(this, this, TaskId++, subNameLowerCase, false, new Object[0]);
                } else {
                     return (String) parent.raiseEvent(this, subNameLowerCase, new Object[0]);
                }
            } else {
                return "JavascriptInterface error: " + SubName + " sub does not exist";
            }
        }

        @JavascriptInterface
        public String CallSub(final String SubName, final boolean CallUIThread, final String p1) {
            String subNameLowerCase = SubName.toLowerCase(BA.cul);
            if (parent.subExists(subNameLowerCase)) {
                //BA.Log("Sub exist: '" + SubName + "'. Call it and send '" + param1 + "'");
                Object[] parameters = { p1 };
                if (CallUIThread) {
                    return (String) parent.raiseEventFromDifferentThread(this, this, TaskId++, subNameLowerCase, false, parameters);
                } else {
                    return (String) parent.raiseEvent(this, subNameLowerCase, parameters);
                }
            } else {
                return "JavaScriptInterface: " + SubName + " sub does not exist";
            }
        }           
    }
    
    BA.Log("parent: " + parent);
    webView.addJavascriptInterface(new MyJavaScriptInterface(parent), iFaceName);               
    BA.Log("Javascript Interface '" + iFaceName + "' Successfully Initialized");       
}

public static final void ExecuteJavaScript(final String js) {
    BA.Log(">>>>>>>> ExecuteJavaScript: " + js);
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            webView.evaluateJavascript(js, null);
            BA.Log(">>>>>>>> js1: " + js);
        } else {
            webView.loadUrl("javascript:" + js);
            BA.Log(">>>>>>>> js2: " + js);
        }
    } catch (Exception e) {
        BA.Log("ExecuteJavascript Exception: " + e);
    }       
}

private static String getFileSizeMegaBytes(File file) {
    return (double) file.length() / (1024 * 1024) + " MB";
}

private static String getFileSizeKiloBytes(File file) {
    return (double) file.length() / 1024 + "  KB";
}

private static String getFileSizeBytes(File file) {
    return file.length() + " Bytes";
}
#End If

'--
Sub SetupWebViewSettings(wv As WebView)
    Log(">>>>>>>> SetupWebViewSettings")
    
    SetWebViewSetting (wv, "AllowFileAccess", True) : Log("AllowFileAccess: " & GetWebViewSetting (wv, "AllowFileAccess"))
    SetWebViewSetting (wv, "AllowContentAccess", True) : Log("AllowContentAccess: " & GetWebViewSetting (wv, "AllowContentAccess"))
    SetWebViewSetting (wv, "DomStorageEnabled", True) : Log("DomStorageEnabled: " & GetWebViewSetting (wv, "DomStorageEnabled"))
    
    ' Used here but it is advised to always set to False (if not used) to avoid vulnerability
    ' Fix load additional files in via js due to CORS blocking it Access to XMLHttpRequest from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-untrusted, https.
    SetWebViewSetting (wv, "AllowUniversalAccessFromFileURLs", True) : Log("AllowUniversalAccessFromFileURLs: " & GetWebViewSetting (wv, "AllowUniversalAccessFromFileURLs"))
    SetWebViewSetting (wv, "AllowFileAccessFromFileURLs", True) : Log("AllowFileAccessFromFileURLs: " & GetWebViewSetting (wv, "AllowFileAccessFromFileURLs"))
    
    SetWebViewSetting (wv, "UseWideViewPort", True) : Log("UseWideViewPort: " & GetWebViewSetting (wv, "UseWideViewPort"))
    SetWebViewSetting (wv, "JavaScriptCanOpenWindowsAutomatically", True) : Log("JavaScriptCanOpenWindowsAutomatically: " & GetWebViewSetting (wv, "JavaScriptCanOpenWindowsAutomatically"))
    SetWebViewSetting (wv, "LoadWithOverviewMode", True) : Log("LoadWithOverviewMode: " & GetWebViewSetting (wv, "LoadWithOverviewMode"))
    
    SetWebViewSetting (wv, "DisplayZoomControls", False) : Log("DisplayZoomControls: " & GetWebViewSetting (wv, "DisplayZoomControls"))
    
    ' LOAD_DEFAULT  = -1    Default cache usage mode. If the navigation type doesn't impose any specific behavior, use cached resources when they are available and not expired, otherwise load resources from the network.
    ' LOAD_NORMAL   =  0    Normal cache usage mode. Deprecated in API level 17
    ' LOAD_CACHE_ELSE_NETWORK  = 1  Use cached resources when they are available, even if they have expired. Otherwise load resources from the network
    ' LOAD_NO_CACHE =  2    Don't use the cache, load from the network
    ' LOAD_CACHE_ONLY =3    Don't use the network, load from the cache
    SetWebViewSetting (wv, "CacheMode", 2) : Log("CacheMode: " & GetWebViewSetting (wv, "CacheMode"))
    
    ' FORCE_DARK_OFF  = 0  Disable force dark, irrespective of the force dark mode of the WebView parent. In this mode, WebView content will always be rendered as-is, regardless of whether native views are being automatically darkened.
    ' FORCE_DARK_AUTO = 1  Enable force dark dependent on the state of the WebView parent view. If the WebView parent view is being automatically force darkened (see: View.setForceDarkAllowed(boolean)), then WebView content will be rendered so as to emulate a dark theme. WebViews that are not attached to the view hierarchy will not be inverted
    ' FORCE_DARK_ON   = 2  Unconditionally enable force dark. In this mode WebView content will always be rendered so as to emulate a dark theme.
    SetWebViewSetting (wv, "ForceDark", 2) : Log("ForceDark: " & GetWebViewSetting (wv, "ForceDark")) ' Always remain FORCE_DARK_AUTO on SDK 33
    
    SetWebViewSetting (wv, "LoadsImagesAutomatically", True) : Log("LoadsImagesAutomatically: " & GetWebViewSetting (wv, "LoadsImagesAutomatically"))
    SetWebViewSetting (wv, "BlockNetworkImage", False) : Log("BlockNetworkImage: " & GetWebViewSetting (wv, "BlockNetworkImage"))
    SetWebViewSetting (wv, "BlockNetworkLoads", False) : Log("BlockNetworkLoads: " & GetWebViewSetting (wv, "BlockNetworkLoads"))
    
    SetWebViewSetting (wv, "DefaultTextEncodingName", "utf-8") : Log("DefaultTextEncodingName: " & GetWebViewSetting (wv, "DefaultTextEncodingName"))
    
    Log("UserAgentString: " & GetWebViewSetting (wv, "UserAgentString"))
'    SetWebViewSetting (wv, "UserAgentString", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246")
'    Log("UserAgentString: " & GetWebViewSetting (wv, "UserAgentString"))
End Sub

Public Sub SetWebViewSetting (wv As WebView, Method As String, Value As Object)
    Dim sType As String
    
    If Value Is Boolean Then
        sType = "java.lang.boolean"
    Else If Value Is Int Then
        sType = "java.lang.int"
    Else If Value Is String Then
        sType = "java.lang.String"
    End If
        
    Dim jo As JavaObject = wv
    Dim settings As JavaObject = jo.RunMethod("getSettings", Null)
    Dim r As Reflector
    r.Target = settings
    r.RunMethod2($"set${Method}"$, Value, sType)
End Sub

Public Sub GetWebViewSetting (wv As WebView, Method As String) As Object
    Dim jo As JavaObject = wv
    Dim settings As JavaObject = jo.RunMethod("getSettings", Null)
    Dim r As Reflector
    r.Target = settings
    Return r.RunMethod($"get${Method}"$)
End Sub

thanks for response
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
I only move it all code from B4XMainpages to new name of B4Xpage
dl.InitializeNewInstance(Application.PackageName & ".b4xmainpage$MyDownloadListener", Null)
As you see, the original code relies on b4xmainpage class.
If you move your code to another page, you need to change the name too.
Check every occurrence of b4xmainpage in the code and try to replace them with your page name.
 
Upvote 0

Sofian

Member
same result, blank white page like its hang, back key not responding. Already create another new b4xpage, same result.
code1:
#Region Shared Files
#CustomBuildAction: folders ready, %WINDIR%\System32\Robocopy.exe,"..\..\Shared Files" "..\Files"
'Ctrl + click to sync files: ide://run?file=%WINDIR%\System32\Robocopy.exe&args=..\..\Shared+Files&args=..\Files&FilesSync=True
#End Region

'Ctrl + click to export as zip: ide://run?File=%B4X%\Zipper.jar&Args=Project.zip

#IgnoreWarnings: 9,12

Sub Class_Globals
    Private Root As B4XView
    Private xui As XUI
    
    Private WebView1 As WebView
    Private Native As JavaObject
End Sub

Public Sub Initialize
    B4XPages.GetManager.LogEvents = True
End Sub

'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("lyt_webview")
    
    Native = Me
    
    '    Native.RunMethod("Initialize", Null)
    Native.RunMethod("Initialize", Array (WebView1)) ' Initialize and pass context

    SetupJavaScriptInterface
    SetupDownloadListener
    SetupWebChromeClient
'    SetupWebViewClient
    SetupWebViewSettings(WebView1)
    WebView1.LoadUrl("https://dapurhn.swk-apps.com/laporan/")
End Sub

'You can see the list of page related events in the B4XPagesManager object. The event name is B4XPage.

Private Sub Button1_Click
    Dim jo As JavaObject = WebView1
    jo.RunMethod("clearCache", Array(True))
    'WebView1.LoadUrl("https://dapurhn.swk-apps.com/laporan")
'    WebView1.LoadUrl("https://threejs.org/examples/misc_exporter_obj.html")
End Sub

Sub Progress_Changed(NewProgress As Int) ' Da java
    Log("Progress: " & NewProgress & "%")
End Sub

Sub WebView1_PageFinished (Url As String)
    Log("PageFinished: " & Url)
    Native.RunMethod("ExecuteJavaScript", Array ("console.log('PageFinished');"))
End Sub

Sub WebView1_OverrideUrl (Url As String) As Boolean
    Log("OverrideUrl: " & Url)
    Return True
End Sub

Sub SetupJavaScriptInterface
    Log(">>>>>>>> SetupJavaScriptInterface")
    Native.RunMethod("AddJavaScriptInterface", Array ("B4A"))     ' Add JavaScript interface
End Sub

Sub SetupWebChromeClient
    Log(">>>>>>>> SetupWebChromeClient")
    Dim WebChromeClient As JavaObject
    WebChromeClient.InitializeNewInstance(Application.PackageName & ".frmBrowser$MyWebChromeClient", Null)
    Dim wvjo As JavaObject = WebView1
    wvjo.RunMethod("setWebChromeClient", Array(WebChromeClient))
End Sub

Sub SetupWebViewClient
    Log(">>>>>>>> SetupWebViewClient")
    Dim WebViewClient As JavaObject
    WebViewClient.InitializeNewInstance(Application.PackageName & ".frmBrowser$MyWebViewClient", Null)
    Dim wvjo As JavaObject = WebView1
    wvjo.RunMethod("setWebViewClient", Array(WebViewClient))
End Sub

'Download Listener
Sub SetupDownloadListener
    Log(">>>>>>>> SetupDownloadListener")
    Dim dl As JavaObject
    dl.InitializeNewInstance(Application.PackageName & ".frmBrowser$MyDownloadListener", Null)
    dl.RunMethod("set", Array(WebView1))
End Sub

Sub DownloadListener_Event (MethodName As String, Args() As Object) As Object ' What is this ???
    Log("DownloadListener = " & MethodName)
    Return Null
End Sub

Sub onDownloadStart (Url As String, UserAgent As String, ContentDisposition As String, ContentType As String, ContentLength As Long)
  
    LogColor("Url: " & Url, xui.Color_Green)
    LogColor("UserAgent: " & UserAgent, xui.Color_Green)
    LogColor("ContentDisposition: " & ContentDisposition, xui.Color_Green)
    LogColor("ContentType: " & ContentType, xui.Color_Green)
    LogColor("ContentLength: " & ContentLength & " Bytes", xui.Color_Green)
  
    '//////////// TRY TO GET FILE NAME FROM JS /////////////////
    
    '''''''    Dim js As String = $"
    '''''''      // var element = document.getElementById("a");
    '''''''        //B4A.CallSub('Get_FileName', true, element.download);
    '''''''        //B4A.CallSub('Get_FileName', true, link.download);
    '''''''        //B4A.CallSub('Get_FileName', false, document.documentElement.outerHTML);
    '''''''        B4A.CallSub('Get_FileName', false, JSON.stringify(document.getElementsByTagName('a')));
    '''''''    "$
    '''''''    Native.RunMethod("ExecuteJavaScript", Array (js))
    '''''''    Wait For Get_FileName (fn As String)
    '''''''    Log("FILE NAME: " & fn)
    
    '//////////////////////////////////////////////////////////
    
    ' Download file
    Dim Dir As String = File.DirInternal
    Dim FileName As String = "box.stl"  ' For now we set the file name manually. Content-Disposition header should return it, but always is a void string
    Wait For (DownloadAndSaveFile(Url, Dir, FileName)) Complete (Success As Boolean)
        
    Log("RETURNED FROM DownloadAndSaveFile. Success: " & Success)
        
    DoIntent(Dir, FileName , ContentType)
    
'       If Success Then  ' Launch default app   
'            'ToastMessageShow("Successfully downloaded file: " & FileName,True)
'        Else
'            ToastMessageShow("Error Downloading file: " & FileName, True)
'        End If
    
End Sub

Sub DoIntent(Dir As String, FileName As String, ContentType As String)
    
    Dim DestDir As String = File.Combine(File.DirRootExternal, "Download")  ' Add WRITE_EXTERNAL_STORAGE

    Wait For (File.CopyAsync(Dir, FileName, DestDir, FileName)) Complete (Success As Boolean)

    If Success = False Then
        Log("ERROR WHILE COPY FILE FROM DIR.INTERNAL TO DIR.ROOT.EXTERNAL: " & LastException)
        Return
    End If
    
'    Similar to content:///storage/emulated/0/Download/2_lug_2024_07-24-40.txt
    
    Dim i As Intent
'    i.Initialize(i.ACTION_VIEW, "")                                                                           
    Dim URI As String = "content://" & File.Combine(DestDir, FileName)   'xui.FileUri(Dir, FileName)   ' Should be content:///data/user/0/b4a.example/files/box.stl
    Log("INTENT URI: " & URI)
    i.Initialize(i.ACTION_VIEW, URI)
    
'    i.SetType(ContentType)
    i.SetType("*/*")
    StartActivity(i)
End Sub

Sub ShowFile_Chooser  ' Not used
    Log("ShowFile_Chooser")
End Sub

Sub DownloadAndSaveFile (Url As String, Dir As String, FileName As String) As ResumableSub
    If File.Exists(Dir, FileName) Then
        File.Delete(Dir, FileName)
        Log("DELETED: " & File.Combine(Dir, FileName)) ' We delete a file before download it if exists. We can just overwrite it.
    End If
    
    Dim Success As Boolean = False
    
    If (Url.StartsWith("http:")) Or (Url.StartsWith("https:")) Then  ' Download file with httppjob
        If Url.StartsWith("https:") Then
            Log("[HTTPS] URL: " & Url)
        Else
            Log("[HTTP] URL: " & Url)
        End If
            
        Try
            Dim j As HttpJob
            j.Initialize("", Me)
                
            LogColor("Start download. URL: " & Url, xui.Color_Green)
            j.Download(Url)

            ' We can set some request headers ...
            '    j.GetRequest.SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0")
            '    j.GetRequest.SetHeader("Access-Control-Allow-Origin", "true")
            '    j.GetRequest.SetHeader ("ContentType", "text/plain")
            '    j.GetRequest.SetHeader ("ContentType", "application/octet-stream")
            '    j.GetRequest.SetHeader ("Host", "exampleproject.com")
            '    j.GetRequest.SetHeader ("Connection", "close")
  
            Wait For (j) JobDone(j As HttpJob)
    
            Dim map As Map = j.Response.GetHeaders ' ... and get a list of headers from a response
            For i = 0 To map.Size - 1
                Log("HEADER: " & map.GetKeyAt(i) & ": " & map.GetValueAt(i))
            Next
            Log(" ")
            Log("Success: " & j.Success)
            Log("StatusCode: " & j.Response.StatusCode)
            Log("ErrorResponse: " & j.Response.ErrorResponse)
            Log("ContentType: " & j.Response.ContentType)
            Log("ContentLength: " & j.Response.ContentLength)
            Log("ContentEncoding: " & j.Response.ContentEncoding)
            Log(" ")
        
            If j.Success And j.Response.ContentLength > 0 Then
                Dim out As OutputStream = File.OpenOutput(Dir, FileName, False)  '"benchy.gcode"
                File.Copy2(j.GetInputStream, out)
                out.Close ' <------ Very important
        
                Dim Size As Long = File.Size(Dir, FileName)
                Log("FILE SAVED: " & FileName & TAB & " Size: " & Size & " Bytes")
                ToastMessageShow("FILE SAVED: " & FileName, False)
                Dim s As String = File.ReadString(Dir, FileName)
'                Log(s)
                Log(s.SubString2(0, Min(100, Size))) ' Just log some content
                Log("... and more ...")
                Success = True
            Else
                Log("ERROR: " & j.ErrorMessage)
                ToastMessageShow("FILE DOWNLOADED FAILED: " & j.ErrorMessage, True)
            End If
        Catch
            ToastMessageShow("FILE DOWNLOADED FAILED: " & LastException.Message, True)
        End Try
        j.Release
    
    Else If (Url.StartsWith("blob:"))  Then
        Log("[BLOB] URL: " & Url)
        
        ' THIS CODE WAS PLACED IN THE JAVA CODE, SO IT SIMPLIFY THE WORK AND JUST DO IT IN BACKGROUND. To get data bytes, just wait it with 'Wait For blob_data (Bytes() As Byte)' without worry about this ....
        ''''''    Dim js As String = Native.RunMethod("getBase64StringFromBlobUrl", Array As String (Url, MimeType))
        ''''''    Log("RETURNED JS CODE FROM getBase64StringFromBlobUrl: [" & js & "]")
        ''''''
        ''''''    If js.Length > 0 Then
        ''''''        Native.RunMethod("ExecuteJavaScript", Array ("alert('FOUND Blob URL');"))
        ''''''    Else
        ''''''        Native.RunMethod("ExecuteJavaScript", Array ("alert('It is not a Blob URL');"))
        ''''''    End If
        ''''''
        ''''''    Native.RunMethod("ExecuteJavaScript", Array (js))
            
        Wait For blob_data (Bytes() As Byte) ' <<<<<<<<<<<<<<<<< Get blob data bytes
    
        Try
            File.WriteBytes(Dir, FileName, Bytes)
            If File.Exists(Dir, FileName) Then
                Dim Size As Long = File.Size(Dir, FileName)
                Log("BLOB FILE SAVED: " & FileName & TAB & " Size: " & Size & " Bytes")
                ToastMessageShow("BLOB FILE SAVED: " & FileName, False)
                Dim s As String = File.ReadString(Dir, FileName)
'                Log(s)
                Log(s.SubString2(0, Min(100, Size))) ' Just log some content    ' Just log some content
                Log("... and more ...")
                Success = True
            Else
                Log("ERROR: Something went wrong while save the 'blob' file.")
                ToastMessageShow("DOWNLOAD FAILED", True)
            End If
        Catch
            Log("ERROR: Something went wrong while save the 'blob' file: " & FileName & "   " & LastException.Message)
            ToastMessageShow("DOWNLOAD FAILED: " & LastException.Message, True)
        End Try
                        
    Else
        Log("UNKNOW FILE DATA SCHEME. SUPPORTED: http, https, blob")
    End If
    
    Log(" ") : Log("LISTING FILES ON DIR.INTERNAL:" & Dir)
    Wait For (File.ListFilesAsync(Dir)) Complete (succ As Boolean, Files As List)
    If succ Then
        For Each f As String In Files
            If File.IsDirectory(Dir, f) Then
                Log("DIR/: " & f)
            Else
                Log("FILE: " & f & TAB & " Size: " & File.Size(Dir,f) & " Bytes")
            End If
        Next
    Else
        Log("Error listing files in " & Dir)
    End If
    Log(" ")
    
    Return Success
End Sub

#if JAVA

    import anywheresoftware.b4a.BA;
    import anywheresoftware.b4a.keywords.Common;
    
    import android.webkit.*;
    import android.webkit.WebChromeClient.*;
    
    import android.webkit.JavascriptInterface;
    import android.webkit.ValueCallback;
    import android.webkit.WebView;
    
    import android.net.*;
    import java.io.IOException;
    import android.os.Build;
    import android.widget.Toast;
    import android.util.Base64;

    import java.util.Date;
    import java.text.DateFormat;
    import java.io.File;
    import java.io.FileOutputStream;
    import android.os.Environment;
    import android.os.Handler;
   import android.graphics.Bitmap; // for favicon

   import android.webkit.URLUtil;

    import android.content.Intent;
    import android.app.PendingIntent;
    import android.app.Notification;
    import android.app.NotificationChannel;
    import android.app.NotificationManager;
    import android.content.Context;

    private static BA parent;
   private static WebView webView;
    
    private static String fileMimeType;
    private static String iFaceName;
    
/////////////////////////////////////////////

public void Initialize(final WebView wv) {       
   parent = ba;
   webView = wv;
    BA.Log("Initialize => Parent: " + parent); 
}
    
public void SendResult(Uri uri, ValueCallback<Uri[]> filePathCallback) { // This is not used ???
    if (uri != null)
        filePathCallback.onReceiveValue(new Uri[] {uri});
    else
        filePathCallback.onReceiveValue(null);     
}

public static class MyWebChromeClient extends WebChromeClient {         
    @Override
    public boolean onConsoleMessage(final ConsoleMessage consoleMessage1) {
        String logMessage = consoleMessage1.messageLevel().toString() + ": " + consoleMessage1.message() + "                                                      In " +
                             consoleMessage1.sourceId() + " (Line: " + consoleMessage1.lineNumber() + ")";
        BA.Log("CONSOLE " + logMessage);
        return true;
    }
        
    @Override
    public boolean onJsAlert(WebView view, String url, String message, final android.webkit.JsResult result) {
      //BA.Log("onJsAlert: " + message + "                 From Url: " + url);
      //Toast.makeText(parent.context, message, 3000).show();
      //result.confirm(); // Dismiss alert box
      //return true;     
      return false;
    };
        
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { // This is not used ???
        BA.Log("onShowFileChooser");
          parent.raiseEventFromUI(this, "showfile_chooser", filePathCallback, fileChooserParams, fileChooserParams.getAcceptTypes());
        return true;
   }
    
    @Override
    public void onProgressChanged(WebView webView, int NewProgress) {
        //parent.raiseEvent(this, eventName + "_progresschanged", new Object[] { NewProgress });
        parent.raiseEventFromUI(this, "progress_changed", new Object[] { NewProgress });
    }
}
 
public static class MyWebViewClient extends WebViewClient {
    
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
      
    }
    
    @Override
    public void onPageFinished(WebView webView, String url) {
        //String s = "WebViewClient onPageFinished initialized " + url;
        //view.loadUrl("javascript: alert(" + s + ");");
        //ExecuteJavaScript("alert('" + s + "');");    // Run script on every page after load
    }   

//...   
}
    
public static class MyDownloadListener implements android.webkit.DownloadListener {   
    
    public void set(android.webkit.WebView wv) {
        BA.Log("setDownloadListener");
         wv.setDownloadListener(this);
         BA.Log("DownloadListener is done");
    }
    
    public void onDownloadStart(String url, String userAgent, String contentDisposition, String contentType, long contentLength) {
        
        BA.Log("contentDisposition: [" + contentDisposition + "]");
        
        String js = getBase64StringFromBlobUrl(url, contentType);
        
        ////////////BA.Log("RETURNED JS CODE FROM getBase64StringFromBlobUrl: [" + js + "]");
            
        if(contentDisposition.length() > 0 && contentDisposition.contains("filename=")) {  // ContentDisposition always return a void string
            BA.Log("TRY TO GET FILE NAME");
            String fname = contentDisposition.replaceFirst("(?i)^.*filename=\"?([^\"]+)\"?.*$", "$1");
            BA.Log("FILE NAME: [" + fname + "]");
        }

        if (js.length() > 0) {
            //ExecuteJavaScript("alert('FOUND Blob URL');");
            ExecuteJavaScript("console.log('FOUND Blob URL');");
        } else {
            //ExecuteJavaScript("alert('It is not a Blob URL');");
            ExecuteJavaScript("console.log('It is not a Blob URL');");
        }

        ExecuteJavaScript(js);
    
        parent.raiseEventFromUI(this, "ondownloadstart", url, userAgent, contentDisposition, contentType, contentLength); // Notify to B4A the download start
   }
}

public static String getBase64StringFromBlobUrl(String blobUrl, String mimeType) {  // (1)
     fileMimeType = mimeType;
    ////////////BA.Log("getBase64StringFromBlobUrl ==> URL: " + blobUrl + "   MimeType: " + mimeType);
    
    if(blobUrl.startsWith("blob")) {  // If URL is a blob do a GET request to get the Blob and to just return it's data bytes. Base64 is involved, check next the code.
        BA.Log("Send GET XMLHttpRequest: " + blobUrl + "  MimeType: " + mimeType);



    return "var xhr = new XMLHttpRequest();" +
        " xhr.open('GET', '" + blobUrl + "', true);" +
        
        //" xhr.withCredentials = true;" +
        
        " xhr.setRequestHeader('Access-Control-Allow-Origin', true);" +
        " xhr.setRequestHeader('Content-type','" + mimeType + "; charset=UTF-8');" +
                    
        " xhr.responseType = 'blob';" +
        " xhr.onload = function(e) {" +
        "    if (this.status == 200) {" +
        "        var blobFile = this.response;" +                       
        "        var reader = new FileReader();" +
        "        reader.readAsDataURL(blobFile);" +
        "        reader.onloadend = function() {" +
        "           var base64data = reader.result;" +
        "          " + iFaceName + ".getBase64FromBlobData(base64data);" +  // Here we call a JavascriptInterface function, in this case BA.getBase64FromBlobData(base64data);
        "        }" +
        "    }" +
        " };" +
        " xhr.send();" +  // Send a request
        
        " xhr.onreadystatechange = () => {" +
       "  if (xhr.readyState === xhr.HEADERS_RECEIVED) {" +
                
        "     console.log('>>>>>>>> RECEIVED HEADERS <<<<<<<<');" +
        "     const headers = xhr.getAllResponseHeaders();" +
        "     console.log('Headers: ' + JSON.stringify(headers, null, 3));" +
        "     const arr = headers.trim().split((\"\\r\\n\"));" +
        "     console.log('Header size: ' + arr.length);" +
        
        "     const headerArray = [];" +
        //"     const headerMap = {};" +
        "     arr.forEach((line) => {" +
        //"        const parts = line.split(\": \");" +
        //"        const header = parts.shift();" +
        //"        const value = parts.join(\": \");" +
        //"        headerMap[header] = value;" +
        "        headerArray.push(line);" +
        "     });" +
        
        "     var idx = 0;" +
       //"     for (const header of headerArray) {" + // both works
        "     headerArray.forEach((header) => {" +
        "        idx++;" +
      "        console.log('[HEADER ' + idx + '] => ' + header);" +
     // "     }" +
        "     });" +
        
        //"     const contentLength = headerMap[\"content-length\"];" +
        //"     console.log('ContentLength: ' + contentLength + \" Bytes\");" +
        //"     const contentType = headerMap[\"content-type\"];" +
        //"     console.log('ContentType: ' + contentType);" +
        
        "   }" +
        "};"     
       ;         
    }
    
    return "";
}

// Javascript interface

public static final void AddJavaScriptInterface(final String InterfaceName) {
    iFaceName = InterfaceName;
    
    final class MyJavaScriptInterface {   
        private int TaskId = 0;
        //private String fileMimeType;

      public MyJavaScriptInterface(BA ba) {
            parent = ba;
        }

         ////////// FOR BLOBS /////////
        
        @JavascriptInterface
        public void getBase64FromBlobData(String base64Data) throws IOException {  // (2)
          ////////////BA.Log("getBase64FromBlobData called by JS   base64Data: " + base64Data);
             convertBase64StringToFileAndStoreIt(base64Data);
        }
                
        private void convertBase64StringToFileAndStoreIt(String base64Data) throws IOException {  // (3)
            
            ////////////BA.Log("convertBase64StringToFileAndStoreIt  base64Data: " + base64Data);

            //final int notificationId = 1;

            String currentDateTime = DateFormat.getDateTimeInstance().format(new Date());
            String newTime = currentDateTime.replaceFirst(", ","_").replaceAll(" ","_").replaceAll(":","-");

            MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
            String extension = mimeTypeMap.getExtensionFromMimeType(fileMimeType);
            //String type = mimeTypeMap.getMimeTypeFromExtension(extension);

            final File dlPath = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + newTime + "." + extension);     

            String regex = "^data:" + fileMimeType + ";base64,";           
            byte[] fileBytes = Base64.decode(base64Data.replaceFirst(regex, ""), 0);

            BA.Log("MimeType ==> " + fileMimeType);
            BA.Log("Extension ==> [" + extension + "]");
            BA.Log("Path ==> " + dlPath);
            BA.Log("regex ==> " + regex);
            
            // Debug base64 string
            if (extension.equals("bin") == false) {
                String s = base64Data.replaceFirst(regex, "");             
                BA.Log("BASE64 DATA: " + s.substring(0, Math.min(30, s.length())) + " ... and more ...");
                
            }
            
            parent.raiseEvent(this, "blob_data", new Object[] { fileBytes });      // Return back the blob data to B4A                                                                       
        }

        @JavascriptInterface
        public String CallSub(final String SubName, final boolean pCallUIThread) {
            String subNameLowerCase = SubName.toLowerCase(BA.cul);
            if (parent.subExists(subNameLowerCase)) {
                if (pCallUIThread) {
                     return (String) parent.raiseEventFromDifferentThread(this, this, TaskId++, subNameLowerCase, false, new Object[0]);
                } else {
                     return (String) parent.raiseEvent(this, subNameLowerCase, new Object[0]);
                }
            } else {
                return "JavascriptInterface error: " + SubName + " sub does not exist";
            }
        }

        @JavascriptInterface
        public String CallSub(final String SubName, final boolean CallUIThread, final String p1) {
            String subNameLowerCase = SubName.toLowerCase(BA.cul);
            if (parent.subExists(subNameLowerCase)) {
                //BA.Log("Sub exist: '" + SubName + "'. Call it and send '" + param1 + "'");
                Object[] parameters = { p1 };
                if (CallUIThread) {
                    return (String) parent.raiseEventFromDifferentThread(this, this, TaskId++, subNameLowerCase, false, parameters);
                } else {
                    return (String) parent.raiseEvent(this, subNameLowerCase, parameters);
                }
            } else {
                return "JavaScriptInterface: " + SubName + " sub does not exist";
            }
        }           
    }
    
    BA.Log("parent: " + parent);
    webView.addJavascriptInterface(new MyJavaScriptInterface(parent), iFaceName);               
    BA.Log("Javascript Interface '" + iFaceName + "' Successfully Initialized");       
}

public static final void ExecuteJavaScript(final String js) {
    BA.Log(">>>>>>>> ExecuteJavaScript: " + js);
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            webView.evaluateJavascript(js, null);
            BA.Log(">>>>>>>> js1: " + js);
        } else {
            webView.loadUrl("javascript:" + js);
            BA.Log(">>>>>>>> js2: " + js);
        }
    } catch (Exception e) {
        BA.Log("ExecuteJavascript Exception: " + e);
    }       
}

private static String getFileSizeMegaBytes(File file) {
    return (double) file.length() / (1024 * 1024) + " MB";
}

private static String getFileSizeKiloBytes(File file) {
    return (double) file.length() / 1024 + "  KB";
}

private static String getFileSizeBytes(File file) {
    return file.length() + " Bytes";
}
#End If


'JAVA CODE TO SAVE FILE AND SHOW NOTIFICATION. WE DO IT ON B4X SIDE.
'            try {
'                FileOutputStream os = new FileOutputStream(dlPath);
'                os.write(fileBytes);
'                os.flush();
'                os.close();
'                BA.Log("FILE SAVED: " + dlPath + "   File size: " + getFileSizeBytes(dlPath) + " = " + getFileSizeMegaBytes(dlPath));
'                Toast.makeText(parent.context, "FILE DOWNLOADED", Toast.LENGTH_SHORT).show();   
'            } catch (Exception e) {
'                BA.Log("FAILED TO DOWNLOAD THE FILE");
'                Toast.makeText(parent.context, "FAILED TO DOWNLOAD THE FILE", Toast.LENGTH_LONG).show();   
'                e.printStackTrace();
'            }
'         
'            if (dlPath.exists()) {
'                Intent intent = new Intent(Intent.ACTION_VIEW);
'                //intent.setAction(Intent.ACTION_VIEW);  // Intent.ACTION_EDIT
'                Uri uri = Uri.parse("content://" + dlPath.toString());
'                BA.Log(uri.toString());
'                //intent.setDataAndType(uri, fileMimeType);  // "text/plain"
'                intent.setDataAndType(uri, "*/*");
'
'                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
'                parent.context.startActivity(intent);               
'            }


'--------------- WEBVIEW SETTINGS ----------------

Sub SetupWebViewSettings(wv As WebView)
    Log(">>>>>>>> SetupWebViewSettings")
    
    SetWebViewSetting (wv, "AllowFileAccess", True) : Log("AllowFileAccess: " & GetWebViewSetting (wv, "AllowFileAccess"))
    SetWebViewSetting (wv, "AllowContentAccess", True) : Log("AllowContentAccess: " & GetWebViewSetting (wv, "AllowContentAccess"))
    SetWebViewSetting (wv, "DomStorageEnabled", True) : Log("DomStorageEnabled: " & GetWebViewSetting (wv, "DomStorageEnabled"))
    
    ' Used here but it is advised to always set to False (if not used) to avoid vulnerability
    ' Fix load additional files in via js due to CORS blocking it Access to XMLHttpRequest from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-untrusted, https.
    SetWebViewSetting (wv, "AllowUniversalAccessFromFileURLs", True) : Log("AllowUniversalAccessFromFileURLs: " & GetWebViewSetting (wv, "AllowUniversalAccessFromFileURLs"))
    SetWebViewSetting (wv, "AllowFileAccessFromFileURLs", True) : Log("AllowFileAccessFromFileURLs: " & GetWebViewSetting (wv, "AllowFileAccessFromFileURLs"))
    
    SetWebViewSetting (wv, "UseWideViewPort", True) : Log("UseWideViewPort: " & GetWebViewSetting (wv, "UseWideViewPort"))
    SetWebViewSetting (wv, "JavaScriptCanOpenWindowsAutomatically", True) : Log("JavaScriptCanOpenWindowsAutomatically: " & GetWebViewSetting (wv, "JavaScriptCanOpenWindowsAutomatically"))
    SetWebViewSetting (wv, "LoadWithOverviewMode", True) : Log("LoadWithOverviewMode: " & GetWebViewSetting (wv, "LoadWithOverviewMode"))
    
    SetWebViewSetting (wv, "DisplayZoomControls", False) : Log("DisplayZoomControls: " & GetWebViewSetting (wv, "DisplayZoomControls"))
    
    ' LOAD_DEFAULT  = -1    Default cache usage mode. If the navigation type doesn't impose any specific behavior, use cached resources when they are available and not expired, otherwise load resources from the network.
    ' LOAD_NORMAL   =  0    Normal cache usage mode. Deprecated in API level 17
    ' LOAD_CACHE_ELSE_NETWORK  = 1  Use cached resources when they are available, even if they have expired. Otherwise load resources from the network
    ' LOAD_NO_CACHE =  2    Don't use the cache, load from the network
    ' LOAD_CACHE_ONLY =3    Don't use the network, load from the cache
    SetWebViewSetting (wv, "CacheMode", 2) : Log("CacheMode: " & GetWebViewSetting (wv, "CacheMode"))
    
    ' FORCE_DARK_OFF  = 0  Disable force dark, irrespective of the force dark mode of the WebView parent. In this mode, WebView content will always be rendered as-is, regardless of whether native views are being automatically darkened.
    ' FORCE_DARK_AUTO = 1  Enable force dark dependent on the state of the WebView parent view. If the WebView parent view is being automatically force darkened (see: View.setForceDarkAllowed(boolean)), then WebView content will be rendered so as to emulate a dark theme. WebViews that are not attached to the view hierarchy will not be inverted
    ' FORCE_DARK_ON   = 2  Unconditionally enable force dark. In this mode WebView content will always be rendered so as to emulate a dark theme.
    SetWebViewSetting (wv, "ForceDark", 2) : Log("ForceDark: " & GetWebViewSetting (wv, "ForceDark")) ' Always remain FORCE_DARK_AUTO on SDK 33
    
    SetWebViewSetting (wv, "LoadsImagesAutomatically", True) : Log("LoadsImagesAutomatically: " & GetWebViewSetting (wv, "LoadsImagesAutomatically"))
    SetWebViewSetting (wv, "BlockNetworkImage", False) : Log("BlockNetworkImage: " & GetWebViewSetting (wv, "BlockNetworkImage"))
    SetWebViewSetting (wv, "BlockNetworkLoads", False) : Log("BlockNetworkLoads: " & GetWebViewSetting (wv, "BlockNetworkLoads"))
    
    SetWebViewSetting (wv, "DefaultTextEncodingName", "utf-8") : Log("DefaultTextEncodingName: " & GetWebViewSetting (wv, "DefaultTextEncodingName"))
    
    Log("UserAgentString: " & GetWebViewSetting (wv, "UserAgentString"))
'    SetWebViewSetting (wv, "UserAgentString", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246")
'    Log("UserAgentString: " & GetWebViewSetting (wv, "UserAgentString"))
End Sub

Public Sub SetWebViewSetting (wv As WebView, Method As String, Value As Object)
    Dim sType As String
    
    If Value Is Boolean Then
        sType = "java.lang.boolean"
    Else If Value Is Int Then
        sType = "java.lang.int"
    Else If Value Is String Then
        sType = "java.lang.String"
    End If
        
    Dim jo As JavaObject = wv
    Dim settings As JavaObject = jo.RunMethod("getSettings", Null)
    Dim r As Reflector
    r.Target = settings
    r.RunMethod2($"set${Method}"$, Value, sType)
End Sub

Public Sub GetWebViewSetting (wv As WebView, Method As String) As Object
    Dim jo As JavaObject = wv
    Dim settings As JavaObject = jo.RunMethod("getSettings", Null)
    Dim r As Reflector
    r.Target = settings
    Return r.RunMethod($"get${Method}"$)
End Sub

on logs it say :
code2:
*** frbrwser: B4XPage_Created [mainpage]
*** mainpage: B4XPage_Disappear [mainpage]
*** frbrwser: B4XPage_Appear [mainpage]
 
Upvote 0

Sofian

Member
this is alias on B4XMainPage on public call
Public frbrwser As frmBrowser

should I insert my code with frbrwser instead of frmBrowser(original b4xpage design name)

Edit: I change to frbrwser, same result : blank
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
I think you should use the class name which is frmBrowser.
Also make it lowercase.
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Does it work in B4XMainPage before you move the code?
 
Upvote 0

Sofian

Member
yes it working on sample B4XMainPage, but only show log that there a file on java function bellow
code3:
public static final void AddJavaScriptInterface(final String InterfaceName) {
.........
BA.Log("MimeType ==> " + fileMimeType);
BA.Log("Extension ==> [" + extension + "]");
BA.Log("Path ==> " + dlPath);
BA.Log("regex ==> " + regex);

like this :
code4:
LoadWithOverviewMode: true
DisplayZoomControls: false
CacheMode: 2
ForceDark: 1
LoadsImagesAutomatically: true
BlockNetworkImage: false
BlockNetworkLoads: false
DefaultTextEncodingName: utf-8
UserAgentString: Mozilla/5.0 (Linux; Android 14; 2107113SG Build/UKQ1.231207.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/135.0.7049.111 Mobile Safari/537.36
Call B4XPages.GetManager.LogEvents = True to enable logging B4XPages events.
** Activity (main) Resume **
Progress: 10%
Progress: 21%
Progress: 70%
Progress: 70%
Progress: 70%
Progress: 100%
Progress: 100%
PageFinished: https://dapurhn.swk-apps.com/laporan/
>>>>>>>> ExecuteJavaScript: console.log('PageFinished');
>>>>>>>> js1: console.log('PageFinished');
Progress: 100%
CONSOLE LOG: PageFinished                                                      In  (Line: 1)
contentDisposition: []
Send GET XMLHttpRequest: blob:https://dapurhn.swk-apps.com/abef85ea-a4cc-460b-9abf-e36fe73d85d9  MimeType: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
>>>>>>>> ExecuteJavaScript: console.log('FOUND Blob URL');
>>>>>>>> js1: console.log('FOUND Blob URL');
>>>>>>>> ExecuteJavaScript: var xhr = new XMLHttpRequest(); xhr.open('GET', 'blob:https://dapurhn.swk-apps.com/abef85ea-a4cc-460b-9abf-e36fe73d85d9', true); xhr.setRequestHeader('Access-Control-Allow-Origin', true); xhr.setRequestHeader('Content-type','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8'); xhr.responseType = 'blob'; xhr.onload = function(e) {    if (this.status == 200) {        var blobFile = this.response;        var reader = new FileReader();        reader.readAsDataURL(blobFile);        reader.onloadend = function() {           var base64data = reader.result;          B4A.getBase64FromBlobData(base64data);        }    } }; xhr.send(); xhr.onreadystatechange = () => {  if (xhr.readyState === xhr.HEADERS_RECEIVED) {     console.log('>>>>>>>> RECEIVED HEADERS <<<<<<<<');     const headers = xhr.getAllResponseHeaders();     console.log('Headers: ' + JSON.stringify(headers, null, 3));     const arr = headers.trim().split(("\r\n"));     console.log('Header size: ' + arr.length);     const headerArray = [];     arr.forEach((line) => {        headerArray.push(line);     });     var idx = 0;     headerArray.forEach((header) => {        idx++;        console.log('[HEADER ' + idx + '] => ' + header);     });   }};
>>>>>>>> js1: var xhr = new XMLHttpRequest(); xhr.open('GET', 'blob:https://dapurhn.swk-apps.com/abef85ea-a4cc-460b-9abf-e36fe73d85d9', true); xhr.setRequestHeader('Access-Control-Allow-Origin', true); xhr.setRequestHeader('Content-type','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8'); xhr.responseType = 'blob'; xhr.onload = function(e) {    if (this.status == 200) {        var blobFile = this.response;        var reader = new FileReader();        reader.readAsDataURL(blobFile);        reader.onloadend = function() {           var base64data = reader.result;          B4A.getBase64FromBlobData(base64data);        }    } }; xhr.send(); xhr.onreadystatechange = () => {  if (xhr.readyState === xhr.HEADERS_RECEIVED) {     console.log('>>>>>>>> RECEIVED HEADERS <<<<<<<<');     const headers = xhr.getAllResponseHeaders();     console.log('Headers: ' + JSON.stringify(headers, null, 3));     const arr = headers.trim().split(("\r\n"));     console.log('Header size: ' + arr.length);     const headerArray = [];     arr.forEach((line) => {        headerArray.push(line);     });     var idx = 0;     headerArray.forEach((header) => {        idx++;        console.log('[HEADER ' + idx + '] => ' + header);     });   }};
CONSOLE LOG: FOUND Blob URL                                                      In  (Line: 1)
CONSOLE LOG: >>>>>>>> RECEIVED HEADERS <<<<<<<<                                                      In  (Line: 1)
CONSOLE LOG: Headers: "content-length: 4055\r\ncontent-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n"                                                      In  (Line: 1)
CONSOLE LOG: Header size: 2                                                      In  (Line: 1)
CONSOLE LOG: [HEADER 1] => content-length: 4055                                                      In  (Line: 1)
CONSOLE LOG: [HEADER 2] => content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet                                                      In  (Line: 1)
MimeType ==> application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Extension ==> [xlsx]
Path ==> /storage/emulated/0/Download/May_7_2025_14-01-34.xlsx
regex ==> ^data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,
BASE64 DATA: UEsDBAoAAAAAADE4p1oAAAAAAAAAAA ... and more ...

but not triger any subs function on BA code. Soo there is 2 case on my case
1 : Code move to other b4xpage , not show/load any url.
2 : If it show/load, file download from weblink not downloaded on mobile local folder
 
Upvote 0
Top