LSFE/LSFE.MobApp/Services/DeviceInfoService.cs
2026-07-30 10:31:04 +08:00

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
}
}