Update location getting method for Android

This commit is contained in:
Nguyen Ngo
2025-08-29 16:30:24 -04:00
parent f0d51ad5ab
commit b4fed2c9ce
+244 -153
View File
@@ -1,13 +1,11 @@
/** /**
* Enhanced Android Location Fix - Separate Module * Android Location Fix - GPS + IP Geolocation Only
* File: static/js/android_location_handler.js * File: static/js/android_location_handler.js
* *
* This module addresses Android-specific geolocation issues: * This version includes only precise location methods:
* 1. Android Chrome timeout handling * 1. Progressive GPS fallback (4 attempts)
* 2. Progressive fallback strategy * 2. IP-based geolocation (3 services) - 5-50km accuracy
* 3. Enhanced permission detection * No timezone analysis (removed 100km accuracy method)
* 4. Network location fallback
* 5. Multiple retry attempts with different configurations
*/ */
// Android-specific geolocation configuration // Android-specific geolocation configuration
@@ -15,46 +13,52 @@ const ANDROID_LOCATION_CONFIG = {
// Primary attempt - High accuracy with reasonable timeout // Primary attempt - High accuracy with reasonable timeout
highAccuracy: { highAccuracy: {
enableHighAccuracy: true, enableHighAccuracy: true,
timeout: 15000, // Increased from 10000 for Android timeout: 15000,
maximumAge: 60000 // Reduced cache time for fresh location maximumAge: 60000
}, },
// Fallback attempt - Network-based location // Fallback attempt - Network-based location
networkBased: { networkBased: {
enableHighAccuracy: false, enableHighAccuracy: false,
timeout: 20000, // Longer timeout for network-based timeout: 20000,
maximumAge: 300000 maximumAge: 300000
}, },
// Final attempt - Any available location // Final attempt - Any available location
anyLocation: { anyLocation: {
enableHighAccuracy: false, enableHighAccuracy: false,
timeout: 30000, // Maximum patience for Android timeout: 30000,
maximumAge: 600000 maximumAge: 600000
} }
}; };
// Enhanced location request with Android-specific handling // Global variables for location state
let locationAttemptInProgress = false;
let currentLocationMethod = '';
// Enhanced location request with GPS + IP fallback only
function requestAndroidEnhancedLocation() { function requestAndroidEnhancedLocation() {
console.log("📱 Starting Android-enhanced location request..."); console.log("📱 Starting Android location request (GPS + IP methods only)...");
if (locationAttemptInProgress) {
console.log("📍 Location attempt already in progress, skipping");
return;
}
if (typeof locationRequestActive !== 'undefined' && locationRequestActive) { if (typeof locationRequestActive !== 'undefined' && locationRequestActive) {
console.log("📍 Location request already active, skipping Android enhancement"); console.log("📍 Location request already active, skipping");
return; return;
} }
if (!navigator.geolocation) { if (!navigator.geolocation) {
console.log("❌ Geolocation not supported"); console.log("❌ Geolocation not supported, trying IP-based location");
if (typeof userLocation !== 'undefined') { attemptIPBasedLocation();
userLocation.source = "manual";
}
if (typeof currentUserLocation !== 'undefined') {
currentUserLocation.source = "manual";
}
return; return;
} }
// Set active flag locationAttemptInProgress = true;
// Set active flags
if (typeof locationRequestActive !== 'undefined') { if (typeof locationRequestActive !== 'undefined') {
locationRequestActive = true; locationRequestActive = true;
} }
@@ -62,19 +66,18 @@ function requestAndroidEnhancedLocation() {
locationCaptureActive = true; locationCaptureActive = true;
} }
console.log("📱 Attempting Android-optimized location sequence..."); console.log("📱 Attempting GPS-based location sequence...");
// Start progressive location attempts
attemptAndroidLocationSequence(); attemptAndroidLocationSequence();
} }
// GPS-based sequence (Steps 1-4)
function attemptAndroidLocationSequence() { function attemptAndroidLocationSequence() {
console.log("🔄 Android Location Sequence - Attempt 1: High Accuracy GPS"); console.log("🔄 Step 1/5: High Accuracy GPS");
currentLocationMethod = 'gps_high_accuracy';
// Attempt 1: High accuracy with Android-optimized timeout
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
(position) => { (position) => {
console.log("✅ Android high-accuracy location success!"); console.log("✅ GPS high-accuracy success!");
handleAndroidLocationSuccess(position, "gps_high_accuracy"); handleAndroidLocationSuccess(position, "gps_high_accuracy");
}, },
(error) => { (error) => {
@@ -86,11 +89,12 @@ function attemptAndroidLocationSequence() {
} }
function attemptNetworkBasedLocation() { function attemptNetworkBasedLocation() {
console.log("🔄 Android Location Sequence - Attempt 2: Network-based"); console.log("🔄 Step 2/5: Network-based GPS");
currentLocationMethod = 'network';
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
(position) => { (position) => {
console.log("✅ Android network-based location success!"); console.log("✅ Network-based GPS success!");
handleAndroidLocationSuccess(position, "network"); handleAndroidLocationSuccess(position, "network");
}, },
(error) => { (error) => {
@@ -102,15 +106,16 @@ function attemptNetworkBasedLocation() {
} }
function attemptAnyAvailableLocation() { function attemptAnyAvailableLocation() {
console.log("🔄 Android Location Sequence - Attempt 3: Any available"); console.log("🔄 Step 3/5: Any available GPS");
currentLocationMethod = 'any';
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
(position) => { (position) => {
console.log("✅ Android any-location success!"); console.log("✅ Any-location GPS success!");
handleAndroidLocationSuccess(position, "any"); handleAndroidLocationSuccess(position, "any");
}, },
(error) => { (error) => {
console.log(`❌ All location attempts failed (${error.message}), trying watchPosition...`); console.log(`❌ Any location failed (${error.message}), trying watchPosition...`);
attemptWatchPosition(); attemptWatchPosition();
}, },
ANDROID_LOCATION_CONFIG.anyLocation ANDROID_LOCATION_CONFIG.anyLocation
@@ -118,77 +123,220 @@ function attemptAnyAvailableLocation() {
} }
function attemptWatchPosition() { function attemptWatchPosition() {
console.log("🔄 Android Location Sequence - Attempt 4: Watch Position (single shot)"); console.log("🔄 Step 4/5: Watch Position (persistent)");
currentLocationMethod = 'watch';
let watchId = null; let watchId = null;
let watchTimeout = null; let watchTimeout = null;
// Set timeout for watch attempt
watchTimeout = setTimeout(() => { watchTimeout = setTimeout(() => {
if (watchId !== null) { if (watchId !== null) {
navigator.geolocation.clearWatch(watchId); navigator.geolocation.clearWatch(watchId);
} }
console.log("❌ Watch position timed out, location failed"); console.log("❌ Watch position timed out, trying IP-based location...");
handleAndroidLocationError("All location methods failed"); attemptIPBasedLocation();
}, 25000); }, 25000);
// Use watchPosition for more persistent location tracking
watchId = navigator.geolocation.watchPosition( watchId = navigator.geolocation.watchPosition(
(position) => { (position) => {
console.log("✅ Android watch position success!"); console.log("✅ Watch position success!");
// Clear watch and timeout
navigator.geolocation.clearWatch(watchId); navigator.geolocation.clearWatch(watchId);
clearTimeout(watchTimeout); clearTimeout(watchTimeout);
handleAndroidLocationSuccess(position, "watch"); handleAndroidLocationSuccess(position, "watch");
}, },
(error) => { (error) => {
console.log(`❌ Watch position error: ${error.message}`); console.log(`❌ Watch position error: ${error.message}`);
// Don't clear watch immediately, let timeout handle it
}, },
{ {
enableHighAccuracy: false, enableHighAccuracy: false,
timeout: 20000, timeout: 20000,
maximumAge: 0 // Force fresh location maximumAge: 0
} }
); );
} }
// IP-based geolocation (Step 5) - Final fallback
function attemptIPBasedLocation() {
console.log("🔄 Step 5/5: IP-based Geolocation (Final Fallback)");
currentLocationMethod = 'ip_geolocation';
// Try multiple IP geolocation services for better accuracy
const ipLocationServices = [
{
url: 'https://ipapi.co/json/',
parseResponse: (data) => ({
lat: data.latitude,
lng: data.longitude,
city: data.city,
region: data.region,
country: data.country_name,
accuracy: data.city ? 10000 : 50000 // Better accuracy if city is available
})
},
{
url: 'https://ip-api.com/json/',
parseResponse: (data) => ({
lat: data.lat,
lng: data.lon,
city: data.city,
region: data.regionName,
country: data.country,
accuracy: data.city ? 10000 : 50000 // Better accuracy if city is available
})
},
{
url: 'https://ipinfo.io/json',
parseResponse: (data) => {
if (data.loc) {
const [lat, lng] = data.loc.split(',');
return {
lat: parseFloat(lat),
lng: parseFloat(lng),
city: data.city,
region: data.region,
country: data.country,
accuracy: data.city ? 15000 : 50000 // Better accuracy if city is available
};
}
return null;
}
}
];
let serviceIndex = 0;
function tryNextIPService() {
if (serviceIndex >= ipLocationServices.length) {
console.log("❌ All IP geolocation services failed - location detection complete");
handleAndroidLocationError("All GPS and IP geolocation methods failed");
return;
}
const service = ipLocationServices[serviceIndex];
console.log(`🌐 Trying IP geolocation service ${serviceIndex + 1}: ${service.url}`);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
fetch(service.url, {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
clearTimeout(timeoutId);
console.log(`🌐 IP service ${serviceIndex + 1} response:`, data);
const parsed = service.parseResponse(data);
if (parsed && parsed.lat && parsed.lng && !isNaN(parsed.lat) && !isNaN(parsed.lng)) {
// Validate coordinates are reasonable
if (parsed.lat >= -90 && parsed.lat <= 90 && parsed.lng >= -180 && parsed.lng <= 180) {
console.log(`✅ IP-based location success: ${parsed.lat}, ${parsed.lng}`);
console.log(`📊 Location: ${parsed.city}, ${parsed.region}, ${parsed.country}`);
console.log(`📊 Estimated accuracy: ${parsed.accuracy}m (~${Math.round(parsed.accuracy/1000)}km)`);
const ipLocationData = {
coords: {
latitude: parsed.lat,
longitude: parsed.lng,
accuracy: parsed.accuracy,
altitude: null
},
locationInfo: {
city: parsed.city,
region: parsed.region,
country: parsed.country,
source: `IP Service ${serviceIndex + 1}`,
serviceUrl: service.url
}
};
handleAndroidLocationSuccess(ipLocationData, "ip_geolocation");
return;
}
}
console.log(`❌ Invalid or missing coordinates from service ${serviceIndex + 1}, trying next...`);
serviceIndex++;
tryNextIPService();
})
.catch(error => {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
console.log(`❌ IP service ${serviceIndex + 1} timed out (10s), trying next...`);
} else {
console.log(`❌ IP service ${serviceIndex + 1} failed: ${error.message}, trying next...`);
}
serviceIndex++;
tryNextIPService();
});
}
tryNextIPService();
}
// Enhanced success handler for GPS and IP location methods
function handleAndroidLocationSuccess(position, source) { function handleAndroidLocationSuccess(position, source) {
console.log(`✅ Android location obtained successfully via ${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 let locationData;
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 (source === "ip_geolocation") {
// Handle IP-based location
locationData = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude,
timestamp: new Date(),
source: source,
address: null,
locationInfo: position.locationInfo || null
};
console.log(`📍 IP-estimated coordinates: ${position.coords.latitude}, ${position.coords.longitude}`);
console.log(`📊 IP-estimated accuracy: ${position.coords.accuracy}m (~${Math.round(position.coords.accuracy/1000)}km)`);
if (position.locationInfo) {
console.log(`🏢 Location info: ${position.locationInfo.city}, ${position.locationInfo.region}`);
}
} else {
// Handle GPS-based location
locationData = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude,
timestamp: new Date(),
source: source,
address: null,
};
console.log(`📍 GPS coordinates: ${position.coords.latitude}, ${position.coords.longitude}`);
console.log(`📊 GPS accuracy: ${position.coords.accuracy}m`);
}
// Update global location variables
if (typeof userLocation !== 'undefined') { if (typeof userLocation !== 'undefined') {
Object.assign(userLocation, locationData); Object.assign(userLocation, locationData);
console.log("📍 Updated userLocation with Android data"); console.log("📍 Updated userLocation with location data");
// Trigger reverse geocoding if function exists // Trigger reverse geocoding if we have coordinates but no address
if (typeof reverseGeocode === 'function') { if (typeof reverseGeocode === 'function' && locationData.latitude && locationData.longitude && !locationData.address) {
reverseGeocode(userLocation.latitude, userLocation.longitude); reverseGeocode(locationData.latitude, locationData.longitude);
} }
} }
if (typeof currentUserLocation !== 'undefined') { if (typeof currentUserLocation !== 'undefined') {
Object.assign(currentUserLocation, locationData); Object.assign(currentUserLocation, locationData);
console.log("📍 Updated currentUserLocation with Android data"); console.log("📍 Updated currentUserLocation with location data");
// Trigger enhanced reverse geocoding if function exists // Trigger enhanced reverse geocoding if available
if (typeof reverseGeocodeEnhanced === 'function') { if (typeof reverseGeocodeEnhanced === 'function' && locationData.latitude && locationData.longitude && !locationData.address) {
reverseGeocodeEnhanced(currentUserLocation.latitude, currentUserLocation.longitude); reverseGeocodeEnhanced(locationData.latitude, locationData.longitude);
} }
} }
@@ -199,19 +347,24 @@ function handleAndroidLocationSuccess(position, source) {
if (typeof locationCaptureActive !== 'undefined') { if (typeof locationCaptureActive !== 'undefined') {
locationCaptureActive = false; locationCaptureActive = false;
} }
locationAttemptInProgress = false;
// Log success for monitoring // Console logging
logLocationAction('android_location_success', { console.log(`📊 LOCATION SUCCESS LOG:`, {
source: source, method: source,
accuracy: position.coords.accuracy, success: true,
coordinates: `${position.coords.latitude},${position.coords.longitude}` coordinates: `${locationData.latitude},${locationData.longitude}`,
accuracy: `${locationData.accuracy}m`,
accuracyKm: `~${Math.round(locationData.accuracy/1000)}km`,
locationInfo: locationData.locationInfo,
timestamp: new Date().toISOString()
}); });
} }
function handleAndroidLocationError(errorMessage) { function handleAndroidLocationError(errorMessage) {
console.log(`❌ Android location error: ${errorMessage}`); console.log(` All Android location methods failed: ${errorMessage}`);
// Update global location variables // Update global location variables to indicate manual source
if (typeof userLocation !== 'undefined') { if (typeof userLocation !== 'undefined') {
userLocation.source = "manual"; userLocation.source = "manual";
} }
@@ -226,69 +379,40 @@ function handleAndroidLocationError(errorMessage) {
if (typeof locationCaptureActive !== 'undefined') { if (typeof locationCaptureActive !== 'undefined') {
locationCaptureActive = false; locationCaptureActive = false;
} }
locationAttemptInProgress = false;
// Log error for monitoring console.log(`📊 LOCATION ERROR LOG:`, {
logLocationAction('android_location_error', {
error: errorMessage, error: errorMessage,
userAgent: navigator.userAgent method: currentLocationMethod,
finalResult: 'manual_entry_required',
gpsAttempts: 4,
ipAttempts: 3,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
}); });
} }
// Enhanced permission checking for Android // Device detection functions
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() { function isAndroidDevice() {
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
return userAgent.includes('android'); return userAgent.includes('android');
} }
// Detect if browser is Chrome on Android
function isAndroidChrome() { function isAndroidChrome() {
const userAgent = navigator.userAgent.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase();
return userAgent.includes('android') && userAgent.includes('chrome') && !userAgent.includes('edg'); return userAgent.includes('android') && userAgent.includes('chrome') && !userAgent.includes('edg');
} }
// Enhanced location initialization for Android // Main initialization function
function initializeAndroidLocation() { function initializeAndroidLocation() {
console.log("📱 Initializing Android-enhanced location services..."); console.log("📱 Initializing Android location services (GPS + IP only)...");
if (isAndroidDevice()) { if (isAndroidDevice()) {
console.log("📱 Android device detected, using enhanced location handling"); console.log("📱 Android device detected, using GPS + IP location methods (5 steps)");
requestAndroidEnhancedLocation();
// Use Android-specific permission checking
checkAndroidLocationPermissions();
} else { } else {
console.log("📱 Non-Android device, using standard location request"); console.log("📱 Non-Android device, using standard location request");
// Fall back to standard location request
if (typeof requestLocationData === 'function') { if (typeof requestLocationData === 'function') {
requestLocationData(); requestLocationData();
} else if (typeof requestEnhancedLocation === 'function') { } else if (typeof requestEnhancedLocation === 'function') {
@@ -297,54 +421,22 @@ function initializeAndroidLocation() {
} }
} }
// Location action logging function // Override standard location initialization for Android devices
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() { document.addEventListener('DOMContentLoaded', function() {
// Small delay to ensure other scripts are loaded
setTimeout(() => { setTimeout(() => {
if (isAndroidDevice()) { if (isAndroidDevice()) {
console.log("📱 Android detected - overriding standard location initialization"); console.log("📱 Android detected - overriding with GPS + IP location handler");
// Replace standard initialization with Android-enhanced version
if (typeof initializeLocation === 'function') { if (typeof initializeLocation === 'function') {
const originalInitializeLocation = initializeLocation;
window.initializeLocation = function() { window.initializeLocation = function() {
console.log("📱 Using Android-enhanced location initialization"); console.log("📱 Using GPS + IP Android location initialization");
initializeAndroidLocation(); initializeAndroidLocation();
}; };
} }
// Also handle the enhanced location capture
if (typeof requestEnhancedLocation === 'function') { if (typeof requestEnhancedLocation === 'function') {
const originalRequestEnhanced = requestEnhancedLocation;
window.requestEnhancedLocation = function() { window.requestEnhancedLocation = function() {
console.log("📱 Using Android-enhanced location request"); console.log("📱 Using GPS + IP Android location request");
initializeAndroidLocation(); initializeAndroidLocation();
}; };
} }
@@ -352,14 +444,13 @@ document.addEventListener('DOMContentLoaded', function() {
}, 100); }, 100);
}); });
// Export functions for external use // Export functions
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.AndroidLocationHandler = { window.AndroidLocationHandler = {
requestAndroidEnhancedLocation, requestAndroidEnhancedLocation,
checkAndroidLocationPermissions,
isAndroidDevice, isAndroidDevice,
isAndroidChrome, isAndroidChrome,
initializeAndroidLocation, initializeAndroidLocation,
logLocationAction attemptIPBasedLocation
}; };
} }