feat: Add log sending functionality with device information and logcat capture, including a new utility and UI integration, and update the application version.

This commit is contained in:
ArtRuntime
2026-02-02 19:47:52 +05:30
parent 5d0da8ec9c
commit 5288031742
7 changed files with 298 additions and 1 deletions
@@ -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();
}
}
@@ -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";
}
}
@@ -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();
}
}
}
@@ -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);
@@ -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
}
}
}
+5
View File
@@ -26,6 +26,11 @@
app:title="@string/use_vpn_network"
app:summary="@string/use_vpn_network_summary" />
<Preference
android:key="send_logs"
android:title="Send Logs"
android:summary="Upload debug logs to developer" />
</PreferenceCategory>
</PreferenceScreen>
+1 -1
View File
@@ -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'
}