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 @@
+