242 lines
8.1 KiB
C#
242 lines
8.1 KiB
C#
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<string> GetDeviceIdentifierAsync();
|
|
Task<Infrastructure.Model.Admin.DeviceInfo> GetDeviceInfoAsync();
|
|
string GetDeviceModel();
|
|
string GetDevicePlatform();
|
|
string GetDeviceVersion();
|
|
}
|
|
|
|
public class DeviceInfoService : IDeviceInfoService
|
|
{
|
|
/// <summary>
|
|
/// Gets a unique device identifier that combines multiple device properties
|
|
/// This is more reliable than IMEI alone and works across platforms
|
|
/// </summary>
|
|
public async Task<string> 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets comprehensive device information
|
|
/// </summary>
|
|
public async Task<Infrastructure.Model.Admin.DeviceInfo> 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<string> GetPlatformDeviceIdAsync()
|
|
{
|
|
#if ANDROID
|
|
return await GetAndroidDeviceIdAsync();
|
|
#elif IOS
|
|
return GetIOSDeviceId();
|
|
#else
|
|
return GetGenericDeviceId();
|
|
#endif
|
|
}
|
|
|
|
#if ANDROID
|
|
private async Task<string> 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<string> 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<string> 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
|
|
}
|
|
} |