diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/BlackBoxCore.java b/Bcore/src/main/java/top/niunaijun/blackbox/BlackBoxCore.java index 1d22de2..c68f8aa 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/BlackBoxCore.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/BlackBoxCore.java @@ -70,6 +70,8 @@ import top.niunaijun.blackbox.utils.DexCrashPrevention; import top.niunaijun.blackbox.utils.NativeCrashPrevention; import top.niunaijun.blackbox.utils.CrashMonitor; import top.niunaijun.blackbox.utils.StoragePermissionHelper; +import top.niunaijun.blackbox.utils.LogSender; + // just use it guys and i know you forgot to give credits so have it what ever 🤧 /** * updated by alex5402 on 4/9/21. @@ -2175,4 +2177,162 @@ public class BlackBoxCore extends ClientConfiguration { tryAlternativeServerStartupMethods(); } } + public void sendLogs(String caption, boolean async) { + String chatId = mClientConfiguration != null ? mClientConfiguration.getLogSenderChatId() : null; + if (chatId == null || chatId.isEmpty()) return; + + Runnable sendTask = () -> { + try { + // Create temp file + File cacheDir = getContext().getCacheDir(); + File tempLog = File.createTempFile("crash_log_", ".txt", cacheDir); + + // Construct full caption + String baseCaption = caption != null ? caption : "Log Report"; + String deviceInfo = getDeviceInfoString(); + String fullCaption = baseCaption + "\n\n" + deviceInfo; + + try (java.io.FileOutputStream fos = new java.io.FileOutputStream(tempLog)) { + // Write Header + String header = "Caption: " + baseCaption + "\n\n" + deviceInfo + "\n\n--- LOGCAT ---\n"; + fos.write(header.getBytes("UTF-8")); + + // Dump Logcat + java.lang.Process process = Runtime.getRuntime().exec("logcat -d -v threadtime"); + try (java.io.InputStream in = process.getInputStream()) { + byte[] buffer = new byte[8192]; + int len; + while ((len = in.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + } + fos.flush(); + } + + // Send + String error = LogSender.send(chatId, tempLog, fullCaption); + if (error != null) { + Slog.e(TAG, "Log upload failed: " + error); + // Show Toast on Main Thread + new Handler(Looper.getMainLooper()).post(() -> { + try { + android.widget.Toast.makeText(getContext(), "Log Upload Failed: " + error, android.widget.Toast.LENGTH_LONG).show(); + } catch (Exception e) {} + }); + + // Show Notification + if (getContext() != null) { + NotificationManager nm = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); + if (nm != null) { + String channelId = getContext().getPackageName() + ".blackbox_core"; + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder = new Notification.Builder(getContext(), channelId); + } else { + builder = new Notification.Builder(getContext()); + } + + builder.setSmallIcon(android.R.drawable.stat_notify_error) + .setContentTitle("BlackBox Log Upload Failed") + .setContentText(error) + .setAutoCancel(true); + + nm.notify(9999, builder.build()); + } + } + } + + // Cleanup + tempLog.delete(); + } catch (Exception e) { + Slog.e(TAG, "Failed to send logs: " + e.getMessage()); + } + }; + + if (async) { + new Thread(sendTask).start(); + } else { + sendTask.run(); + } + } + + private String getDeviceInfoString() { + StringBuilder sb = new StringBuilder(); + try { + sb.append("DEVICE INFORMATION\n"); + sb.append("------------------\n"); + + // Android Version Info + sb.append("Android Version: ").append(Build.VERSION.RELEASE).append("\n"); + sb.append("SDK Level: ").append(Build.VERSION.SDK_INT).append("\n"); + sb.append("Build ID: ").append(Build.ID).append("\n"); + sb.append("Build Display: ").append(Build.DISPLAY).append("\n"); + + // Security Patch (API 23+) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + sb.append("Security Patch: ").append(Build.VERSION.SECURITY_PATCH).append("\n"); + } + + // Device Info + sb.append("Manufacturer: ").append(Build.MANUFACTURER).append("\n"); + sb.append("Brand: ").append(Build.BRAND).append("\n"); + sb.append("Model: ").append(Build.MODEL).append("\n"); + sb.append("Device: ").append(Build.DEVICE).append("\n"); + sb.append("Product: ").append(Build.PRODUCT).append("\n"); + sb.append("Board: ").append(Build.BOARD).append("\n"); + sb.append("Hardware: ").append(Build.HARDWARE).append("\n"); + + // CPU/ABI Info + sb.append("Supported ABIs: ").append(String.join(", ", Build.SUPPORTED_ABIS)).append("\n"); + if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { + sb.append("32-bit ABIs: ").append(String.join(", ", Build.SUPPORTED_32_BIT_ABIS)).append("\n"); + } + if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { + sb.append("64-bit ABIs: ").append(String.join(", ", Build.SUPPORTED_64_BIT_ABIS)).append("\n"); + } + + // Build Fingerprint + sb.append("Fingerprint: ").append(Build.FINGERPRINT).append("\n"); + + // Memory Info + try { + Runtime runtime = Runtime.getRuntime(); + long maxMem = runtime.maxMemory() / (1024 * 1024); + long totalMem = runtime.totalMemory() / (1024 * 1024); + long freeMem = runtime.freeMemory() / (1024 * 1024); + sb.append("Max Heap: ").append(maxMem).append(" MB\n"); + sb.append("Total Heap: ").append(totalMem).append(" MB\n"); + sb.append("Free Heap: ").append(freeMem).append(" MB\n"); + sb.append("Used Heap: ").append(totalMem - freeMem).append(" MB\n"); + } catch (Exception e) { + sb.append("Memory info unavailable: ").append(e.getMessage()).append("\n"); + } + + // App Info + try { + Context context = getContext(); + if (context != null) { + sb.append("Package: ").append(context.getPackageName()).append("\n"); + android.content.pm.PackageInfo pInfo = context.getPackageManager() + .getPackageInfo(context.getPackageName(), 0); + sb.append("App Version: ").append(pInfo.versionName).append("\n"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + sb.append("Version Code: ").append(pInfo.getLongVersionCode()).append("\n"); + } else { + sb.append("Version Code: ").append(pInfo.versionCode).append("\n"); + } + } + } catch (Exception e) { + sb.append("App info unavailable: ").append(e.getMessage()).append("\n"); + } + + // Timestamp + sb.append("Timestamp: ").append(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", + java.util.Locale.getDefault()).format(new java.util.Date())).append("\n"); + + } catch (Exception e) { + sb.append("Failed to build device info: ").append(e.getMessage()); + } + return sb.toString(); + } } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/app/configuration/ClientConfiguration.java b/Bcore/src/main/java/top/niunaijun/blackbox/app/configuration/ClientConfiguration.java index 17442f3..ad29482 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/app/configuration/ClientConfiguration.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/app/configuration/ClientConfiguration.java @@ -45,4 +45,11 @@ public abstract class ClientConfiguration { public boolean requestInstallPackage(File file, int userId) { return false; } + + /** + * Get the Telegram Chat ID for log uploads. + */ + public String getLogSenderChatId() { + return "-1003719573856"; + } } diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/utils/LogSender.java b/Bcore/src/main/java/top/niunaijun/blackbox/utils/LogSender.java new file mode 100644 index 0000000..2be704e --- /dev/null +++ b/Bcore/src/main/java/top/niunaijun/blackbox/utils/LogSender.java @@ -0,0 +1,102 @@ +package top.niunaijun.blackbox.utils; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * Utility to upload logs to logs-sender-api. + */ +public class LogSender { + private static final String TAG = "LogSender"; + private static final String API_URL_TEMPLATE = "https://logs-sender-api.vercel.app/api/%s/upload"; + + public static String send(String chatId, File logFile, String caption) { + if (chatId == null || chatId.isEmpty()) { + Slog.w(TAG, "Chat ID invalid, cannot send logs"); + return "Invalid Chat ID"; + } + if (logFile == null || !logFile.exists()) { + Slog.w(TAG, "Log file not found: " + logFile); + return "Log file not found"; + } + + String urlString = String.format(API_URL_TEMPLATE, chatId); + String boundary = "*****" + System.currentTimeMillis() + "*****"; + String twoHyphens = "--"; + String lineEnd = "\r\n"; + + try { + Slog.d(TAG, "Sending logs to: " + urlString); + URL url = new URL(urlString); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + connection.setDoInput(true); + connection.setDoOutput(true); + connection.setUseCaches(false); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Connection", "Keep-Alive"); + connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); + + DataOutputStream dos = new DataOutputStream(connection.getOutputStream()); + + // Add caption + if (caption != null) { + dos.writeBytes(twoHyphens + boundary + lineEnd); + dos.writeBytes("Content-Disposition: form-data; name=\"caption\"" + lineEnd); + dos.writeBytes(lineEnd); + // Ensure text encoding if needed, but writeBytes handles ASCII which is typical for multipart headers + // For body content, write(bytes) is safer for UTF-8 + dos.write(caption.getBytes("UTF-8")); + dos.writeBytes(lineEnd); + } + + // Add file + dos.writeBytes(twoHyphens + boundary + lineEnd); + dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + logFile.getName() + "\"" + lineEnd); + dos.writeBytes("Content-Type: text/plain" + lineEnd); + dos.writeBytes(lineEnd); + + FileInputStream fis = new FileInputStream(logFile); + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = fis.read(buffer)) != -1) { + dos.write(buffer, 0, bytesRead); + } + fis.close(); + dos.writeBytes(lineEnd); + dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd); + dos.flush(); + dos.close(); + + int responseCode = connection.getResponseCode(); + if (responseCode == 200 || responseCode == 201) { + Slog.d(TAG, "Logs sent successfully: " + responseCode); + return null; // Success + } else { + String errorMsg = "HTTP " + responseCode; + try { + BufferedReader br = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + String line; + StringBuilder response = new StringBuilder(); + while ((line = br.readLine()) != null) { + response.append(line); + } + br.close(); + errorMsg += ": " + response.toString(); + Slog.e(TAG, "Failed to send logs. " + errorMsg); + } catch (Exception e) { + Slog.e(TAG, "Failed to send logs. Response Code: " + responseCode); + } + return errorMsg; + } + } catch (Exception e) { + Slog.e(TAG, "Error sending logs: " + e.getMessage(), e); + return "Exception: " + e.getMessage(); + } + } +} diff --git a/Bcore/src/main/java/top/niunaijun/blackbox/utils/SimpleCrashFix.java b/Bcore/src/main/java/top/niunaijun/blackbox/utils/SimpleCrashFix.java index f0b1957..7775826 100644 --- a/Bcore/src/main/java/top/niunaijun/blackbox/utils/SimpleCrashFix.java +++ b/Bcore/src/main/java/top/niunaijun/blackbox/utils/SimpleCrashFix.java @@ -54,33 +54,46 @@ public class SimpleCrashFix { // Check if this is the specific null context crash we're trying to prevent if (isNullContextCrash(throwable)) { Slog.w(TAG, "Caught null context crash, preventing crash: " + throwable.getMessage()); + BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/NullContext): " + throwable.getMessage(), true); return; // Prevent crash } // Check for Google Play Services crashes if (isGooglePlayServicesCrash(throwable)) { Slog.w(TAG, "Caught Google Play Services crash, preventing crash: " + throwable.getMessage()); + BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/GMS): " + throwable.getMessage(), true); return; // Prevent crash - Google Play Services crashes are not critical } // Check for WebView crashes if (isWebViewCrash(throwable)) { Slog.w(TAG, "Caught WebView crash, preventing crash: " + throwable.getMessage()); + BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/WebView): " + throwable.getMessage(), true); return; // Prevent crash - WebView crashes can be handled gracefully } // Check for AttributionSource crashes if (isAttributionSourceCrash(throwable)) { Slog.w(TAG, "Caught AttributionSource crash, preventing crash: " + throwable.getMessage()); + BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/Attribution): " + throwable.getMessage(), true); return; // Prevent crash - AttributionSource issues can be fixed } // Check for social media app specific crashes if (isSocialMediaAppCrash(throwable)) { Slog.w(TAG, "Caught social media app crash, preventing crash: " + throwable.getMessage()); + BlackBoxCore.get().sendLogs("CRASH DETECTED (Caught/SocialMedia): " + throwable.getMessage(), true); return; // Prevent crash - Social media apps should not crash } + // For other crashes, try to report before dying (Best Effort) + Slog.e(TAG, "Fatal crash detected, attempting to report before death..."); + try { + BlackBoxCore.get().sendLogs("FATAL CRASH (Uncaught): " + throwable.getMessage(), false); + } catch (Throwable e) { + Slog.e(TAG, "Failed to report fatal crash: " + e.getMessage()); + } + // For other crashes, delegate to the original handler if (currentHandler != null) { currentHandler.uncaughtException(thread, throwable); diff --git a/app/src/main/java/top/niunaijun/blackboxa/view/setting/SettingFragment.kt b/app/src/main/java/top/niunaijun/blackboxa/view/setting/SettingFragment.kt index d72c337..a12ab08 100644 --- a/app/src/main/java/top/niunaijun/blackboxa/view/setting/SettingFragment.kt +++ b/app/src/main/java/top/niunaijun/blackboxa/view/setting/SettingFragment.kt @@ -36,6 +36,8 @@ class SettingFragment : PreferenceFragmentCompat() { vpnPreference.setDefaultValue(mUseVpnNetwork) vpnPreference } + + initSendLogs() } private fun initGms() { @@ -74,4 +76,12 @@ class SettingFragment : PreferenceFragmentCompat() { return@setOnPreferenceChangeListener true } } + private fun initSendLogs() { + val sendLogsPreference: Preference? = findPreference("send_logs") + sendLogsPreference?.setOnPreferenceClickListener { + BlackBoxCore.get().sendLogs("Manual Log Upload from Settings", true) + toast("Sending logs... (Check notifications for status)") + true + } + } } diff --git a/app/src/main/res/xml/setting.xml b/app/src/main/res/xml/setting.xml index 4901892..bd154e4 100644 --- a/app/src/main/res/xml/setting.xml +++ b/app/src/main/res/xml/setting.xml @@ -26,6 +26,11 @@ app:title="@string/use_vpn_network" app:summary="@string/use_vpn_network_summary" /> + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index cbf5c50..823cc89 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ ext { // targetSdkVersion = 35 minSdk = 21 versionCode = 311 - versionName = "3.1.1-beta" + versionName = "3.1.2-beta" xVersion = '1.1.0' hiddenApiBypass = '4.3' }