Article
The "Shake for Bug Reports" feature allows users to report bugs by simply shaking their device, making bug reporting effortless and intuitive. This is particularly useful for mobile applications but can also be adapted for desktop applications with alternative triggers.
This guide covers everything from motion detection to comprehensive bug report generation and submission.
Architecture Overview
- Motion Detection: Accelerometer data processing for shake detection
- Screenshot Capture: Automatic screen capture when shake is detected
- Context Collection: App state, logs, and system information
- Report Generation: Structured bug report creation
- Submission: Sending reports to backend services
1. Project Setup and Dependencies
Maven Dependencies:
<properties>
<gson.version>2.10.1</gson.version>
<okhttp.version>4.11.0</okhttp.version>
<slf4j.version>2.0.7</slf4j.version>
</properties>
<dependencies>
<!-- JSON Processing -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- For Android-specific implementations -->
<!-- <dependency>
<groupId>androidx.appcompat</groupId>
<artifactId>appcompat</artifactId>
<version>1.6.1</version>
</dependency> -->
</dependencies>
2. Core Shake Detection
Motion Sensor Service (Generic Java):
package com.example.shakebug;
import java.util.ArrayList;
import java.util.List;
public class ShakeDetector {
private static final int SHAKE_THRESHOLD = 800;
private static final int MIN_SHAKE_INTERVAL = 1000; // milliseconds
private static final int MIN_SHAKE_COUNT = 2;
private long lastShakeTime;
private final List<ShakeListener> listeners = new ArrayList<>();
private final List<Float> accelerationHistory = new ArrayList<>();
private static final int HISTORY_SIZE = 10;
public interface ShakeListener {
void onShakeDetected();
}
public void addShakeListener(ShakeListener listener) {
listeners.add(listener);
}
public void removeShakeListener(ShakeListener listener) {
listeners.remove(listener);
}
// For applications with accelerometer access
public void onAccelerometerData(float x, float y, float z) {
long currentTime = System.currentTimeMillis();
// Calculate acceleration magnitude
float acceleration = (float) Math.sqrt(x * x + y * y + z * z);
// Add to history
accelerationHistory.add(acceleration);
if (accelerationHistory.size() > HISTORY_SIZE) {
accelerationHistory.remove(0);
}
// Check if we have enough data
if (accelerationHistory.size() < HISTORY_SIZE) {
return;
}
// Calculate average acceleration
float average = calculateAverageAcceleration();
// Check for shake (significant deviation from average)
if (Math.abs(acceleration - average) > SHAKE_THRESHOLD) {
if (currentTime - lastShakeTime > MIN_SHAKE_INTERVAL) {
lastShakeTime = currentTime;
notifyShakeDetected();
}
}
}
private float calculateAverageAcceleration() {
float sum = 0;
for (float accel : accelerationHistory) {
sum += accel;
}
return sum / accelerationHistory.size();
}
private void notifyShakeDetected() {
for (ShakeListener listener : listeners) {
listener.onShakeDetected();
}
}
// For desktop applications without accelerometer
public void simulateShake() {
notifyShakeDetected();
}
}
Android-Specific Shake Detection:
package com.example.shakebug.android;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
public class AndroidShakeDetector implements SensorEventListener {
private static final float SHAKE_THRESHOLD_GRAVITY = 2.7F;
private static final int SHAKE_SLOP_TIME_MS = 500;
private static final int SHAKE_COUNT_RESET_TIME_MS = 3000;
private long lastShakeTimestamp;
private int shakeCount;
private final ShakeDetector.ShakeListener shakeListener;
private final SensorManager sensorManager;
public AndroidShakeDetector(Context context, ShakeDetector.ShakeListener listener) {
this.shakeListener = listener;
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
}
public void start() {
Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelerometer != null) {
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
}
}
public void stop() {
sensorManager.unregisterListener(this);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float gX = x / SensorManager.GRAVITY_EARTH;
float gY = y / SensorManager.GRAVITY_EARTH;
float gZ = z / SensorManager.GRAVITY_EARTH;
// Calculate g-force
float gForce = (float) Math.sqrt(gX * gX + gY * gY + gZ * gZ);
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
final long now = System.currentTimeMillis();
// Ignize shake events too close to each other
if (lastShakeTimestamp + SHAKE_SLOP_TIME_MS > now) {
return;
}
// Reset shake count after 3 seconds of no shakes
if (lastShakeTimestamp + SHAKE_COUNT_RESET_TIME_MS < now) {
shakeCount = 0;
}
lastShakeTimestamp = now;
shakeCount++;
if (shakeCount >= 2) {
shakeCount = 0;
shakeListener.onShakeDetected();
}
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Not used
}
}
3. Bug Report Data Model
Bug Report Model:
package com.example.shakebug.model;
import java.util.*;
public class BugReport {
private final String id;
private final String title;
private final String description;
private final String userEmail;
private final long timestamp;
private final Map<String, Object> deviceInfo;
private final Map<String, Object> appInfo;
private final List<String> recentLogs;
private final String screenshotPath;
private final String stackTrace;
private final Map<String, Object> customFields;
private final String reportType;
public BugReport(Builder builder) {
this.id = builder.id;
this.title = builder.title;
this.description = builder.description;
this.userEmail = builder.userEmail;
this.timestamp = builder.timestamp;
this.deviceInfo = builder.deviceInfo;
this.appInfo = builder.appInfo;
this.recentLogs = builder.recentLogs;
this.screenshotPath = builder.screenshotPath;
this.stackTrace = builder.stackTrace;
this.customFields = builder.customFields;
this.reportType = builder.reportType;
}
// Getters
public String getId() { return id; }
public String getTitle() { return title; }
public String getDescription() { return description; }
public String getUserEmail() { return userEmail; }
public long getTimestamp() { return timestamp; }
public Map<String, Object> getDeviceInfo() { return Collections.unmodifiableMap(deviceInfo); }
public Map<String, Object> getAppInfo() { return Collections.unmodifiableMap(appInfo); }
public List<String> getRecentLogs() { return Collections.unmodifiableList(recentLogs); }
public String getScreenshotPath() { return screenshotPath; }
public String getStackTrace() { return stackTrace; }
public Map<String, Object> getCustomFields() { return Collections.unmodifiableMap(customFields); }
public String getReportType() { return reportType; }
public static class Builder {
private String id;
private String title = "Bug Report";
private String description;
private String userEmail;
private long timestamp = System.currentTimeMillis();
private Map<String, Object> deviceInfo = new HashMap<>();
private Map<String, Object> appInfo = new HashMap<>();
private List<String> recentLogs = new ArrayList<>();
private String screenshotPath;
private String stackTrace;
private Map<String, Object> customFields = new HashMap<>();
private String reportType = "SHAKE_REPORT";
public Builder(String id) {
this.id = id;
}
public Builder title(String title) {
this.title = title;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder userEmail(String email) {
this.userEmail = email;
return this;
}
public Builder timestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder deviceInfo(Map<String, Object> deviceInfo) {
this.deviceInfo.putAll(deviceInfo);
return this;
}
public Builder appInfo(Map<String, Object> appInfo) {
this.appInfo.putAll(appInfo);
return this;
}
public Builder recentLogs(List<String> logs) {
this.recentLogs.addAll(logs);
return this;
}
public Builder screenshotPath(String path) {
this.screenshotPath = path;
return this;
}
public Builder stackTrace(String stackTrace) {
this.stackTrace = stackTrace;
return this;
}
public Builder customField(String key, Object value) {
this.customFields.put(key, value);
return this;
}
public Builder reportType(String type) {
this.reportType = type;
return this;
}
public BugReport build() {
return new BugReport(this);
}
}
}
4. Context Collection Service
Context Collector:
package com.example.shakebug.service;
import com.example.shakebug.model.BugReport;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
public class ContextCollector {
private static final int MAX_LOG_LINES = 1000;
private final String logFilePath;
private final String appName;
private final String appVersion;
public ContextCollector(String appName, String appVersion, String logFilePath) {
this.appName = appName;
this.appVersion = appVersion;
this.logFilePath = logFilePath;
}
public Map<String, Object> collectDeviceInfo() {
Map<String, Object> deviceInfo = new HashMap<>();
try {
deviceInfo.put("hostname", InetAddress.getLocalHost().getHostName());
deviceInfo.put("ipAddress", InetAddress.getLocalHost().getHostAddress());
} catch (UnknownHostException e) {
deviceInfo.put("hostname", "unknown");
deviceInfo.put("ipAddress", "unknown");
}
deviceInfo.put("osName", System.getProperty("os.name"));
deviceInfo.put("osVersion", System.getProperty("os.version"));
deviceInfo.put("osArch", System.getProperty("os.arch"));
deviceInfo.put("javaVersion", System.getProperty("java.version"));
deviceInfo.put("javaVendor", System.getProperty("java.vendor"));
deviceInfo.put("userName", System.getProperty("user.name"));
deviceInfo.put("userHome", System.getProperty("user.home"));
deviceInfo.put("workingDirectory", System.getProperty("user.dir"));
// Runtime information
Runtime runtime = Runtime.getRuntime();
deviceInfo.put("availableProcessors", runtime.availableProcessors());
deviceInfo.put("freeMemory", runtime.freeMemory());
deviceInfo.put("totalMemory", runtime.totalMemory());
deviceInfo.put("maxMemory", runtime.maxMemory());
// JVM arguments
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
deviceInfo.put("jvmArguments", runtimeMxBean.getInputArguments());
deviceInfo.put("jvmUptime", runtimeMxBean.getUptime());
return deviceInfo;
}
public Map<String, Object> collectAppInfo() {
Map<String, Object> appInfo = new HashMap<>();
appInfo.put("appName", appName);
appInfo.put("appVersion", appVersion);
appInfo.put("timestamp", System.currentTimeMillis());
appInfo.put("timezone", TimeZone.getDefault().getID());
appInfo.put("locale", Locale.getDefault().toString());
// Thread information
ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
while (rootGroup.getParent() != null) {
rootGroup = rootGroup.getParent();
}
appInfo.put("activeThreads", rootGroup.activeCount());
return appInfo;
}
public List<String> collectRecentLogs() {
List<String> logs = new ArrayList<>();
if (logFilePath != null && Files.exists(Paths.get(logFilePath))) {
try {
logs = Files.lines(Paths.get(logFilePath))
.skip(Math.max(0, Files.lines(Paths.get(logFilePath)).count() - MAX_LOG_LINES))
.collect(Collectors.toList());
} catch (IOException e) {
logs.add("Failed to read log file: " + e.getMessage());
}
} else {
// Fallback: collect recent system out/err if available
logs.add("Log file not available at: " + logFilePath);
}
return logs;
}
public String captureStackTrace() {
StringBuilder stackTrace = new StringBuilder();
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTraceElements) {
stackTrace.append(element.toString()).append("\n");
}
return stackTrace.toString();
}
public Map<String, Object> collectSystemProperties() {
Map<String, Object> systemProperties = new HashMap<>();
Properties props = System.getProperties();
for (String key : props.stringPropertyNames()) {
// Filter out sensitive information
if (!key.toLowerCase().contains("password") &&
!key.toLowerCase().contains("secret") &&
!key.toLowerCase().contains("key")) {
systemProperties.put(key, props.getProperty(key));
}
}
return systemProperties;
}
public Map<String, Object> collectEnvironmentVariables() {
Map<String, Object> envVars = new HashMap<>();
Map<String, String> env = System.getenv();
for (String key : env.keySet()) {
// Filter out sensitive information
if (!key.toLowerCase().contains("password") &&
!key.toLowerCase().contains("secret") &&
!key.toLowerCase().contains("key")) {
envVars.put(key, env.get(key));
}
}
return envVars;
}
}
5. Screenshot Capture (Desktop)
Desktop Screenshot Service:
package com.example.shakebug.service;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ScreenshotService {
private final String screenshotDirectory;
public ScreenshotService(String screenshotDirectory) {
this.screenshotDirectory = screenshotDirectory;
createScreenshotDirectory();
}
private void createScreenshotDirectory() {
try {
Files.createDirectories(Paths.get(screenshotDirectory));
} catch (IOException e) {
System.err.println("Failed to create screenshot directory: " + e.getMessage());
}
}
public String captureScreenshot() {
try {
Rectangle screenRect = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
BufferedImage screenshot = new Robot().createScreenCapture(screenRect);
String filename = generateScreenshotFilename();
String filePath = screenshotDirectory + File.separator + filename;
// Save screenshot
javax.imageio.ImageIO.write(screenshot, "png", new File(filePath));
return filePath;
} catch (AWTException | IOException e) {
System.err.println("Failed to capture screenshot: " + e.getMessage());
return null;
}
}
public String captureComponentScreenshot(Component component) {
try {
BufferedImage image = new BufferedImage(
component.getWidth(),
component.getHeight(),
BufferedImage.TYPE_INT_RGB
);
component.paint(image.getGraphics());
String filename = generateScreenshotFilename();
String filePath = screenshotDirectory + File.separator + filename;
javax.imageio.ImageIO.write(image, "png", new File(filePath));
return filePath;
} catch (IOException e) {
System.err.println("Failed to capture component screenshot: " + e.getMessage());
return null;
}
}
private String generateScreenshotFilename() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");
String timestamp = dateFormat.format(new Date());
return "screenshot_" + timestamp + ".png";
}
public boolean cleanupScreenshot(String filePath) {
if (filePath != null) {
try {
return Files.deleteIfExists(Paths.get(filePath));
} catch (IOException e) {
System.err.println("Failed to delete screenshot: " + e.getMessage());
}
}
return false;
}
}
6. Bug Report Manager
Main Bug Report Manager:
package com.example.shakebug;
import com.example.shakebug.model.BugReport;
import com.example.shakebug.service.ContextCollector;
import com.example.shakebug.service.ScreenshotService;
import com.example.shakebug.service.BugReportSender;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class BugReportManager {
private final ShakeDetector shakeDetector;
private final ContextCollector contextCollector;
private final ScreenshotService screenshotService;
private final BugReportSender reportSender;
private final ScheduledExecutorService executor;
private boolean enabled = true;
private String currentUserEmail;
private final List<BugReportListener> listeners = new ArrayList<>();
public interface BugReportListener {
void onBugReportStarted();
void onBugReportCompleted(BugReport report);
void onBugReportFailed(Exception error);
}
public BugReportManager(ContextCollector contextCollector,
ScreenshotService screenshotService,
BugReportSender reportSender) {
this.contextCollector = contextCollector;
this.screenshotService = screenshotService;
this.reportSender = reportSender;
this.shakeDetector = new ShakeDetector();
this.executor = Executors.newSingleThreadScheduledExecutor();
setupShakeDetection();
}
private void setupShakeDetection() {
shakeDetector.addShakeListener(() -> {
if (enabled) {
triggerBugReport();
}
});
}
public void triggerBugReport() {
triggerBugReport("Bug reported via shake gesture", null);
}
public void triggerBugReport(String description, String stackTrace) {
if (!enabled) return;
notifyReportStarted();
CompletableFuture.supplyAsync(() -> {
try {
// Collect all context information
Map<String, Object> deviceInfo = contextCollector.collectDeviceInfo();
Map<String, Object> appInfo = contextCollector.collectAppInfo();
List<String> recentLogs = contextCollector.collectRecentLogs();
// Capture screenshot
String screenshotPath = screenshotService.captureScreenshot();
// Generate report ID
String reportId = UUID.randomUUID().toString();
// Build bug report
BugReport report = new BugReport.Builder(reportId)
.title("Shake Bug Report - " + new Date())
.description(description != null ? description : "Bug reported via shake gesture")
.userEmail(currentUserEmail)
.deviceInfo(deviceInfo)
.appInfo(appInfo)
.recentLogs(recentLogs)
.screenshotPath(screenshotPath)
.stackTrace(stackTrace != null ? stackTrace : contextCollector.captureStackTrace())
.customField("trigger", "shake")
.build();
return report;
} catch (Exception e) {
throw new RuntimeException("Failed to collect bug report data", e);
}
}, executor).thenCompose(report -> {
// Send the report
return reportSender.sendReportAsync(report);
}).thenAccept(report -> {
notifyReportCompleted(report);
}).exceptionally(throwable -> {
notifyReportFailed(throwable);
return null;
});
}
public void triggerManualBugReport(String description, String userEmail) {
this.currentUserEmail = userEmail;
triggerBugReport(description, null);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setUserEmail(String email) {
this.currentUserEmail = email;
}
public void addBugReportListener(BugReportListener listener) {
listeners.add(listener);
}
public void removeBugReportListener(BugReportListener listener) {
listeners.remove(listener);
}
private void notifyReportStarted() {
for (BugReportListener listener : listeners) {
listener.onBugReportStarted();
}
}
private void notifyReportCompleted(BugReport report) {
for (BugReportListener listener : listeners) {
listener.onBugReportCompleted(report);
}
}
private void notifyReportFailed(Throwable error) {
Exception exception = error instanceof Exception ?
(Exception) error : new Exception(error.getMessage(), error);
for (BugReportListener listener : listeners) {
listener.onBugReportFailed(exception);
}
}
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
// For testing and manual triggering
public void simulateShake() {
shakeDetector.simulateShake();
}
}
7. Report Sending Service
Bug Report Sender:
package com.example.shakebug.service;
import com.example.shakebug.model.BugReport;
import com.google.gson.Gson;
import okhttp3.*;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
public class BugReportSender {
private final OkHttpClient httpClient;
private final Gson gson;
private final String apiUrl;
private final String apiKey;
public BugReportSender(String apiUrl, String apiKey) {
this.apiUrl = apiUrl;
this.apiKey = apiKey;
this.httpClient = new OkHttpClient();
this.gson = new Gson();
}
public CompletableFuture<BugReport> sendReportAsync(BugReport report) {
return CompletableFuture.supplyAsync(() -> {
try {
sendReport(report);
return report;
} catch (IOException e) {
throw new RuntimeException("Failed to send bug report", e);
}
});
}
public void sendReport(BugReport report) throws IOException {
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM);
// Add JSON report data
String reportJson = gson.toJson(report);
bodyBuilder.addFormDataPart("report", reportJson);
// Add screenshot if exists
if (report.getScreenshotPath() != null) {
File screenshotFile = new File(report.getScreenshotPath());
if (screenshotFile.exists()) {
bodyBuilder.addFormDataPart(
"screenshot",
screenshotFile.getName(),
RequestBody.create(screenshotFile, MediaType.parse("image/png"))
);
}
}
// Add log files if needed
// ... additional attachments
RequestBody requestBody = bodyBuilder.build();
Request request = new Request.Builder()
.url(apiUrl)
.post(requestBody)
.addHeader("Authorization", "Bearer " + apiKey)
.addHeader("User-Agent", "ShakeBug-Client/1.0")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected response code: " + response.code() +
" - " + response.body().string());
}
// Cleanup screenshot after successful upload
if (report.getScreenshotPath() != null) {
new File(report.getScreenshotPath()).delete();
}
}
}
// Alternative: Save to local file system
public void saveReportLocally(BugReport report, String directory) throws IOException {
String filename = "bugreport_" + report.getId() + ".json";
java.nio.file.Files.write(
java.nio.file.Paths.get(directory, filename),
gson.toJson(report).getBytes()
);
}
}
8. Spring Boot Integration
Configuration:
package com.example.shakebug.config;
import com.example.shakebug.BugReportManager;
import com.example.shakebug.service.BugReportSender;
import com.example.shakebug.service.ContextCollector;
import com.example.shakebug.service.ScreenshotService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShakeBugConfig {
@Value("${app.name:MyApplication}")
private String appName;
@Value("${app.version:1.0.0}")
private String appVersion;
@Value("${app.log.path:logs/application.log}")
private String logFilePath;
@Value("${shakebug.screenshot.dir:temp/screenshots}")
private String screenshotDir;
@Value("${shakebug.api.url:https://api.example.com/bugreports}")
private String apiUrl;
@Value("${shakebug.api.key:}")
private String apiKey;
@Bean
public ContextCollector contextCollector() {
return new ContextCollector(appName, appVersion, logFilePath);
}
@Bean
public ScreenshotService screenshotService() {
return new ScreenshotService(screenshotDir);
}
@Bean
public BugReportSender bugReportSender() {
return new BugReportSender(apiUrl, apiKey);
}
@Bean
public BugReportManager bugReportManager(ContextCollector contextCollector,
ScreenshotService screenshotService,
BugReportSender reportSender) {
return new BugReportManager(contextCollector, screenshotService, reportSender);
}
}
REST Controller for Manual Reporting:
package com.example.shakebug.controller;
import com.example.shakebug.BugReportManager;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/bugreport")
public class BugReportController {
private final BugReportManager bugReportManager;
public BugReportController(BugReportManager bugReportManager) {
this.bugReportManager = bugReportManager;
}
@PostMapping("/manual")
public ResponseEntity<Map<String, String>> submitManualReport(
@RequestBody ManualReportRequest request) {
bugReportManager.triggerManualBugReport(
request.getDescription(),
request.getUserEmail()
);
return ResponseEntity.ok(Map.of(
"status", "success",
"message", "Bug report submitted successfully"
));
}
@PostMapping("/shake")
public ResponseEntity<Map<String, String>> triggerShakeReport() {
bugReportManager.simulateShake();
return ResponseEntity.ok(Map.of(
"status", "success",
"message", "Shake detection triggered"
));
}
@GetMapping("/status")
public ResponseEntity<Map<String, Boolean>> getStatus() {
return ResponseEntity.ok(Map.of("enabled", true));
}
public static class ManualReportRequest {
private String description;
private String userEmail;
// Getters and setters
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getUserEmail() { return userEmail; }
public void setUserEmail(String userEmail) { this.userEmail = userEmail; }
}
}
9. Desktop Application Integration
Swing Application Example:
package com.example.shakebug.ui;
import com.example.shakebug.BugReportManager;
import com.example.shakebug.model.BugReport;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class ShakeBugDialog extends JDialog {
private final BugReportManager bugReportManager;
private JTextArea descriptionArea;
private JTextField emailField;
public ShakeBugDialog(Frame parent, BugReportManager bugReportManager) {
super(parent, "Report a Bug", true);
this.bugReportManager = bugReportManager;
initializeUI();
setupListeners();
}
private void initializeUI() {
setLayout(new BorderLayout(10, 10));
setSize(500, 400);
setLocationRelativeTo(getParent());
// Description
JLabel descLabel = new JLabel("Describe the issue:");
descriptionArea = new JTextArea(8, 40);
descriptionArea.setLineWrap(true);
descriptionArea.setWrapStyleWord(true);
JScrollPane scrollPane = new JScrollPane(descriptionArea);
// Email
JLabel emailLabel = new JLabel("Your email (optional):");
emailField = new JTextField(30);
// Buttons
JButton submitButton = new JButton("Submit Bug Report");
JButton cancelButton = new JButton("Cancel");
// Layout
JPanel formPanel = new JPanel(new GridLayout(3, 1, 10, 10));
formPanel.add(descLabel);
formPanel.add(scrollPane);
formPanel.add(emailLabel);
formPanel.add(emailField);
JPanel buttonPanel = new JPanel(new FlowLayout());
buttonPanel.add(submitButton);
buttonPanel.add(cancelButton);
add(formPanel, BorderLayout.CENTER);
add(buttonPanel, BorderLayout.SOUTH);
// Setup listeners
submitButton.addActionListener(this::onSubmit);
cancelButton.addActionListener(e -> dispose());
}
private void setupListeners() {
bugReportManager.addBugReportListener(new BugReportManager.BugReportListener() {
@Override
public void onBugReportStarted() {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(ShakeBugDialog.this,
"Collecting bug report data...", "Bug Report",
JOptionPane.INFORMATION_MESSAGE);
});
}
@Override
public void onBugReportCompleted(BugReport report) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(ShakeBugDialog.this,
"Bug report submitted successfully! ID: " + report.getId(),
"Bug Report", JOptionPane.INFORMATION_MESSAGE);
dispose();
});
}
@Override
public void onBugReportFailed(Exception error) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(ShakeBugDialog.this,
"Failed to submit bug report: " + error.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
});
}
});
}
private void onSubmit(ActionEvent event) {
String description = descriptionArea.getText().trim();
String email = emailField.getText().trim();
if (description.isEmpty()) {
JOptionPane.showMessageDialog(this,
"Please describe the issue", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
bugReportManager.triggerManualBugReport(description, email);
}
public static void showDialog(Frame parent, BugReportManager bugReportManager) {
ShakeBugDialog dialog = new ShakeBugDialog(parent, bugReportManager);
dialog.setVisible(true);
}
}
10. Configuration Properties
application.properties:
# Application Info app.name=My Java Application app.version=2.1.0 # ShakeBug Configuration shakebug.enabled=true shakebug.screenshot.dir=temp/screenshots shakebug.log.path=logs/application.log # API Configuration shakebug.api.url=https://api.yourbugtracker.com/reports shakebug.api.key=your-api-key-here # Shake Sensitivity shakebug.sensitivity=800 shakebug.min-interval=1000
Conclusion
This comprehensive Shake for Bug Reports implementation provides:
- Motion Detection: Robust shake detection algorithms
- Automatic Context Collection: Device info, logs, system state
- Screenshot Capture: Automatic screen capture on shake
- Flexible Reporting: Multiple ways to trigger reports
- Extensible Architecture: Easy to customize and extend
- Cross-Platform: Works on desktop and mobile (with adaptations)
Key benefits:
- User-Friendly: Simple shake gesture for bug reporting
- Comprehensive: Rich context automatically captured
- Non-Intrusive: Works in the background
- Customizable: Adaptable to different applications and requirements
- Production-Ready: Error handling, async operations, and proper resource management
This implementation can significantly improve bug reporting efficiency and provide developers with the detailed information needed to quickly identify and fix issues.
Advanced Java Supply Chain Security, Kubernetes Hardening & Runtime Threat Detection
Sigstore Rekor in Java – https://macronepal.com/blog/sigstore-rekor-in-java/
Explains integrating Sigstore Rekor into Java systems to create a transparent, tamper-proof log of software signatures and metadata for verifying supply chain integrity.
Securing Java Applications with Chainguard Wolfi – https://macronepal.com/blog/securing-java-applications-with-chainguard-wolfi-a-comprehensive-guide/
Explains using Chainguard Wolfi minimal container images to reduce vulnerabilities and secure Java applications with hardened, lightweight runtime environments.
Cosign Image Signing in Java Complete Guide – https://macronepal.com/blog/cosign-image-signing-in-java-complete-guide/
Explains how to digitally sign container images using Cosign in Java-based workflows to ensure authenticity and prevent unauthorized modifications.
Secure Supply Chain Enforcement Kyverno Image Verification for Java Containers – https://macronepal.com/blog/secure-supply-chain-enforcement-kyverno-image-verification-for-java-containers/
Explains enforcing Kubernetes policies with Kyverno to verify container image signatures and ensure only trusted Java container images are deployed.
Pod Security Admission in Java Securing Kubernetes Deployments for JVM Applications – https://macronepal.com/blog/pod-security-admission-in-java-securing-kubernetes-deployments-for-jvm-applications/
Explains Kubernetes Pod Security Admission policies that enforce security rules like restricted privileges and safe configurations for Java workloads.
Securing Java Applications at Runtime Kubernetes Security Context – https://macronepal.com/blog/securing-java-applications-at-runtime-a-guide-to-kubernetes-security-context/
Explains how Kubernetes security contexts control runtime permissions, user IDs, and access rights for Java containers to improve isolation.
Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring-2/
Explains detecting abnormal runtime behavior in Java applications to identify potential security threats using process monitoring techniques.
Achieving Security Excellence CIS Benchmark Compliance for Java Applications – https://macronepal.com/blog/achieving-security-excellence-implementing-cis-benchmark-compliance-for-java-applications/
Explains applying CIS security benchmarks to Java environments to standardize hardening and improve overall system security posture.
Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring/
Explains behavioral monitoring of Java processes to detect anomalies and improve runtime security through continuous observation and analysis.
JAVA CODE COMPILER