using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace LSFE.MobApp.Services { public interface IDeviceInfoService { Task GetDeviceIdentifierAsync(); Task GetDeviceInfoAsync(); string GetDeviceModel(); string GetDevicePlatform(); string GetDeviceVersion(); } public class DeviceInfoService : IDeviceInfoService { /// /// Gets a unique device identifier that combines multiple device properties /// This is more reliable than IMEI alone and works across platforms /// public async Task GetDeviceIdentifierAsync() { try { // Get device ID from platform-specific implementation var deviceId = await GetPlatformDeviceIdAsync(); // Create a hash of the device ID for security var hashedId = CreateHashedIdentifier(deviceId); return hashedId; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error getting device identifier: {ex.Message}"); // Fallback to a generated ID stored in preferences return await GetOrCreateStoredDeviceIdAsync(); } } /// /// Gets comprehensive device information /// public async Task GetDeviceInfoAsync() { var deviceId = await GetDeviceIdentifierAsync(); return new Infrastructure.Model.Admin.DeviceInfo { DeviceId = deviceId, DeviceModel = GetDeviceModel(), Platform = GetDevicePlatform(), Version = GetDeviceVersion(), Manufacturer = DeviceInfo.Current.Manufacturer, Name = DeviceInfo.Current.Name, DeviceType = DeviceInfo.Current.DeviceType.ToString(), VersionString = DeviceInfo.Current.VersionString }; } public string GetDeviceModel() { return DeviceInfo.Current.Model; } public string GetDevicePlatform() { return DeviceInfo.Current.Platform.ToString(); } public string GetDeviceVersion() { return DeviceInfo.Current.VersionString; } #region Private Methods private async Task GetPlatformDeviceIdAsync() { #if ANDROID return await GetAndroidDeviceIdAsync(); #elif IOS return GetIOSDeviceId(); #else return GetGenericDeviceId(); #endif } #if ANDROID private async Task GetAndroidDeviceIdAsync() { try { var context = Android.App.Application.Context; // Method 1: Try to get Android ID (most reliable, doesn't require permissions) var androidId = Android.Provider.Settings.Secure.GetString( context.ContentResolver, Android.Provider.Settings.Secure.AndroidId); if (!string.IsNullOrEmpty(androidId) && androidId != "9774d56d682e549c") { return $"ANDROID_{androidId}"; } // Method 2: Get IMEI (requires READ_PHONE_STATE permission) // Note: This is restricted in Android 10+ for privacy var imei = await TryGetImeiAsync(context); if (!string.IsNullOrEmpty(imei)) { return $"IMEI_{imei}"; } // Method 3: Fallback to combination of identifiers var buildId = Android.OS.Build.Id; var serial = Android.OS.Build.Serial; var combined = $"{androidId}_{buildId}_{serial}"; return $"ANDROID_COMBINED_{CreateHashedIdentifier(combined)}"; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Android device ID error: {ex.Message}"); return GetGenericDeviceId(); } } private async Task TryGetImeiAsync(Android.Content.Context context) { try { // Check if we have permission var permission = Android.Manifest.Permission.ReadPhoneState; var hasPermission = Android.Content.PM.Permission.Granted == AndroidX.Core.Content.ContextCompat.CheckSelfPermission(context, permission); if (!hasPermission) { System.Diagnostics.Debug.WriteLine("READ_PHONE_STATE permission not granted"); return null; } // For Android 10+, IMEI access is restricted if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Q) { System.Diagnostics.Debug.WriteLine("IMEI access restricted on Android 10+"); return null; } var telephonyManager = context.GetSystemService(Android.Content.Context.TelephonyService) as Android.Telephony.TelephonyManager; if (telephonyManager != null) { #pragma warning disable CS0618 // Type or member is obsolete var imei = telephonyManager.DeviceId; #pragma warning restore CS0618 return imei; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"IMEI retrieval error: {ex.Message}"); } return null; } #endif #if IOS private string GetIOSDeviceId() { try { // iOS uses IdentifierForVendor which is unique per app installation var uuid = UIKit.UIDevice.CurrentDevice.IdentifierForVendor; if (uuid != null) { return $"iOS_{uuid.AsString()}"; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"iOS device ID error: {ex.Message}"); } return GetGenericDeviceId(); } #endif private string GetGenericDeviceId() { // Fallback: combination of available device info var model = DeviceInfo.Current.Model; var manufacturer = DeviceInfo.Current.Manufacturer; var name = DeviceInfo.Current.Name; var platform = DeviceInfo.Current.Platform.ToString(); var combined = $"{platform}_{manufacturer}_{model}_{name}"; return CreateHashedIdentifier(combined); } private async Task GetOrCreateStoredDeviceIdAsync() { const string key = "device_unique_id"; try { var storedId = await SecureStorage.GetAsync(key); if (string.IsNullOrEmpty(storedId)) { // Generate a new unique ID storedId = Guid.NewGuid().ToString(); await SecureStorage.SetAsync(key, storedId); } return $"STORED_{storedId}"; } catch { // If SecureStorage fails, use Preferences var storedId = Preferences.Get(key, string.Empty); if (string.IsNullOrEmpty(storedId)) { storedId = Guid.NewGuid().ToString(); Preferences.Set(key, storedId); } return $"PREF_{storedId}"; } } private string CreateHashedIdentifier(string input) { using var sha256 = SHA256.Create(); var bytes = Encoding.UTF8.GetBytes(input); var hash = sha256.ComputeHash(bytes); return Convert.ToBase64String(hash); } #endregion } }