Update location handler for Android

This commit is contained in:
Nguyen Ngo
2025-08-29 11:11:54 -04:00
parent 6a3c5430fc
commit e1d23faf44
5 changed files with 677 additions and 1 deletions
+3
View File
@@ -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')
+293
View File
@@ -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
+365
View File
@@ -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
};
}
+8
View File
@@ -511,8 +511,16 @@ function validateEmployeeId() {
// All location and language functions remain unchanged from original
function initializeLocation() {
console.log("📍 Initializing location services");
// 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() {
if (locationRequestActive) {
+7
View File
@@ -838,6 +838,7 @@
<!-- Enhanced JavaScript with preserved functionality -->
<script src="{{ url_for('static', filename='js/qr_destination.js') }}"></script>
<script src="{{ url_for('static', filename='js/android_location_handler.js') }}"></script>
<script>
// Use different variable name to avoid conflict with external JS
let currentUserLocation = {
@@ -926,6 +927,12 @@
// Request location data from device (renamed to avoid conflicts)
function requestUserLocationData() {
if (typeof AndroidLocationHandler !== 'undefined' && AndroidLocationHandler.isAndroidDevice()) {
console.log("📱 Using Android-enhanced location request");
AndroidLocationHandler.initializeAndroidLocation();
return;
}
if (locationCaptureActive) {
console.log("📍 Location request already active, skipping");
return;