diff --git a/app.py b/app.py index b36392f..a8abe60 100644 --- a/app.py +++ b/app.py @@ -138,6 +138,8 @@ def create_audit_triggers(): print(f"āŒ Error creating audit triggers: {e}") db.session.rollback() +from location_logging import * + # Initialize Google Maps client try: GOOGLE_MAPS_API_KEY = os.environ.get('GOOGLE_MAPS_API_KEY') @@ -6044,6 +6046,7 @@ def employee_detail(employee_index): flash('Error loading employee details. Please try again.', 'error') return redirect(url_for('employees')) +create_location_logging_routes(app, db, logger_handler) # Jinja2 filters for better template functionality @app.template_filter('days_since') diff --git a/location_logging.py b/location_logging.py new file mode 100644 index 0000000..a966008 --- /dev/null +++ b/location_logging.py @@ -0,0 +1,293 @@ +# File: location_logging.py +# Enhanced location action logging for Android debugging + +from flask import request, jsonify +from datetime import datetime +import json +import traceback + +def create_location_logging_routes(app, db, logger_handler): + """ + Create location logging routes for monitoring Android location issues + This should be included in your main app.py file + """ + + @app.route('/api/log-location-action', methods=['POST']) + def log_location_action(): + """ + Log location actions for debugging Android location issues + """ + try: + data = request.get_json() + + if not data: + return jsonify({'status': 'error', 'message': 'No data provided'}), 400 + + action = data.get('action', 'unknown') + action_data = data.get('data', {}) + timestamp = data.get('timestamp', datetime.now().isoformat()) + user_agent = data.get('userAgent', request.headers.get('User-Agent', '')) + + # Extract device information + device_info = extract_device_info(user_agent) + + # Create log entry + log_entry = { + 'action': action, + 'timestamp': timestamp, + 'device_info': device_info, + 'action_data': action_data, + 'ip_address': get_client_ip_enhanced(), + 'user_agent': user_agent[:500] # Limit length + } + + # Log to console for immediate debugging + print(f"šŸ“Š LOCATION ACTION LOG: {action}") + print(f" Device: {device_info.get('platform')} {device_info.get('browser')}") + print(f" Data: {json.dumps(action_data, indent=2)}") + + # Use existing logger if available + if logger_handler: + logger_handler.log_user_activity( + f'location_action_{action}', + f"Location action: {action} | Device: {device_info.get('platform')} | Data: {json.dumps(action_data)}" + ) + + # Store in database for analysis (optional) + try: + store_location_log_in_db(db, log_entry) + except Exception as db_error: + print(f"āš ļø Could not store location log in database: {db_error}") + + return jsonify({'status': 'success', 'message': 'Location action logged'}) + + except Exception as e: + print(f"āŒ Error logging location action: {e}") + print(f"āŒ Traceback: {traceback.format_exc()}") + return jsonify({'status': 'error', 'message': 'Logging failed'}), 500 + + @app.route('/api/location-debug-info', methods=['GET']) + def get_location_debug_info(): + """ + Get debugging information about location services + """ + try: + user_agent = request.headers.get('User-Agent', '') + device_info = extract_device_info(user_agent) + + debug_info = { + 'timestamp': datetime.now().isoformat(), + 'ip_address': get_client_ip_enhanced(), + 'device_info': device_info, + 'headers': dict(request.headers), + 'is_android': 'android' in user_agent.lower(), + 'is_chrome': 'chrome' in user_agent.lower() and 'edg' not in user_agent.lower(), + 'is_secure': request.is_secure, + 'protocol': request.scheme + } + + return jsonify(debug_info) + + except Exception as e: + print(f"āŒ Error getting debug info: {e}") + return jsonify({'error': str(e)}), 500 + +def extract_device_info(user_agent_string): + """ + Extract detailed device information from user agent + """ + try: + # Use existing user agent parsing if available + if 'user_agents' in globals(): + from user_agents import parse + user_agent = parse(user_agent_string) + + return { + 'platform': user_agent.os.family, + 'platform_version': user_agent.os.version_string, + 'browser': user_agent.browser.family, + 'browser_version': user_agent.browser.version_string, + 'device': user_agent.device.family, + 'is_mobile': user_agent.is_mobile, + 'is_tablet': user_agent.is_tablet, + 'is_pc': user_agent.is_pc, + 'is_android': 'android' in user_agent_string.lower(), + 'is_chrome': 'chrome' in user_agent_string.lower() and 'edg' not in user_agent_string.lower() + } + else: + # Fallback manual parsing + ua_lower = user_agent_string.lower() + + return { + 'platform': 'Android' if 'android' in ua_lower else 'Unknown', + 'browser': 'Chrome' if 'chrome' in ua_lower else 'Unknown', + 'is_android': 'android' in ua_lower, + 'is_chrome': 'chrome' in ua_lower and 'edg' not in ua_lower, + 'user_agent_raw': user_agent_string[:200] + } + + except Exception as e: + print(f"āš ļø Error parsing user agent: {e}") + return { + 'error': str(e), + 'user_agent_raw': user_agent_string[:200] + } + +def get_client_ip_enhanced(): + """ + Enhanced client IP detection + """ + # Check various headers for real IP + for header in ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED']: + if header in request.environ: + ip = request.environ[header].split(',')[0].strip() + if ip: + return ip + + return request.environ.get('REMOTE_ADDR', 'unknown') + +def store_location_log_in_db(db, log_entry): + """ + Store location log in database for analysis (optional) + Create table if it doesn't exist + """ + try: + # Create table if it doesn't exist + db.session.execute(text(""" + CREATE TABLE IF NOT EXISTS location_action_logs ( + id INT AUTO_INCREMENT PRIMARY KEY, + action VARCHAR(100), + timestamp TIMESTAMP, + device_info JSON, + action_data JSON, + ip_address VARCHAR(45), + user_agent TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """)) + + # Insert log entry + db.session.execute(text(""" + INSERT INTO location_action_logs ( + action, timestamp, device_info, action_data, ip_address, user_agent + ) VALUES ( + :action, :timestamp, :device_info, :action_data, :ip_address, :user_agent + ) + """), { + 'action': log_entry['action'], + 'timestamp': log_entry['timestamp'], + 'device_info': json.dumps(log_entry['device_info']), + 'action_data': json.dumps(log_entry['action_data']), + 'ip_address': log_entry['ip_address'], + 'user_agent': log_entry['user_agent'] + }) + + db.session.commit() + + except Exception as e: + print(f"āš ļø Database logging error: {e}") + db.session.rollback() + +# Enhanced location processing for form submission +def process_location_data_enhanced(form_data): + """ + Enhanced location data processing with better Android handling + This replaces or enhances the existing process_location_data function + """ + print(f"\nšŸ“± ENHANCED LOCATION PROCESSING:") + print(f" Raw location data received: {dict(form_data)}") + + processed = { + 'latitude': None, + 'longitude': None, + 'accuracy': None, + 'altitude': None, + 'source': 'manual', + 'address': None + } + + try: + # Process coordinates with enhanced validation + if form_data.get('latitude') and form_data.get('longitude'): + lat_str = str(form_data['latitude']).strip() + lng_str = str(form_data['longitude']).strip() + + # Handle various input formats + if lat_str not in ['null', '', 'undefined', 'NaN'] and lng_str not in ['null', '', 'undefined', 'NaN']: + try: + lat_val = float(lat_str) + lng_val = float(lng_str) + + # Validate coordinate ranges + if -90 <= lat_val <= 90 and -180 <= lng_val <= 180: + processed['latitude'] = lat_val + processed['longitude'] = lng_val + processed['source'] = form_data.get('location_source', 'gps') + print(f"āœ… Valid coordinates processed: {lat_val:.10f}, {lng_val:.10f}") + else: + print(f"āš ļø Coordinates out of valid range: {lat_val}, {lng_val}") + except (ValueError, TypeError) as e: + print(f"āš ļø Could not convert coordinates to float: {e}") + + # Process accuracy + if form_data.get('accuracy'): + try: + acc_str = str(form_data['accuracy']).strip() + if acc_str not in ['null', '', 'undefined', 'NaN']: + acc_val = float(acc_str) + if acc_val > 0: # Accuracy should be positive + processed['accuracy'] = acc_val + print(f"āœ… Accuracy processed: {acc_val}m") + except (ValueError, TypeError): + print(f"āš ļø Could not process accuracy value") + + # Process altitude + if form_data.get('altitude'): + try: + alt_str = str(form_data['altitude']).strip() + if alt_str not in ['null', '', 'undefined', 'NaN']: + processed['altitude'] = float(alt_str) + except (ValueError, TypeError): + print(f"āš ļø Could not process altitude value") + + # Process address with coordinate detection + if form_data.get('address'): + address = str(form_data['address']).strip() + if address and address not in ['null', '', 'undefined']: + # Check if address is actually coordinates + if re.match(r'^-?\d+\.\d+,?\s*-?\d+\.\d+$', address.replace(' ', '')): + print(f"šŸ” Address appears to be coordinates: {address}") + processed['address'] = None # Will trigger reverse geocoding + else: + processed['address'] = address[:500] + print(f"āœ… Address processed: {address[:50]}...") + + # Enhanced reverse geocoding trigger + if (processed['latitude'] is not None and processed['longitude'] is not None + and not processed['address']): + print(f"šŸŒ Triggering enhanced reverse geocoding...") + try: + # Use existing reverse geocoding function + reverse_geocoded = reverse_geocode_coordinates(processed['latitude'], processed['longitude']) + if reverse_geocoded: + processed['address'] = reverse_geocoded[:500] + print(f"āœ… Reverse geocoding successful: {reverse_geocoded[:50]}...") + else: + processed['address'] = f"{processed['latitude']:.6f}, {processed['longitude']:.6f}" + print(f"āš ļø Reverse geocoding failed, using coordinates") + except Exception as geocoding_error: + print(f"āŒ Reverse geocoding error: {geocoding_error}") + processed['address'] = f"{processed['latitude']:.6f}, {processed['longitude']:.6f}" + + print(f"šŸ“ FINAL PROCESSED LOCATION:") + print(f" Coordinates: {processed['latitude']}, {processed['longitude']}") + print(f" Accuracy: {processed['accuracy']}m") + print(f" Source: {processed['source']}") + print(f" Address: {processed['address']}") + + return processed + + except Exception as e: + print(f"āŒ Error in enhanced location processing: {e}") + print(f"āŒ Traceback: {traceback.format_exc()}") + return processed \ No newline at end of file diff --git a/static/js/android_location_handler.js b/static/js/android_location_handler.js new file mode 100644 index 0000000..a223558 --- /dev/null +++ b/static/js/android_location_handler.js @@ -0,0 +1,365 @@ +/** + * Enhanced Android Location Fix - Separate Module + * File: static/js/android_location_handler.js + * + * This module addresses Android-specific geolocation issues: + * 1. Android Chrome timeout handling + * 2. Progressive fallback strategy + * 3. Enhanced permission detection + * 4. Network location fallback + * 5. Multiple retry attempts with different configurations + */ + +// Android-specific geolocation configuration +const ANDROID_LOCATION_CONFIG = { + // Primary attempt - High accuracy with reasonable timeout + highAccuracy: { + enableHighAccuracy: true, + timeout: 15000, // Increased from 10000 for Android + maximumAge: 60000 // Reduced cache time for fresh location + }, + + // Fallback attempt - Network-based location + networkBased: { + enableHighAccuracy: false, + timeout: 20000, // Longer timeout for network-based + maximumAge: 300000 + }, + + // Final attempt - Any available location + anyLocation: { + enableHighAccuracy: false, + timeout: 30000, // Maximum patience for Android + maximumAge: 600000 + } +}; + +// Enhanced location request with Android-specific handling +function requestAndroidEnhancedLocation() { + console.log("šŸ“± Starting Android-enhanced location request..."); + + if (typeof locationRequestActive !== 'undefined' && locationRequestActive) { + console.log("šŸ“ Location request already active, skipping Android enhancement"); + return; + } + + if (!navigator.geolocation) { + console.log("āŒ Geolocation not supported"); + if (typeof userLocation !== 'undefined') { + userLocation.source = "manual"; + } + if (typeof currentUserLocation !== 'undefined') { + currentUserLocation.source = "manual"; + } + return; + } + + // Set active flag + if (typeof locationRequestActive !== 'undefined') { + locationRequestActive = true; + } + if (typeof locationCaptureActive !== 'undefined') { + locationCaptureActive = true; + } + + console.log("šŸ“± Attempting Android-optimized location sequence..."); + + // Start progressive location attempts + attemptAndroidLocationSequence(); +} + +function attemptAndroidLocationSequence() { + console.log("šŸ”„ Android Location Sequence - Attempt 1: High Accuracy GPS"); + + // Attempt 1: High accuracy with Android-optimized timeout + navigator.geolocation.getCurrentPosition( + (position) => { + console.log("āœ… Android high-accuracy location success!"); + handleAndroidLocationSuccess(position, "gps_high_accuracy"); + }, + (error) => { + console.log(`āŒ High accuracy failed (${error.message}), trying network-based...`); + attemptNetworkBasedLocation(); + }, + ANDROID_LOCATION_CONFIG.highAccuracy + ); +} + +function attemptNetworkBasedLocation() { + console.log("šŸ”„ Android Location Sequence - Attempt 2: Network-based"); + + navigator.geolocation.getCurrentPosition( + (position) => { + console.log("āœ… Android network-based location success!"); + handleAndroidLocationSuccess(position, "network"); + }, + (error) => { + console.log(`āŒ Network-based failed (${error.message}), trying any location...`); + attemptAnyAvailableLocation(); + }, + ANDROID_LOCATION_CONFIG.networkBased + ); +} + +function attemptAnyAvailableLocation() { + console.log("šŸ”„ Android Location Sequence - Attempt 3: Any available"); + + navigator.geolocation.getCurrentPosition( + (position) => { + console.log("āœ… Android any-location success!"); + handleAndroidLocationSuccess(position, "any"); + }, + (error) => { + console.log(`āŒ All location attempts failed (${error.message}), trying watchPosition...`); + attemptWatchPosition(); + }, + ANDROID_LOCATION_CONFIG.anyLocation + ); +} + +function attemptWatchPosition() { + console.log("šŸ”„ Android Location Sequence - Attempt 4: Watch Position (single shot)"); + + let watchId = null; + let watchTimeout = null; + + // Set timeout for watch attempt + watchTimeout = setTimeout(() => { + if (watchId !== null) { + navigator.geolocation.clearWatch(watchId); + } + console.log("āŒ Watch position timed out, location failed"); + handleAndroidLocationError("All location methods failed"); + }, 25000); + + // Use watchPosition for more persistent location tracking + watchId = navigator.geolocation.watchPosition( + (position) => { + console.log("āœ… Android watch position success!"); + + // Clear watch and timeout + navigator.geolocation.clearWatch(watchId); + clearTimeout(watchTimeout); + + handleAndroidLocationSuccess(position, "watch"); + }, + (error) => { + console.log(`āŒ Watch position error: ${error.message}`); + // Don't clear watch immediately, let timeout handle it + }, + { + enableHighAccuracy: false, + timeout: 20000, + maximumAge: 0 // Force fresh location + } + ); +} + +function handleAndroidLocationSuccess(position, source) { + console.log(`āœ… Android location obtained successfully via ${source}`); + console.log(`šŸ“ Coordinates: ${position.coords.latitude}, ${position.coords.longitude}`); + console.log(`šŸ“Š Accuracy: ${position.coords.accuracy}m`); + + // Create location object + const locationData = { + latitude: position.coords.latitude, + longitude: position.coords.longitude, + accuracy: position.coords.accuracy, + altitude: position.coords.altitude, + timestamp: new Date(), + source: source, + address: null, + }; + + // Update global location variables based on what's available + if (typeof userLocation !== 'undefined') { + Object.assign(userLocation, locationData); + console.log("šŸ“ Updated userLocation with Android data"); + + // Trigger reverse geocoding if function exists + if (typeof reverseGeocode === 'function') { + reverseGeocode(userLocation.latitude, userLocation.longitude); + } + } + + if (typeof currentUserLocation !== 'undefined') { + Object.assign(currentUserLocation, locationData); + console.log("šŸ“ Updated currentUserLocation with Android data"); + + // Trigger enhanced reverse geocoding if function exists + if (typeof reverseGeocodeEnhanced === 'function') { + reverseGeocodeEnhanced(currentUserLocation.latitude, currentUserLocation.longitude); + } + } + + // Clear active flags + if (typeof locationRequestActive !== 'undefined') { + locationRequestActive = false; + } + if (typeof locationCaptureActive !== 'undefined') { + locationCaptureActive = false; + } + + // Log success for monitoring + logLocationAction('android_location_success', { + source: source, + accuracy: position.coords.accuracy, + coordinates: `${position.coords.latitude},${position.coords.longitude}` + }); +} + +function handleAndroidLocationError(errorMessage) { + console.log(`āŒ Android location error: ${errorMessage}`); + + // Update global location variables + if (typeof userLocation !== 'undefined') { + userLocation.source = "manual"; + } + if (typeof currentUserLocation !== 'undefined') { + currentUserLocation.source = "manual"; + } + + // Clear active flags + if (typeof locationRequestActive !== 'undefined') { + locationRequestActive = false; + } + if (typeof locationCaptureActive !== 'undefined') { + locationCaptureActive = false; + } + + // Log error for monitoring + logLocationAction('android_location_error', { + error: errorMessage, + userAgent: navigator.userAgent + }); +} + +// Enhanced permission checking for Android +function checkAndroidLocationPermissions() { + console.log("šŸ“± Checking Android location permissions..."); + + // Check if permissions API is available (newer Android browsers) + if ('permissions' in navigator) { + navigator.permissions.query({name: 'geolocation'}).then(function(result) { + console.log(`šŸ“± Geolocation permission: ${result.state}`); + + if (result.state === 'granted') { + console.log("āœ… Android location permission granted"); + requestAndroidEnhancedLocation(); + } else if (result.state === 'prompt') { + console.log("āš ļø Android location permission will be prompted"); + requestAndroidEnhancedLocation(); + } else { + console.log("āŒ Android location permission denied"); + handleAndroidLocationError("Permission denied"); + } + }).catch(function(error) { + console.log("āš ļø Could not check permissions, proceeding with location request"); + requestAndroidEnhancedLocation(); + }); + } else { + // Fallback for older Android browsers + console.log("šŸ“± Permissions API not available, proceeding with location request"); + requestAndroidEnhancedLocation(); + } +} + +// Detect if device is Android +function isAndroidDevice() { + const userAgent = navigator.userAgent.toLowerCase(); + return userAgent.includes('android'); +} + +// Detect if browser is Chrome on Android +function isAndroidChrome() { + const userAgent = navigator.userAgent.toLowerCase(); + return userAgent.includes('android') && userAgent.includes('chrome') && !userAgent.includes('edg'); +} + +// Enhanced location initialization for Android +function initializeAndroidLocation() { + console.log("šŸ“± Initializing Android-enhanced location services..."); + + if (isAndroidDevice()) { + console.log("šŸ“± Android device detected, using enhanced location handling"); + + // Use Android-specific permission checking + checkAndroidLocationPermissions(); + } else { + console.log("šŸ“± Non-Android device, using standard location request"); + + // Fall back to standard location request + if (typeof requestLocationData === 'function') { + requestLocationData(); + } else if (typeof requestEnhancedLocation === 'function') { + requestEnhancedLocation(); + } + } +} + +// Location action logging function +function logLocationAction(action, data) { + try { + console.log(`šŸ“Š Location Action Log: ${action}`, data); + + // Send to server for monitoring if endpoint exists + if (typeof fetch !== 'undefined') { + fetch('/api/log-location-action', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + action: action, + data: data, + timestamp: new Date().toISOString(), + userAgent: navigator.userAgent + }) + }).catch(error => { + console.log('šŸ“Š Could not send location log to server:', error); + }); + } + } catch (error) { + console.log('šŸ“Š Location logging error:', error); + } +} + +// Override standard location initialization if this is an Android device +document.addEventListener('DOMContentLoaded', function() { + // Small delay to ensure other scripts are loaded + setTimeout(() => { + if (isAndroidDevice()) { + console.log("šŸ“± Android detected - overriding standard location initialization"); + + // Replace standard initialization with Android-enhanced version + if (typeof initializeLocation === 'function') { + const originalInitializeLocation = initializeLocation; + window.initializeLocation = function() { + console.log("šŸ“± Using Android-enhanced location initialization"); + initializeAndroidLocation(); + }; + } + + // Also handle the enhanced location capture + if (typeof requestEnhancedLocation === 'function') { + const originalRequestEnhanced = requestEnhancedLocation; + window.requestEnhancedLocation = function() { + console.log("šŸ“± Using Android-enhanced location request"); + initializeAndroidLocation(); + }; + } + } + }, 100); +}); + +// Export functions for external use +if (typeof window !== 'undefined') { + window.AndroidLocationHandler = { + requestAndroidEnhancedLocation, + checkAndroidLocationPermissions, + isAndroidDevice, + isAndroidChrome, + initializeAndroidLocation, + logLocationAction + }; +} \ No newline at end of file diff --git a/static/js/qr_destination.js b/static/js/qr_destination.js index 36c7019..ff78907 100644 --- a/static/js/qr_destination.js +++ b/static/js/qr_destination.js @@ -511,7 +511,15 @@ function validateEmployeeId() { // All location and language functions remain unchanged from original function initializeLocation() { console.log("šŸ“ Initializing location services"); - requestLocationData(); + + // Check if Android enhanced location handler is available + if (typeof AndroidLocationHandler !== 'undefined' && AndroidLocationHandler.isAndroidDevice()) { + console.log("šŸ“± Using Android-enhanced location initialization"); + AndroidLocationHandler.initializeAndroidLocation(); + } else { + console.log("šŸ“ Using standard location initialization"); + requestLocationData(); + } } function requestLocationData() { diff --git a/templates/qr_destination.html b/templates/qr_destination.html index 9318bf4..93fbcbc 100644 --- a/templates/qr_destination.html +++ b/templates/qr_destination.html @@ -838,6 +838,7 @@ +