383 lines
14 KiB
C#
383 lines
14 KiB
C#
|
|
using UnityEngine;
|
|||
|
|
using System;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Android平台数据导出权限管理器
|
|||
|
|
/// 处理Android平台导出数据所需的存储权限
|
|||
|
|
/// </summary>
|
|||
|
|
public static class AndroidExportPermissionManager
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检查是否有外部存储写入权限
|
|||
|
|
/// </summary>
|
|||
|
|
public static bool HasWriteExternalStoragePermission()
|
|||
|
|
{
|
|||
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
|||
|
|
using (var currentActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity"))
|
|||
|
|
using (var context = currentActivity.Call<AndroidJavaObject>("getApplicationContext"))
|
|||
|
|
{
|
|||
|
|
var permissionClass = new AndroidJavaClass("android.Manifest$permission");
|
|||
|
|
string writePermission = permissionClass.GetStatic<string>("WRITE_EXTERNAL_STORAGE");
|
|||
|
|
|
|||
|
|
var contextCompat = new AndroidJavaClass("androidx.core.content.ContextCompat");
|
|||
|
|
int result = contextCompat.CallStatic<int>("checkSelfPermission", context, writePermission);
|
|||
|
|
|
|||
|
|
return result == 0; // PackageManager.PERMISSION_GRANTED = 0
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"检查存储权限失败: {ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
#else
|
|||
|
|
return true; // 非Android平台默认有权限
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 请求外部存储写入权限
|
|||
|
|
/// </summary>
|
|||
|
|
public static void RequestWriteExternalStoragePermission()
|
|||
|
|
{
|
|||
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
|||
|
|
using (var currentActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity"))
|
|||
|
|
{
|
|||
|
|
var permissionClass = new AndroidJavaClass("android.Manifest$permission");
|
|||
|
|
string writePermission = permissionClass.GetStatic<string>("WRITE_EXTERNAL_STORAGE");
|
|||
|
|
|
|||
|
|
// 检查是否需要显示权限说明
|
|||
|
|
var activityCompat = new AndroidJavaClass("androidx.core.app.ActivityCompat");
|
|||
|
|
bool shouldShow = activityCompat.CallStatic<bool>("shouldShowRequestPermissionRationale",
|
|||
|
|
currentActivity, writePermission);
|
|||
|
|
|
|||
|
|
if (shouldShow)
|
|||
|
|
{
|
|||
|
|
ShowPermissionRationale();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 请求权限
|
|||
|
|
string[] permissions = { writePermission };
|
|||
|
|
activityCompat.CallStatic("requestPermissions", currentActivity, permissions, 1001);
|
|||
|
|
|
|||
|
|
Debug.Log("已请求存储权限");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"请求存储权限失败: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检查是否有管理外部存储权限(Android 11+)
|
|||
|
|
/// </summary>
|
|||
|
|
public static bool HasManageExternalStoragePermission()
|
|||
|
|
{
|
|||
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// Android 11 (API 30) 及以上需要MANAGE_EXTERNAL_STORAGE权限
|
|||
|
|
var buildVersion = new AndroidJavaClass("android.os.Build$VERSION");
|
|||
|
|
int sdkInt = buildVersion.GetStatic<int>("SDK_INT");
|
|||
|
|
|
|||
|
|
if (sdkInt >= 30) // Android 11+
|
|||
|
|
{
|
|||
|
|
var environment = new AndroidJavaClass("android.os.Environment");
|
|||
|
|
return environment.CallStatic<bool>("isExternalStorageManager");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Android 10及以下使用传统权限
|
|||
|
|
return HasWriteExternalStoragePermission();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"检查管理外部存储权限失败: {ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
#else
|
|||
|
|
return true;
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 请求管理外部存储权限(Android 11+)
|
|||
|
|
/// </summary>
|
|||
|
|
public static void RequestManageExternalStoragePermission()
|
|||
|
|
{
|
|||
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var buildVersion = new AndroidJavaClass("android.os.Build$VERSION");
|
|||
|
|
int sdkInt = buildVersion.GetStatic<int>("SDK_INT");
|
|||
|
|
|
|||
|
|
if (sdkInt >= 30) // Android 11+
|
|||
|
|
{
|
|||
|
|
using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
|||
|
|
using (var currentActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity"))
|
|||
|
|
{
|
|||
|
|
var intent = new AndroidJavaObject("android.content.Intent");
|
|||
|
|
intent.Call<AndroidJavaObject>("setAction", "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION");
|
|||
|
|
|
|||
|
|
var uri = new AndroidJavaClass("android.net.Uri");
|
|||
|
|
var packageUri = uri.CallStatic<AndroidJavaObject>("parse",
|
|||
|
|
"package:" + currentActivity.Call<string>("getPackageName"));
|
|||
|
|
intent.Call<AndroidJavaObject>("setData", packageUri);
|
|||
|
|
|
|||
|
|
currentActivity.Call("startActivity", intent);
|
|||
|
|
|
|||
|
|
Debug.Log("已打开管理外部存储权限设置页面");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Android 10及以下使用传统权限请求
|
|||
|
|
RequestWriteExternalStoragePermission();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"请求管理外部存储权限失败: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取推荐的导出目录
|
|||
|
|
/// </summary>
|
|||
|
|
public static string GetRecommendedExportDirectory()
|
|||
|
|
{
|
|||
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
|||
|
|
using (var currentActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity"))
|
|||
|
|
{
|
|||
|
|
var buildVersion = new AndroidJavaClass("android.os.Build$VERSION");
|
|||
|
|
int sdkInt = buildVersion.GetStatic<int>("SDK_INT");
|
|||
|
|
|
|||
|
|
if (sdkInt >= 29) // Android 10+,使用分区存储
|
|||
|
|
{
|
|||
|
|
// 使用应用专用目录,无需权限
|
|||
|
|
var context = currentActivity.Call<AndroidJavaObject>("getApplicationContext");
|
|||
|
|
var externalFilesDir = context.Call<AndroidJavaObject>("getExternalFilesDir", "Documents");
|
|||
|
|
|
|||
|
|
if (externalFilesDir != null)
|
|||
|
|
{
|
|||
|
|
return externalFilesDir.Call<string>("getAbsolutePath");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return Application.persistentDataPath;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Android 9及以下,使用公共目录
|
|||
|
|
var environment = new AndroidJavaClass("android.os.Environment");
|
|||
|
|
var externalStorage = environment.CallStatic<AndroidJavaObject>("getExternalStorageDirectory");
|
|||
|
|
string storagePath = externalStorage.Call<string>("getAbsolutePath");
|
|||
|
|
|
|||
|
|
return System.IO.Path.Combine(storagePath, "Download", "DCX_Export");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"获取导出目录失败: {ex.Message}");
|
|||
|
|
return Application.persistentDataPath;
|
|||
|
|
}
|
|||
|
|
#else
|
|||
|
|
return System.IO.Path.Combine(System.Environment.GetFolderPath(
|
|||
|
|
System.Environment.SpecialFolder.Desktop), "DCX_Export");
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取应用内部稳定目录。Android 7+优先使用Device Protected Storage,
|
|||
|
|
/// 可在系统刚启动且用户未解锁时访问,适合作为历史索引主存储。
|
|||
|
|
/// </summary>
|
|||
|
|
public static string GetStableAppInternalDirectory()
|
|||
|
|
{
|
|||
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
|||
|
|
using (var currentActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity"))
|
|||
|
|
{
|
|||
|
|
var context = currentActivity.Call<AndroidJavaObject>("getApplicationContext");
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using (var buildVersion = new AndroidJavaClass("android.os.Build$VERSION"))
|
|||
|
|
{
|
|||
|
|
int sdkInt = buildVersion.GetStatic<int>("SDK_INT");
|
|||
|
|
if (sdkInt >= 24)
|
|||
|
|
{
|
|||
|
|
var deviceProtectedContext = context.Call<AndroidJavaObject>("createDeviceProtectedStorageContext");
|
|||
|
|
if (deviceProtectedContext != null)
|
|||
|
|
{
|
|||
|
|
var deviceProtectedDir = deviceProtectedContext.Call<AndroidJavaObject>("getFilesDir");
|
|||
|
|
if (deviceProtectedDir != null)
|
|||
|
|
{
|
|||
|
|
return deviceProtectedDir.Call<string>("getAbsolutePath");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"获取Device Protected Storage失败,回退内部目录: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var filesDir = context.Call<AndroidJavaObject>("getFilesDir");
|
|||
|
|
if (filesDir != null)
|
|||
|
|
{
|
|||
|
|
return filesDir.Call<string>("getAbsolutePath");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"获取应用内部稳定目录失败: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return Application.persistentDataPath;
|
|||
|
|
#else
|
|||
|
|
return Application.persistentDataPath;
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检查导出目录是否可访问
|
|||
|
|
/// </summary>
|
|||
|
|
public static bool IsExportDirectoryAccessible(string directory)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (!System.IO.Directory.Exists(directory))
|
|||
|
|
{
|
|||
|
|
System.IO.Directory.CreateDirectory(directory);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 尝试写入测试文件
|
|||
|
|
string testFile = System.IO.Path.Combine(directory, "test.txt");
|
|||
|
|
System.IO.File.WriteAllText(testFile, "test");
|
|||
|
|
|
|||
|
|
if (System.IO.File.Exists(testFile))
|
|||
|
|
{
|
|||
|
|
System.IO.File.Delete(testFile);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"目录访问测试失败: {ex.Message}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 显示权限说明
|
|||
|
|
/// </summary>
|
|||
|
|
private static void ShowPermissionRationale()
|
|||
|
|
{
|
|||
|
|
string message = "数据导出功能需要存储权限来保存文件到设备存储空间。\n\n" +
|
|||
|
|
"请在弹出的权限请求中选择'允许',以便:\n" +
|
|||
|
|
"• 导出BFI测试报告\n" +
|
|||
|
|
"• 保存折线图图片\n" +
|
|||
|
|
"• 导出历史数据";
|
|||
|
|
|
|||
|
|
Debug.Log($"权限说明: {message}");
|
|||
|
|
|
|||
|
|
// 在实际应用中,这里应该显示一个用户友好的对话框
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取存储权限状态说明
|
|||
|
|
/// </summary>
|
|||
|
|
public static string GetPermissionStatusDescription()
|
|||
|
|
{
|
|||
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|||
|
|
var buildVersion = new AndroidJavaClass("android.os.Build$VERSION");
|
|||
|
|
int sdkInt = buildVersion.GetStatic<int>("SDK_INT");
|
|||
|
|
|
|||
|
|
if (sdkInt >= 30) // Android 11+
|
|||
|
|
{
|
|||
|
|
bool hasPermission = HasManageExternalStoragePermission();
|
|||
|
|
if (hasPermission)
|
|||
|
|
{
|
|||
|
|
return "✓ 已获得管理外部存储权限,可以导出数据到任何位置";
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return "⚠ 需要管理外部存储权限才能导出数据到公共目录";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (sdkInt >= 23) // Android 6.0+
|
|||
|
|
{
|
|||
|
|
bool hasPermission = HasWriteExternalStoragePermission();
|
|||
|
|
if (hasPermission)
|
|||
|
|
{
|
|||
|
|
return "✓ 已获得存储权限,可以导出数据";
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return "⚠ 需要存储权限才能导出数据";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return "✓ 系统版本较低,无需额外权限";
|
|||
|
|
}
|
|||
|
|
#else
|
|||
|
|
return "✓ 当前平台无权限限制";
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 处理权限请求结果
|
|||
|
|
/// </summary>
|
|||
|
|
public static void HandlePermissionResult(string permission, bool granted)
|
|||
|
|
{
|
|||
|
|
if (permission.Contains("WRITE_EXTERNAL_STORAGE"))
|
|||
|
|
{
|
|||
|
|
if (granted)
|
|||
|
|
{
|
|||
|
|
Debug.Log("存储权限已获得,可以开始导出数据");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning("存储权限被拒绝,数据导出功能将受限");
|
|||
|
|
ShowPermissionDeniedGuidance();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 显示权限被拒绝时的指导
|
|||
|
|
/// </summary>
|
|||
|
|
private static void ShowPermissionDeniedGuidance()
|
|||
|
|
{
|
|||
|
|
string message = "存储权限被拒绝,您仍然可以:\n\n" +
|
|||
|
|
"1. 使用应用内部存储(容量有限)\n" +
|
|||
|
|
"2. 通过分享功能发送数据\n" +
|
|||
|
|
"3. 前往设置手动开启存储权限\n\n" +
|
|||
|
|
"建议开启权限以获得完整的导出功能。";
|
|||
|
|
|
|||
|
|
Debug.Log($"权限拒绝指导: {message}");
|
|||
|
|
}
|
|||
|
|
}
|