diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/LoggingManager.java b/android/plugin/src/main/java/com/timesafari/dailynotification/LoggingManager.java new file mode 100644 index 0000000..33cfd4f --- /dev/null +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/LoggingManager.java @@ -0,0 +1,394 @@ +/** + * LoggingManager.java + * + * Optimized logging management with privacy controls and level management + * Implements structured logging, privacy protection, and performance optimization + * + * @author Matthew Raymer + * @version 2.0.0 - Optimized Architecture + */ + +package com.timesafari.dailynotification; + +import android.content.Context; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +/** + * Optimized logging manager with privacy controls + * + * Features: + * - Structured logging with levels + * - Privacy protection for sensitive data + * - Performance optimization + * - Configurable log levels + * - Log filtering and sanitization + */ +public class LoggingManager { + + private static final String TAG = "LoggingManager"; + + // Log levels + public static final int VERBOSE = Log.VERBOSE; + public static final int DEBUG = Log.DEBUG; + public static final int INFO = Log.INFO; + public static final int WARN = Log.WARN; + public static final int ERROR = Log.ERROR; + + // Privacy patterns for sensitive data + private static final Pattern EMAIL_PATTERN = Pattern.compile("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b"); + private static final Pattern PHONE_PATTERN = Pattern.compile("\\b\\d{3}-\\d{3}-\\d{4}\\b"); + private static final Pattern SSN_PATTERN = Pattern.compile("\\b\\d{3}-\\d{2}-\\d{4}\\b"); + private static final Pattern CREDIT_CARD_PATTERN = Pattern.compile("\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b"); + + // Configuration + private static int currentLogLevel = INFO; + private static boolean privacyEnabled = true; + private static boolean performanceLogging = false; + + // Performance tracking + private static final Map performanceStartTimes = new ConcurrentHashMap<>(); + private static final Map logCounts = new ConcurrentHashMap<>(); + + // Context + private final Context context; + + /** + * Initialize logging manager + * + * @param context Application context + */ + public LoggingManager(Context context) { + this.context = context; + + Log.d(TAG, "LoggingManager initialized with level: " + getLevelName(currentLogLevel)); + } + + /** + * Set the current log level + * + * @param level Log level (VERBOSE, DEBUG, INFO, WARN, ERROR) + */ + public static void setLogLevel(int level) { + currentLogLevel = level; + Log.i(TAG, "Log level set to: " + getLevelName(level)); + } + + /** + * Get the current log level + * + * @return Current log level + */ + public static int getLogLevel() { + return currentLogLevel; + } + + /** + * Enable or disable privacy protection + * + * @param enabled true to enable privacy protection + */ + public static void setPrivacyEnabled(boolean enabled) { + privacyEnabled = enabled; + Log.i(TAG, "Privacy protection " + (enabled ? "enabled" : "disabled")); + } + + /** + * Enable or disable performance logging + * + * @param enabled true to enable performance logging + */ + public static void setPerformanceLogging(boolean enabled) { + performanceLogging = enabled; + Log.i(TAG, "Performance logging " + (enabled ? "enabled" : "disabled")); + } + + /** + * Log verbose message with privacy protection + * + * @param tag Log tag + * @param message Message to log + */ + public static void v(String tag, String message) { + if (shouldLog(VERBOSE)) { + String sanitizedMessage = sanitizeMessage(message); + Log.v(tag, sanitizedMessage); + incrementLogCount(tag, VERBOSE); + } + } + + /** + * Log debug message with privacy protection + * + * @param tag Log tag + * @param message Message to log + */ + public static void d(String tag, String message) { + if (shouldLog(DEBUG)) { + String sanitizedMessage = sanitizeMessage(message); + Log.d(tag, sanitizedMessage); + incrementLogCount(tag, DEBUG); + } + } + + /** + * Log info message with privacy protection + * + * @param tag Log tag + * @param message Message to log + */ + public static void i(String tag, String message) { + if (shouldLog(INFO)) { + String sanitizedMessage = sanitizeMessage(message); + Log.i(tag, sanitizedMessage); + incrementLogCount(tag, INFO); + } + } + + /** + * Log warning message with privacy protection + * + * @param tag Log tag + * @param message Message to log + */ + public static void w(String tag, String message) { + if (shouldLog(WARN)) { + String sanitizedMessage = sanitizeMessage(message); + Log.w(tag, sanitizedMessage); + incrementLogCount(tag, WARN); + } + } + + /** + * Log error message with privacy protection + * + * @param tag Log tag + * @param message Message to log + */ + public static void e(String tag, String message) { + if (shouldLog(ERROR)) { + String sanitizedMessage = sanitizeMessage(message); + Log.e(tag, sanitizedMessage); + incrementLogCount(tag, ERROR); + } + } + + /** + * Log error message with exception + * + * @param tag Log tag + * @param message Message to log + * @param throwable Exception to log + */ + public static void e(String tag, String message, Throwable throwable) { + if (shouldLog(ERROR)) { + String sanitizedMessage = sanitizeMessage(message); + Log.e(tag, sanitizedMessage, throwable); + incrementLogCount(tag, ERROR); + } + } + + /** + * Start performance timing + * + * @param operation Operation name + */ + public static void startTiming(String operation) { + if (performanceLogging) { + performanceStartTimes.put(operation, System.currentTimeMillis()); + d(TAG, "Started timing: " + operation); + } + } + + /** + * End performance timing + * + * @param operation Operation name + */ + public static void endTiming(String operation) { + if (performanceLogging) { + Long startTime = performanceStartTimes.remove(operation); + if (startTime != null) { + long duration = System.currentTimeMillis() - startTime; + i(TAG, "Timing completed: " + operation + " took " + duration + "ms"); + } + } + } + + /** + * Log structured data + * + * @param tag Log tag + * @param level Log level + * @param data Structured data to log + */ + public static void logStructured(String tag, int level, Map data) { + if (shouldLog(level)) { + StringBuilder message = new StringBuilder(); + message.append("Structured data: "); + + for (Map.Entry entry : data.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + // Sanitize sensitive keys + if (isSensitiveKey(key)) { + message.append(key).append("=[REDACTED] "); + } else { + String sanitizedValue = sanitizeMessage(value.toString()); + message.append(key).append("=").append(sanitizedValue).append(" "); + } + } + + logMessage(tag, level, message.toString()); + } + } + + /** + * Check if should log at given level + * + * @param level Log level + * @return true if should log + */ + private static boolean shouldLog(int level) { + return level >= currentLogLevel; + } + + /** + * Log message at given level + * + * @param tag Log tag + * @param level Log level + * @param message Message to log + */ + private static void logMessage(String tag, int level, String message) { + switch (level) { + case VERBOSE: + Log.v(tag, message); + break; + case DEBUG: + Log.d(tag, message); + break; + case INFO: + Log.i(tag, message); + break; + case WARN: + Log.w(tag, message); + break; + case ERROR: + Log.e(tag, message); + break; + } + } + + /** + * Sanitize message for privacy protection + * + * @param message Original message + * @return Sanitized message + */ + private static String sanitizeMessage(String message) { + if (!privacyEnabled || message == null) { + return message; + } + + String sanitized = message; + + // Replace email addresses + sanitized = EMAIL_PATTERN.matcher(sanitized).replaceAll("[EMAIL_REDACTED]"); + + // Replace phone numbers + sanitized = PHONE_PATTERN.matcher(sanitized).replaceAll("[PHONE_REDACTED]"); + + // Replace SSNs + sanitized = SSN_PATTERN.matcher(sanitized).replaceAll("[SSN_REDACTED]"); + + // Replace credit card numbers + sanitized = CREDIT_CARD_PATTERN.matcher(sanitized).replaceAll("[CARD_REDACTED]"); + + return sanitized; + } + + /** + * Check if key is sensitive + * + * @param key Key to check + * @return true if key is sensitive + */ + private static boolean isSensitiveKey(String key) { + if (key == null) { + return false; + } + + String lowerKey = key.toLowerCase(); + return lowerKey.contains("password") || + lowerKey.contains("token") || + lowerKey.contains("secret") || + lowerKey.contains("key") || + lowerKey.contains("auth") || + lowerKey.contains("credential"); + } + + /** + * Increment log count for statistics + * + * @param tag Log tag + * @param level Log level + */ + private static void incrementLogCount(String tag, int level) { + String key = tag + "_" + getLevelName(level); + logCounts.put(key, logCounts.getOrDefault(key, 0) + 1); + } + + /** + * Get level name + * + * @param level Log level + * @return Level name + */ + private static String getLevelName(int level) { + switch (level) { + case VERBOSE: + return "VERBOSE"; + case DEBUG: + return "DEBUG"; + case INFO: + return "INFO"; + case WARN: + return "WARN"; + case ERROR: + return "ERROR"; + default: + return "UNKNOWN"; + } + } + + /** + * Get logging statistics + * + * @return Logging statistics + */ + public static Map getLoggingStats() { + Map stats = new HashMap<>(); + stats.put("currentLogLevel", getLevelName(currentLogLevel)); + stats.put("privacyEnabled", privacyEnabled); + stats.put("performanceLogging", performanceLogging); + stats.put("logCounts", new HashMap<>(logCounts)); + stats.put("activeTimings", performanceStartTimes.size()); + + return stats; + } + + /** + * Clear logging statistics + */ + public static void clearStats() { + logCounts.clear(); + performanceStartTimes.clear(); + Log.i(TAG, "Logging statistics cleared"); + } +} diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/PrivacyManager.java b/android/plugin/src/main/java/com/timesafari/dailynotification/PrivacyManager.java new file mode 100644 index 0000000..302aef0 --- /dev/null +++ b/android/plugin/src/main/java/com/timesafari/dailynotification/PrivacyManager.java @@ -0,0 +1,417 @@ +/** + * PrivacyManager.java + * + * Privacy configuration and data protection manager + * Implements GDPR compliance, data anonymization, and privacy controls + * + * @author Matthew Raymer + * @version 2.0.0 - Optimized Architecture + */ + +package com.timesafari.dailynotification; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Privacy manager for data protection and compliance + * + * Features: + * - GDPR compliance controls + * - Data anonymization + * - Privacy settings management + * - Sensitive data detection + * - Consent management + */ +public class PrivacyManager { + + private static final String TAG = "PrivacyManager"; + private static final String PREFS_NAME = "PrivacySettings"; + + // Privacy settings keys + private static final String KEY_PRIVACY_ENABLED = "privacy_enabled"; + private static final String KEY_DATA_COLLECTION = "data_collection"; + private static final String KEY_ANALYTICS_ENABLED = "analytics_enabled"; + private static final String KEY_CRASH_REPORTING = "crash_reporting"; + private static final String KEY_USER_CONSENT = "user_consent"; + private static final String KEY_DATA_RETENTION_DAYS = "data_retention_days"; + + // Default privacy settings + private static final boolean DEFAULT_PRIVACY_ENABLED = true; + private static final boolean DEFAULT_DATA_COLLECTION = false; + private static final boolean DEFAULT_ANALYTICS_ENABLED = false; + private static final boolean DEFAULT_CRASH_REPORTING = false; + private static final boolean DEFAULT_USER_CONSENT = false; + private static final int DEFAULT_DATA_RETENTION_DAYS = 30; + + // Privacy levels + public static final int PRIVACY_LEVEL_NONE = 0; + public static final int PRIVACY_LEVEL_BASIC = 1; + public static final int PRIVACY_LEVEL_ENHANCED = 2; + public static final int PRIVACY_LEVEL_MAXIMUM = 3; + + private final Context context; + private final SharedPreferences prefs; + + // Privacy configuration + private boolean privacyEnabled; + private boolean dataCollectionEnabled; + private boolean analyticsEnabled; + private boolean crashReportingEnabled; + private boolean userConsentGiven; + private int dataRetentionDays; + private int privacyLevel; + + // Sensitive data patterns + private final Map sensitiveDataPatterns = new ConcurrentHashMap<>(); + + /** + * Initialize privacy manager + * + * @param context Application context + */ + public PrivacyManager(Context context) { + this.context = context; + this.prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + + // Initialize privacy settings + loadPrivacySettings(); + + // Initialize sensitive data patterns + initializeSensitiveDataPatterns(); + + Log.d(TAG, "PrivacyManager initialized with level: " + privacyLevel); + } + + /** + * Load privacy settings from storage + */ + private void loadPrivacySettings() { + privacyEnabled = prefs.getBoolean(KEY_PRIVACY_ENABLED, DEFAULT_PRIVACY_ENABLED); + dataCollectionEnabled = prefs.getBoolean(KEY_DATA_COLLECTION, DEFAULT_DATA_COLLECTION); + analyticsEnabled = prefs.getBoolean(KEY_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_ENABLED); + crashReportingEnabled = prefs.getBoolean(KEY_CRASH_REPORTING, DEFAULT_CRASH_REPORTING); + userConsentGiven = prefs.getBoolean(KEY_USER_CONSENT, DEFAULT_USER_CONSENT); + dataRetentionDays = prefs.getInt(KEY_DATA_RETENTION_DAYS, DEFAULT_DATA_RETENTION_DAYS); + + // Calculate privacy level + calculatePrivacyLevel(); + } + + /** + * Calculate privacy level based on settings + */ + private void calculatePrivacyLevel() { + if (!privacyEnabled) { + privacyLevel = PRIVACY_LEVEL_NONE; + } else if (!dataCollectionEnabled && !analyticsEnabled && !crashReportingEnabled) { + privacyLevel = PRIVACY_LEVEL_MAXIMUM; + } else if (!dataCollectionEnabled && !analyticsEnabled) { + privacyLevel = PRIVACY_LEVEL_ENHANCED; + } else if (!dataCollectionEnabled) { + privacyLevel = PRIVACY_LEVEL_BASIC; + } else { + privacyLevel = PRIVACY_LEVEL_BASIC; + } + } + + /** + * Initialize sensitive data patterns + */ + private void initializeSensitiveDataPatterns() { + sensitiveDataPatterns.put("email", "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b"); + sensitiveDataPatterns.put("phone", "\\b\\d{3}-\\d{3}-\\d{4}\\b"); + sensitiveDataPatterns.put("ssn", "\\b\\d{3}-\\d{2}-\\d{4}\\b"); + sensitiveDataPatterns.put("credit_card", "\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b"); + sensitiveDataPatterns.put("ip_address", "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"); + sensitiveDataPatterns.put("mac_address", "\\b([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})\\b"); + } + + /** + * Set privacy enabled + * + * @param enabled true to enable privacy protection + */ + public void setPrivacyEnabled(boolean enabled) { + this.privacyEnabled = enabled; + prefs.edit().putBoolean(KEY_PRIVACY_ENABLED, enabled).apply(); + calculatePrivacyLevel(); + + Log.i(TAG, "Privacy protection " + (enabled ? "enabled" : "disabled")); + } + + /** + * Set data collection enabled + * + * @param enabled true to enable data collection + */ + public void setDataCollectionEnabled(boolean enabled) { + this.dataCollectionEnabled = enabled; + prefs.edit().putBoolean(KEY_DATA_COLLECTION, enabled).apply(); + calculatePrivacyLevel(); + + Log.i(TAG, "Data collection " + (enabled ? "enabled" : "disabled")); + } + + /** + * Set analytics enabled + * + * @param enabled true to enable analytics + */ + public void setAnalyticsEnabled(boolean enabled) { + this.analyticsEnabled = enabled; + prefs.edit().putBoolean(KEY_ANALYTICS_ENABLED, enabled).apply(); + calculatePrivacyLevel(); + + Log.i(TAG, "Analytics " + (enabled ? "enabled" : "disabled")); + } + + /** + * Set crash reporting enabled + * + * @param enabled true to enable crash reporting + */ + public void setCrashReportingEnabled(boolean enabled) { + this.crashReportingEnabled = enabled; + prefs.edit().putBoolean(KEY_CRASH_REPORTING, enabled).apply(); + calculatePrivacyLevel(); + + Log.i(TAG, "Crash reporting " + (enabled ? "enabled" : "disabled")); + } + + /** + * Set user consent + * + * @param consent true if user has given consent + */ + public void setUserConsent(boolean consent) { + this.userConsentGiven = consent; + prefs.edit().putBoolean(KEY_USER_CONSENT, consent).apply(); + + Log.i(TAG, "User consent " + (consent ? "given" : "revoked")); + } + + /** + * Set data retention period + * + * @param days Number of days to retain data + */ + public void setDataRetentionDays(int days) { + this.dataRetentionDays = days; + prefs.edit().putInt(KEY_DATA_RETENTION_DAYS, days).apply(); + + Log.i(TAG, "Data retention set to " + days + " days"); + } + + /** + * Check if privacy is enabled + * + * @return true if privacy is enabled + */ + public boolean isPrivacyEnabled() { + return privacyEnabled; + } + + /** + * Check if data collection is enabled + * + * @return true if data collection is enabled + */ + public boolean isDataCollectionEnabled() { + return dataCollectionEnabled && userConsentGiven; + } + + /** + * Check if analytics is enabled + * + * @return true if analytics is enabled + */ + public boolean isAnalyticsEnabled() { + return analyticsEnabled && userConsentGiven; + } + + /** + * Check if crash reporting is enabled + * + * @return true if crash reporting is enabled + */ + public boolean isCrashReportingEnabled() { + return crashReportingEnabled && userConsentGiven; + } + + /** + * Check if user has given consent + * + * @return true if user has given consent + */ + public boolean hasUserConsent() { + return userConsentGiven; + } + + /** + * Get data retention period + * + * @return Number of days to retain data + */ + public int getDataRetentionDays() { + return dataRetentionDays; + } + + /** + * Get privacy level + * + * @return Privacy level (0-3) + */ + public int getPrivacyLevel() { + return privacyLevel; + } + + /** + * Anonymize data based on privacy level + * + * @param data Data to anonymize + * @return Anonymized data + */ + public String anonymizeData(String data) { + if (!privacyEnabled || data == null) { + return data; + } + + String anonymized = data; + + switch (privacyLevel) { + case PRIVACY_LEVEL_MAXIMUM: + // Remove all potentially sensitive data + anonymized = removeAllSensitiveData(anonymized); + break; + case PRIVACY_LEVEL_ENHANCED: + // Remove most sensitive data + anonymized = removeSensitiveData(anonymized, new String[]{"email", "phone", "ssn", "credit_card"}); + break; + case PRIVACY_LEVEL_BASIC: + // Remove highly sensitive data + anonymized = removeSensitiveData(anonymized, new String[]{"ssn", "credit_card"}); + break; + case PRIVACY_LEVEL_NONE: + // No anonymization + break; + } + + return anonymized; + } + + /** + * Remove all sensitive data + * + * @param data Data to process + * @return Data with all sensitive information removed + */ + private String removeAllSensitiveData(String data) { + String result = data; + + for (String pattern : sensitiveDataPatterns.values()) { + result = result.replaceAll(pattern, "[REDACTED]"); + } + + return result; + } + + /** + * Remove specific sensitive data types + * + * @param data Data to process + * @param types Types of sensitive data to remove + * @return Data with specified sensitive information removed + */ + private String removeSensitiveData(String data, String[] types) { + String result = data; + + for (String type : types) { + String pattern = sensitiveDataPatterns.get(type); + if (pattern != null) { + result = result.replaceAll(pattern, "[REDACTED]"); + } + } + + return result; + } + + /** + * Check if data contains sensitive information + * + * @param data Data to check + * @return true if data contains sensitive information + */ + public boolean containsSensitiveData(String data) { + if (data == null) { + return false; + } + + for (String pattern : sensitiveDataPatterns.values()) { + if (data.matches(".*" + pattern + ".*")) { + return true; + } + } + + return false; + } + + /** + * Get privacy configuration summary + * + * @return Privacy configuration summary + */ + public Map getPrivacySummary() { + Map summary = new HashMap<>(); + summary.put("privacyEnabled", privacyEnabled); + summary.put("dataCollectionEnabled", dataCollectionEnabled); + summary.put("analyticsEnabled", analyticsEnabled); + summary.put("crashReportingEnabled", crashReportingEnabled); + summary.put("userConsentGiven", userConsentGiven); + summary.put("dataRetentionDays", dataRetentionDays); + summary.put("privacyLevel", privacyLevel); + summary.put("privacyLevelName", getPrivacyLevelName(privacyLevel)); + + return summary; + } + + /** + * Get privacy level name + * + * @param level Privacy level + * @return Privacy level name + */ + private String getPrivacyLevelName(int level) { + switch (level) { + case PRIVACY_LEVEL_NONE: + return "NONE"; + case PRIVACY_LEVEL_BASIC: + return "BASIC"; + case PRIVACY_LEVEL_ENHANCED: + return "ENHANCED"; + case PRIVACY_LEVEL_MAXIMUM: + return "MAXIMUM"; + default: + return "UNKNOWN"; + } + } + + /** + * Reset privacy settings to defaults + */ + public void resetToDefaults() { + setPrivacyEnabled(DEFAULT_PRIVACY_ENABLED); + setDataCollectionEnabled(DEFAULT_DATA_COLLECTION); + setAnalyticsEnabled(DEFAULT_ANALYTICS_ENABLED); + setCrashReportingEnabled(DEFAULT_CRASH_REPORTING); + setUserConsent(DEFAULT_USER_CONSENT); + setDataRetentionDays(DEFAULT_DATA_RETENTION_DAYS); + + Log.i(TAG, "Privacy settings reset to defaults"); + } +}