Enable download from WebView in Sketchware pro

To enable download from WebView, following steps are required.

1. Add a WebView webview1 in main.xml.

2. Switch ON AppCompat and design.

3. In permission manager add INTERNET permission.

4. In Java/Kotlin manager, add a new Java class file DownloadHelper.java. Put following codes in this file (change package name in first line to your own package name).


package com.my.newproject28;

import android.app.DownloadManager;
import android.content.Context;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.util.Base64;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;

import androidx.core.content.FileProvider;

import android.print.PrintManager;
import android.print.PrintDocumentAdapter;
import android.print.PrintAttributes;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class DownloadHelper {

    private Context context;
    private WebView webView;
    private static final String JS_INTERFACE_NAME = "Android";
    private static final String TAG = "DownloadHelper";

    public DownloadHelper(Context context, WebView webView) {
        this.context = context;
        this.webView = webView;
        setupJavaScriptInterface();
    }

    // Set up JavaScript interface for WebView communication
    private void setupJavaScriptInterface() {
        webView.addJavascriptInterface(this, JS_INTERFACE_NAME);
    }

    // Extract filename from URL or Content-Disposition
    private String extractFilenameFromUrl(String url, String contentDisposition, String mimeType) {
        String filename = null;
        
        // Try to get filename from Content-Disposition header
        if (contentDisposition != null && !contentDisposition.isEmpty()) {
            filename = parseContentDisposition(contentDisposition);
        }
        
        // If not found in header, extract from URL
        if (filename == null || filename.isEmpty()) {
            filename = extractFilenameFromUrlPath(url);
        }
        
        // If still no filename, generate one based on mime type and timestamp
        if (filename == null || filename.isEmpty()) {
            filename = generateDefaultFilename(mimeType);
        }
        
        // Sanitize filename (remove invalid characters)
        filename = sanitizeFilename(filename);
        
        return filename;
    }

    // Parse Content-Disposition header to get filename
    private String parseContentDisposition(String contentDisposition) {
        try {
            // Look for filename* (RFC 5987) or filename parameter
            String[] parts = contentDisposition.split(";");
            for (String part : parts) {
                part = part.trim();
                if (part.toLowerCase().startsWith("filename*=")) {
                    // RFC 5987 format: filename*=UTF-8''filename.ext
                    String value = part.substring(part.indexOf("=") + 1);
                    if (value.startsWith("UTF-8''")) {
                        value = value.substring(7);
                        return URLDecoder.decode(value, "UTF-8");
                    }
                } else if (part.toLowerCase().startsWith("filename=")) {
                    // Simple filename format
                    String value = part.substring(part.indexOf("=") + 1);
                    // Remove quotes if present
                    if (value.startsWith("\"") && value.endsWith("\"")) {
                        value = value.substring(1, value.length() - 1);
                    }
                    return value;
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Error parsing Content-Disposition: " + e.getMessage());
        }
        return null;
    }

    // Extract filename from URL path
    private String extractFilenameFromUrlPath(String url) {
        try {
            // Remove query parameters
            String path = url;
            int queryIndex = url.indexOf('?');
            if (queryIndex > 0) {
                path = url.substring(0, queryIndex);
            }
            
            // Get last segment after '/'
            int lastSlash = path.lastIndexOf('/');
            if (lastSlash >= 0 && lastSlash < path.length() - 1) {
                String filename = path.substring(lastSlash + 1);
                if (!filename.isEmpty() && !filename.contains("/")) {
                    // URL decode the filename
                    return URLDecoder.decode(filename, "UTF-8");
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Error extracting filename from URL: " + e.getMessage());
        }
        return null;
    }

    // Generate default filename based on mime type and timestamp
    private String generateDefaultFilename(String mimeType) {
        String extension = getFileExtensionFromMimeType(mimeType);
        String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
        return "download_" + timestamp + extension;
    }

    // Get file extension from mime type
    private String getFileExtensionFromMimeType(String mimeType) {
        if (mimeType == null) return ".bin";
        
        switch (mimeType.toLowerCase()) {
            case "application/pdf":
                return ".pdf";
            case "image/jpeg":
                return ".jpg";
            case "image/png":
                return ".png";
            case "image/gif":
                return ".gif";
            case "text/plain":
                return ".txt";
            case "text/html":
                return ".html";
            case "application/zip":
                return ".zip";
            case "application/x-zip-compressed":
                return ".zip";
            case "application/msword":
                return ".doc";
            case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
                return ".docx";
            case "application/vnd.ms-excel":
                return ".xls";
            case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
                return ".xlsx";
            case "application/vnd.ms-powerpoint":
                return ".ppt";
            case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
                return ".pptx";
            case "audio/mpeg":
                return ".mp3";
            case "audio/mp4":
                return ".m4a";
            case "video/mp4":
                return ".mp4";
            case "video/x-matroska":
                return ".mkv";
            default:
                return ".bin";
        }
    }

    // Remove invalid characters from filename
    private String sanitizeFilename(String filename) {
        if (filename == null) return "download.bin";
        
        // Remove path separators and other invalid characters
        filename = filename.replaceAll("[\\\\/:*?\"<>|]", "_");
        
        // Ensure filename is not empty after sanitization
        if (filename.isEmpty() || filename.equals(".") || filename.equals("..")) {
            return "download.bin";
        }
        
        // Truncate if too long
        if (filename.length() > 200) {
            String ext = "";
            int dotIndex = filename.lastIndexOf('.');
            if (dotIndex > 0 && dotIndex < filename.length() - 1) {
                ext = filename.substring(dotIndex);
                filename = filename.substring(0, Math.min(dotIndex, 190));
            }
            filename = filename + ext;
        }
        
        return filename;
    }

    // Get unique filename if file already exists
    private File getUniqueFile(File directory, String filename) {
        File file = new File(directory, filename);
        
        if (!file.exists()) {
            return file;
        }
        
        String baseName = filename;
        String extension = "";
        int dotIndex = filename.lastIndexOf('.');
        if (dotIndex > 0) {
            baseName = filename.substring(0, dotIndex);
            extension = filename.substring(dotIndex);
        }
        
        int counter = 1;
        while (file.exists()) {
            String newFilename = baseName + " (" + counter + ")" + extension;
            file = new File(directory, newFilename);
            counter++;
        }
        
        return file;
    }

    // Handle regular HTTP/HTTPS file downloads
    public void handleRegularDownload(String url, String userAgent, 
                                    String contentDisposition, String mimeType, 
                                    long contentLength) {
        try {
            // Extract correct filename
            String filename = extractFilenameFromUrl(url, contentDisposition, mimeType);
            
            Uri uri = Uri.parse(url);
            DownloadManager.Request request = new DownloadManager.Request(uri);
            
            request.setMimeType(mimeType);
            String cookies = CookieManager.getInstance().getCookie(url);
            if (cookies != null) {
                request.addRequestHeader("cookie", cookies);
            }
            request.addRequestHeader("User-Agent", userAgent);
            
            request.setTitle(filename);
            request.setDescription("Downloading file");
            request.allowScanningByMediaScanner();
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            
            // Set destination in Downloads folder
            File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            File destinationFile = getUniqueFile(downloadsDir, filename);
            
            // Use Uri.fromFile for the destination
            request.setDestinationUri(Uri.fromFile(destinationFile));
            
            DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
            if (dm != null) {
                long downloadId = dm.enqueue(request);
                Log.d(TAG, "Download started with ID: " + downloadId + ", filename: " + destinationFile.getName());
                
                // Monitor download completion to scan the file
                monitorDownloadCompletion(dm, downloadId, destinationFile);
                
                Toast.makeText(context, "Download started: " + destinationFile.getName(), Toast.LENGTH_LONG).show();
            }
        } catch (Exception e) {
            Log.e(TAG, "Regular download failed: " + e.getMessage());
            Toast.makeText(context, "Download failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    // Monitor download completion to scan the file
    private void monitorDownloadCompletion(final DownloadManager dm, final long downloadId, final File file) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                boolean downloading = true;
                while (downloading) {
                    DownloadManager.Query q = new DownloadManager.Query();
                    q.setFilterById(downloadId);
                    Cursor cursor = dm.query(q);
                    if (cursor != null && cursor.moveToFirst()) {
                        int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
                        if (status == DownloadManager.STATUS_SUCCESSFUL) {
                            downloading = false;
                            // Scan the file so it appears in gallery/file managers
                            MediaScannerConnection.scanFile(context, 
                                new String[]{file.getAbsolutePath()}, null, null);
                            Log.d(TAG, "Download completed and scanned: " + file.getName());
                        } else if (status == DownloadManager.STATUS_FAILED) {
                            downloading = false;
                            Log.e(TAG, "Download failed");
                        }
                        cursor.close();
                    }
                    try {
                        Thread.sleep(1000); // Check every second
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    // Handle blob URL downloads using JavaScript fetch API
    public void handleBlobUrlDownload(String blobUrl, String fileName, String mimeType) {
        try {
            // If filename is not provided or is empty, generate one
            if (fileName == null || fileName.isEmpty()) {
                fileName = generateDefaultFilename(mimeType);
            } else {
                fileName = sanitizeFilename(fileName);
            }
            
            final String javascript = "javascript: (function() {" +
                "var blobUrl = '" + blobUrl + "';" +
                "fetch(blobUrl)" +
                ".then(response => response.blob())" +
                ".then(blob => {" +
                "   var reader = new FileReader();" +
                "   reader.onloadend = function() {" +
                "       var base64data = reader.result;" +
                "       " + JS_INTERFACE_NAME + ".handleBase64Data(base64data, '" + mimeType + "', '" + fileName + "');" +
                "   };" +
                "   reader.readAsDataURL(blob);" +
                "})" +
                ".catch(error => {" +
                "   console.error('Blob download error:', error);" +
                "   " + JS_INTERFACE_NAME + ".handleBase64Data('', '" + mimeType + "', '" + fileName + "');" +
                "});" +
                "})()";
            
            webView.post(new Runnable() {
                @Override
                public void run() {
                    webView.loadUrl(javascript);
                    Log.d(TAG, "Blob download initiated");
                }
            });
        } catch (Exception e) {
            Log.e(TAG, "Blob URL download setup failed: " + e.getMessage());
            Toast.makeText(context, "Download setup failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    // JavaScript interface method to handle base64 data from blob URLs
    @JavascriptInterface
    public void handleBase64Data(String base64Data, String mimeType, String fileName) {
        try {
            if (base64Data == null || base64Data.isEmpty()) {
                throw new Exception("Empty base64 data received");
            }
            
            String pureBase64 = base64Data.substring(base64Data.indexOf(",") + 1);
            byte[] fileData = Base64.decode(pureBase64, Base64.DEFAULT);
            
            saveBase64Data(fileData, fileName, mimeType);
            
        } catch (final Exception e) {
            Log.e(TAG, "Blob download failed: " + e.getMessage());
            new Handler(context.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(context, "Download failed: " + e.getMessage(), Toast.LENGTH_LONG).show();
                }
            });
        }
    }

    // Save base64 data to Downloads directory with duplicate handling
    public void saveBase64Data(byte[] data, final String fileName, String mimeType) {
        try {
            File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            if (!downloadsDir.exists()) {
                downloadsDir.mkdirs();
            }
            
            // Sanitize filename and handle duplicates
            String safeFileName = sanitizeFilename(fileName);
            File file = getUniqueFile(downloadsDir, safeFileName);
            
            final String savedFileName = file.getName();
            
            // Write file
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(data);
            fos.close();
            
            // Scan file so it appears in gallery/file managers
            MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, 
                new String[]{mimeType}, new MediaScannerConnection.OnScanCompletedListener() {
                    @Override
                    public void onScanCompleted(String path, Uri uri) {
                        Log.d(TAG, "Media scan completed for: " + path);
                    }
                });
            
            // Show success message
            new Handler(context.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(context, "File saved: " + savedFileName, Toast.LENGTH_LONG).show();
                }
            });
            
            Log.d(TAG, "File saved successfully: " + file.getAbsolutePath());
            
        } catch (final Exception e) {
            Log.e(TAG, "Save base64 data failed: " + e.getMessage());
            new Handler(context.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(context, "Save failed: " + e.getMessage(), Toast.LENGTH_LONG).show();
                }
            });
        }
    }

    // Clean up JavaScript interface when no longer needed
    public void cleanup() {
        try {
            webView.removeJavascriptInterface(JS_INTERFACE_NAME);
            Log.d(TAG, "Cleanup completed");
        } catch (Exception e) {
            Log.e(TAG, "Cleanup failed: " + e.getMessage());
        }
    }
}

5. In onCreate, put following codes (change the url to the url you want to load).


WebSettings settings = binding.webview1.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setAllowFileAccess(true);
settings.setAllowContentAccess(true);
settings.setAllowFileAccessFromFileURLs(true);
settings.setAllowUniversalAccessFromFileURLs(true);

// Initialize download helper
final DownloadHelper downloadHelper = new DownloadHelper(this, binding.webview1);

binding.webview1.loadUrl("https://www.apkmirror.com/");

// Handle download requests
binding.webview1.setDownloadListener(new DownloadListener() {
    @Override
    public void onDownloadStart(String url, String userAgent, 
    String contentDisposition, String mimeType, 
    long contentLength) {
        
        if (url.startsWith("blob:") || url.startsWith("data:")) {
            // For blob URLs, let the DownloadHelper generate a filename
            downloadHelper.handleBlobUrlDownload(
                url,           // Blob URL
                null,          // Let DownloadHelper generate filename
                mimeType
            );
        } else {
            // For regular HTTP downloads
            downloadHelper.handleRegularDownload(
                url,
                userAgent,
                contentDisposition,
                mimeType,
                contentLength
            );
        }
    }
});

6. Run the project.

Comments

Popular posts from this blog

Simple car racing android game in Sketchware

How to enable upload from webview in Sketchware?

Custom dialog box in Sketchware using CustomView

Simple Audio recorder app in Sketchware

How to enable download in webview in Sketchware apps?