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
Post a Comment