fix Android Guide Pages Test API 31 🆗

This commit is contained in:
Aigio Liu
2022-03-25 00:17:47 +08:00
parent 170dc355b7
commit ceabdaaa64
21 changed files with 318 additions and 105 deletions
@@ -2,33 +2,54 @@ using Android.Runtime;
using Java.Security.Cert;
using System.IO;
using Xamarin.Android.Net;
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
// ReSharper disable once CheckNamespace
namespace System
{
public static partial class HttpClientExtensions
{
const string certificateFileType = "X.509";
static readonly Lazy<CertificateFactory?> certificateFactory = new(() =>
{
var certificateFactory = CertificateFactory.GetInstance(certificateFileType);
return certificateFactory;
});
internal static CertificateFactory CertificateFactory
{
get
{
var certificateFactory = HttpClientExtensions.certificateFactory.Value;
if (certificateFactory == null)
throw new NullReferenceException(
$"CertificateFactory.GetInstance Fail, Type: {certificateFileType}");
return certificateFactory;
}
}
/// <summary>
/// 添加可信任证书,可用于调试时HTTPS抓包
/// </summary>
/// <param name="handler"></param>
/// <param name="certificateFileData">证书文件数据(文件流,内嵌资源流)</param>
/// <param name="certificateFileType">证书文件类型,默认使用X.509</param>
public static void AddTrustedCert(
this AndroidClientHandler handler,
Stream certificateFileData,
string certificateFileType = "X.509")
Stream certificateFileData)
{
var certificateFactory = CertificateFactory.GetInstance(certificateFileType);
if (certificateFactory == null)
throw new NullReferenceException(
$"CertificateFactory.GetInstance Fail, Type: {certificateFileType}");
var certificate = certificateFactory.GenerateCertificate(certificateFileData);
var certificate = CertificateFactory.GenerateCertificate(certificateFileData);
if (certificate == null)
throw new NullReferenceException("GenerateCertificate Fail");
if (handler.TrustedCerts == null)
handler.TrustedCerts = new JavaList<Certificate>();
handler.TrustedCerts.Add(certificate);
}
public static X509Certificate Convert(this X509Certificate2 certificate2)
{
using var stream = new MemoryStream(certificate2.RawData);
return CertificateFactory.GenerateCertificate(stream)!.JavaCast<X509Certificate>();
}
}
}
+51 -31
View File
@@ -84,38 +84,38 @@ namespace System.Application
return default;
}
// [Obsolete]
// public static void StartActivity(Activity activity, Type activityType, object viewModel, Type? viewModelType = null)
// {
// viewModelType ??= viewModel.GetType();
// var viewModel_ = Serializable.SMP(viewModelType, viewModel);
//#if DEBUG
// var t = Serializable.DMP(viewModelType, viewModel_);
//#endif
// StartActivity(activity, activityType, viewModel_);
// }
// [Obsolete]
// public static void StartActivity(Activity activity, Type activityType, object viewModel, Type? viewModelType = null)
// {
// viewModelType ??= viewModel.GetType();
// var viewModel_ = Serializable.SMP(viewModelType, viewModel);
//#if DEBUG
// var t = Serializable.DMP(viewModelType, viewModel_);
//#endif
// StartActivity(activity, activityType, viewModel_);
// }
// [Obsolete]
// public static void StartActivity(Fragment fragment, Type activityType, object viewModel, Type? viewModelType = null)
// {
// var activity = fragment.Activity;
// if (activity == null) return;
// StartActivity(activity, activityType, viewModel, viewModelType);
// }
// [Obsolete]
// public static void StartActivity(Fragment fragment, Type activityType, object viewModel, Type? viewModelType = null)
// {
// var activity = fragment.Activity;
// if (activity == null) return;
// StartActivity(activity, activityType, viewModel, viewModelType);
// }
// [Obsolete]
// public static void StartActivity<TActivity>(Activity activity, object viewModel, Type? viewModelType = null)
// {
// StartActivity(activity, typeof(TActivity), viewModel, viewModelType);
// }
// [Obsolete]
// public static void StartActivity<TActivity>(Activity activity, object viewModel, Type? viewModelType = null)
// {
// StartActivity(activity, typeof(TActivity), viewModel, viewModelType);
// }
// [Obsolete]
// public static void StartActivity<TActivity>(Fragment fragment, object viewModel, Type? viewModelType = null)
// {
// var activity = fragment.Activity;
// if (activity == null) return;
// StartActivity<TActivity>(activity, viewModel, viewModelType);
// }
// [Obsolete]
// public static void StartActivity<TActivity>(Fragment fragment, object viewModel, Type? viewModelType = null)
// {
// var activity = fragment.Activity;
// if (activity == null) return;
// StartActivity<TActivity>(activity, viewModel, viewModelType);
// }
public static void StartActivity<TViewModel>(Activity activity, Type activityType, TViewModel viewModel)
{
@@ -184,7 +184,7 @@ namespace System.Application
return InstallApk(context, apkFile);
}
public static bool InstallCertificate(Context context, string filePath, string name)
public static Intent? GetInstallCertificateIntent(string filePath, string name)
{
try
{
@@ -193,6 +193,20 @@ namespace System.Application
intent.PutExtra("name", name);
intent.PutExtra("CERT", File.ReadAllBytes(filePath));
intent.AddFlags(ActivityFlags.NewTask);
return intent;
}
catch
{
return null;
}
}
public static bool InstallCertificate(Context context, string filePath, string name)
{
try
{
var intent = GetInstallCertificateIntent(filePath, name);
if (intent == null) return false;
context.StartActivity(intent);
return true;
}
@@ -202,10 +216,16 @@ namespace System.Application
}
}
static void StartNewTaskActivity(Context context, string action)
public static Intent GetNewTaskIntent(string action)
{
var intent = new Intent(action);
intent.AddFlags(ActivityFlags.NewTask);
return intent;
}
static void StartNewTaskActivity(Context context, string action)
{
var intent = GetNewTaskIntent(action);
context.StartActivity(intent);
}
@@ -15,6 +15,13 @@ namespace System.Application
ConfigChanges.LayoutDirection |
ConfigChanges.SmallestScreenSize;
public const ConfigChanges ConfigurationChangesWithOutOrientationLocale =
ConfigChanges.ScreenSize |
ConfigChanges.Keyboard |
ConfigChanges.KeyboardHidden |
ConfigChanges.ScreenLayout |
ConfigChanges.SmallestScreenSize;
public const string MainTheme2 = "@style/MainTheme2";
public const string MainTheme2_NoActionBar = "@style/MainTheme2.NoActionBar";
public const string MainTheme2_Splash = "@style/MainTheme2.Splash";
@@ -0,0 +1,46 @@
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Java.Security;
using Javax.Net.Ssl;
namespace System.Application.Services.Implementation
{
partial class AndroidPlatformServiceImpl
{
static IX509TrustManager? X509TrustManager
{
get
{
var instance = KeyStore.GetInstance("AndroidCAStore");
if (instance == null) return null;
instance.Load(null);
var instance2 = TrustManagerFactory.GetInstance(TrustManagerFactory.DefaultAlgorithm);
if (instance2 == null) return null;
instance2.Init(instance);
var trustManager = instance2.GetTrustManagers()?
.Select(x => x is IX509TrustManager x509TrustManager ? x509TrustManager : null)
.FirstOrDefault(x => x != null);
return trustManager;
}
}
internal static bool IsCertificateInstalled(X509Certificate2 certificate2)
{
var x509TrustManager = X509TrustManager;
if (x509TrustManager == null) return false;
using var certificate = certificate2.Convert();
var authType = certificate.SigAlgName;
try
{
x509TrustManager.CheckServerTrusted(new[] { certificate }, authType);
return true;
}
catch (Exception e)
{
return false;
}
}
bool IPlatformService.IsCertificateInstalled(X509Certificate2 certificate2) => IsCertificateInstalled(certificate2);
}
}
@@ -33,8 +33,15 @@ namespace System.Application.Services.Native
{
void OnResult(Intent intent)
=> activity.StartOrStopForegroundService<VpnService>(true);
await IntermediateActivity.StartAsync(intent,
requestCodeVpnService, onResult: OnResult);
try
{
await IntermediateActivity.StartAsync(intent,
requestCodeVpnService, onResult: OnResult);
}
catch (OperationCanceledException)
{
}
return;
}
}
@@ -18,6 +18,8 @@ using Android.Views;
using System.Application.Services;
using ASettings = Android.Provider.Settings;
using static AndroidX.Activity.Result.ActivityResultTask;
using System.Threading.Tasks;
using XEPlatform = Xamarin.Essentials.Platform;
namespace System.Application.UI.Activities
{
@@ -27,7 +29,7 @@ namespace System.Application.UI.Activities
[Register(JavaPackageConstants.Activities + nameof(GuideCACertActivity))]
[Activity(Theme = ManifestConstants.MainTheme2_NoActionBar,
LaunchMode = LaunchMode.SingleTask,
ConfigurationChanges = ManifestConstants.ConfigurationChanges)]
ConfigurationChanges = ManifestConstants.ConfigurationChangesWithOutOrientationLocale)]
internal sealed class GuideCACertActivity : BaseActivity<activity_guide_ca_cert>
{
protected override int? LayoutResource => Resource.Layout.activity_guide_ca_cert;
@@ -38,9 +40,13 @@ namespace System.Application.UI.Activities
this.SetSupportActionBarWithNavigationClick(binding!.toolbar, true);
Title = string.Empty;
var appBarConfiguration = new AppBarConfiguration.Builder(Resource.Id.navigation_guide_export_ca_cert).Build();
navController = ((NavHostFragment)SupportFragmentManager.FindFragmentById(Resource.Id.nav_host_fragment)).NavController;
NavigationUI.SetupActionBarWithNavController(this, navController, appBarConfiguration);
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
}
[Register(JavaPackageConstants.Fragments + nameof(GuideExportCACertFragment))]
@@ -53,10 +59,24 @@ namespace System.Application.UI.Activities
return IViewModelManager.Instance.GetMainPageViewModel<CommunityProxyPageViewModel>();
}
bool canInstalledCACert;
public override void OnCreateView(View view)
{
base.OnCreateView(view);
var sdkInt = (int)Build.VERSION.SdkInt;
canInstalledCACert = !(sdkInt > 29 || (sdkInt == 29 && Build.VERSION.PreviewSdkInt > 0));
// com.adguard.kit.compatibility.h()
// com.adguard.android.ui.fragments.https_ca_installation.b()
// return com.adguard.kit.compatibility.a.h() ? super.b() : R.l.https_ca_installation_guide_install_ca;
// Android 10+ 只能通过系统设置从存储区导入 CA 证书
if (canInstalledCACert)
{
binding!.tvDesc3.Visibility = ViewStates.Gone;
binding!.btnDone.Text = GetString(Resource.String.guide_export_ca_cert_btn_install);
}
SetOnClickListener(binding!.btnDone);
}
@@ -64,7 +84,14 @@ namespace System.Application.UI.Activities
{
if (view.Id == Resource.Id.btnDone)
{
ExportCertificateFile();
if (canInstalledCACert)
{
InstallCertificate();
}
else
{
ExportCertificateFile();
}
return true;
}
return base.OnClick(view);
@@ -75,15 +102,48 @@ namespace System.Application.UI.Activities
var isOK = await ViewModel!.ExportCertificateFileAsync();
if (isOK)
{
GoToGuideHowInstallCACertPage();
await GoToGuideHowInstallCACertPageAsync();
}
}
void GoToGuideHowInstallCACertPage()
async Task GoToGuideHowInstallCACertPageAsync()
{
int count = 0;
while (XEPlatform.CurrentActivity != RequireActivity() && count < 10)
{
await Task.Delay(200);
count++;
}
var navController = this.GetNavController();
navController?.Navigate(Resource.Id.action_navigation_guide_export_ca_cert_to_navigation_guide_how_install_ca_cert);
}
async void InstallCertificate()
{
var isOK = await ViewModel!.ExportCertificateFileAsync(InstallCertificateAsync);
if (isOK)
{
var a = RequireActivity();
var intent = IntermediateActivity.SetResult(new(), Result.Ok);
a.SetResult(Result.Ok, intent);
a.Finish();
}
}
async Task<bool> InstallCertificateAsync(string cefFilePath)
{
var intent = GoToPlatformPages.GetInstallCertificateIntent(cefFilePath, IHttpProxyService.RootCertificateName);
if (intent == null) return false;
try
{
await IntermediateActivity.StartAsync(intent, NextRequestCode());
}
catch (OperationCanceledException)
{
}
return IHttpProxyService.Instance.IsCurrentCertificateInstalled;
}
}
[Register(JavaPackageConstants.Fragments + nameof(GuideHowInstallCACertFragment))]
@@ -98,29 +158,28 @@ namespace System.Application.UI.Activities
SetOnClickListener(binding!.btnDone);
}
public override void OnResume()
{
base.OnResume();
if (IHttpProxyService.Instance.IsCurrentCertificateInstalled)
{
var intent = IntermediateActivity.SetResult(new(), Result.Ok);
var a = RequireActivity();
a.SetResult(Result.Ok, intent);
a.Finish();
}
}
protected override bool OnClick(View view)
{
if (view.Id == Resource.Id.btnDone)
{
GoToSystemSettingsSecurity();
GoToPlatformPages.SystemSettingsSecurity(RequireActivity());
return true;
}
return base.OnClick(view);
}
async void GoToSystemSettingsSecurity()
{
var intent = new Intent(ASettings.ActionSecuritySettings);
intent.AddFlags(ActivityFlags.NewTask);
intent = await IntermediateActivity.StartAsync(intent, NextRequestCode());
var a = RequireActivity();
if (IHttpProxyService.Instance.IsCurrentCertificateInstalled)
{
intent = IntermediateActivity.SetResult(intent, Result.Ok);
a.SetResult(Result.Ok, intent);
}
a.Finish();
}
}
}
}
@@ -26,7 +26,7 @@ namespace System.Application.UI.Activities
[Register(JavaPackageConstants.Activities + nameof(GuideVPNActivity))]
[Activity(Theme = ManifestConstants.MainTheme2_NoActionBar,
LaunchMode = LaunchMode.SingleTask,
ConfigurationChanges = ManifestConstants.ConfigurationChanges)]
ConfigurationChanges = ManifestConstants.ConfigurationChangesWithOutOrientationLocale)]
internal sealed class GuideVPNActivity : BaseActivity<activity_guide_vpn>
{
protected override int? LayoutResource => Resource.Layout.activity_guide_vpn;
@@ -37,6 +37,8 @@ namespace System.Application.UI.Activities
this.SetSupportActionBarWithNavigationClick(binding!.toolbar, true);
Title = string.Empty;
SetOnClickListener(binding.btnDone);
}
@@ -51,8 +53,15 @@ namespace System.Application.UI.Activities
SetResult(Result.Ok, intent);
Finish();
}
await IntermediateActivity.StartAsync(intent,
NextRequestCode(), onResult: OnResult);
try
{
await IntermediateActivity.StartAsync(intent,
NextRequestCode(), onResult: OnResult);
}
catch (OperationCanceledException)
{
}
}
else
{
@@ -163,7 +163,14 @@ namespace System.Application.UI.Fragments
{
// 需要授予 VPN 权限
intent = new Intent(a, typeof(GuideVPNActivity));
intent = await IntermediateActivity.StartAsync(intent, NextRequestCode());
try
{
intent = await IntermediateActivity.StartAsync(intent, NextRequestCode());
}
catch (OperationCanceledException)
{
return;
}
}
}
@@ -171,7 +178,14 @@ namespace System.Application.UI.Fragments
{
// 当未安装证书时
intent = new Intent(a, typeof(GuideCACertActivity));
intent = await IntermediateActivity.StartAsync(intent, NextRequestCode(), onResult: OnResult);
try
{
intent = await IntermediateActivity.StartAsync(intent, NextRequestCode());
}
catch (OperationCanceledException)
{
return;
}
}
if (intent != null)
@@ -39,6 +39,7 @@ namespace AndroidX.Activity.Result
const string actualIntentExtra = "actual_intent";
const string guidExtra = "guid";
const string requestCodeExtra = "request_code";
const string resultExtra = "droid_app_result";
static readonly ConcurrentDictionary<string?, IntermediateTask> pendingTasks =
new();
@@ -173,17 +174,15 @@ namespace AndroidX.Activity.Result
public Action<Intent>? OnResult { get; }
}
const string AndroidAppResult = "AndroidAppResult";
public static Intent SetResult(Intent? data, AndroidAppResult resultCode)
{
if (data == null) data = new();
data.PutExtra(AndroidAppResult, (int)resultCode);
data.PutExtra(resultExtra, (int)resultCode);
return data;
}
public static AndroidAppResult GetResult(Intent data, int defaultValue = int.MinValue)
=> (AndroidAppResult)data.GetIntExtra(AndroidAppResult, defaultValue);
=> (AndroidAppResult)data.GetIntExtra(resultExtra, defaultValue);
}
}
}
@@ -33,7 +33,6 @@
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingEnd="@dimen/activity_horizontal_margin"
android:tint="?colorPrimary"
app:layout_constraintBottom_toTopOf="@+id/tvTitle"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toTopOf="parent"
@@ -44,57 +43,56 @@
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:text="@string/guide_export_ca_cert_title"
android:textColor="@color/text_primary"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/tvDesc1"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/ivLogo"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDesc1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:lineSpacingExtra="4sp"
android:text="@string/guide_export_ca_cert_desc1"
android:textColor="@color/text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/tvDesc2"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDesc2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:lineSpacingExtra="4sp"
android:text="@string/guide_export_ca_cert_desc2"
android:textColor="@color/text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/tvDesc3"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/tvDesc1"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDesc3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/guide_desc_margin_bottom"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:lineSpacingExtra="4sp"
android:text="@string/guide_export_ca_cert_desc3"
android:textColor="@color/text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/btnDone"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/tvDesc2"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<com.google.android.material.button.MaterialButton
@@ -105,7 +103,7 @@
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:gravity="start|center_vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:text="@string/guide_export_ca_cert_btn"
android:text="@string/guide_export_ca_cert_btn_save"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -33,7 +33,6 @@
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingEnd="@dimen/activity_horizontal_margin"
android:tint="?colorPrimary"
app:layout_constraintBottom_toTopOf="@+id/tvTitle"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toTopOf="parent"
@@ -44,85 +43,85 @@
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:text="@string/guide_how_install_ca_cert_title"
android:textColor="@color/text_primary"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/tvDesc1"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/ivLogo"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDesc1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:lineSpacingExtra="4sp"
android:text="@string/guide_how_install_ca_cert_desc1"
android:textColor="@color/text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/tvDesc2"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDesc2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:lineSpacingExtra="4sp"
android:text="@string/guide_how_install_ca_cert_desc2"
android:textColor="@color/text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/tvDesc3"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/tvDesc1"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDesc3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:lineSpacingExtra="4sp"
android:text="@string/guide_how_install_ca_cert_desc3"
android:textColor="@color/text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/tvDesc4"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/tvDesc2"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDesc4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:lineSpacingExtra="4sp"
android:text="@string/guide_how_install_ca_cert_desc4"
android:textColor="@color/text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/tvDesc5"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/tvDesc3"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDesc5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/guide_desc_margin_bottom"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:lineSpacingExtra="4sp"
android:text="@string/guide_how_install_ca_cert_desc5"
android:textColor="@color/text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/btnDone"
app:layout_constraintEnd_toStartOf="@+id/guideline_right"
app:layout_constraintStart_toStartOf="@+id/guideline_left"
app:layout_constraintTop_toBottomOf="@+id/tvDesc4"
xamarin:managedType="AndroidX.AppCompat.Widget.AppCompatTextView" />
<com.google.android.material.button.MaterialButton
@@ -8,7 +8,8 @@
<string name="guide_export_ca_cert_desc1">This certificate needs to be installed in order for Network Acceleration to work properly.</string>
<string name="guide_export_ca_cert_desc2">The certificate is unique, generated by your device, and stored locally on the device.</string>
<string name="guide_export_ca_cert_desc3">Please save the certificate. The next step describes how to install it manually.</string>
<string name="guide_export_ca_cert_btn">Save it now</string>
<string name="guide_export_ca_cert_btn_save">Save it now</string>
<string name="guide_export_ca_cert_btn_install">Install CA certificate</string>
<!-- Guide How Install CA Cert ↓ -->
<string name="guide_how_install_ca_cert_title">How do I install the certificate?</string>
<string name="guide_how_install_ca_cert_desc1">1. Open [Security] settings</string>
@@ -8,7 +8,8 @@
<string name="guide_export_ca_cert_desc1">Este certificado es necesario para que la Aceleración de Red funcione correctamente.</string>
<string name="guide_export_ca_cert_desc2">El certificado es único, generado por su dispositivo y almacenado localmente en el mismo.</string>
<string name="guide_export_ca_cert_desc3">Por favor, guarde el certificado. El siguiente paso describe cómo instalarlo manualmente.</string>
<string name="guide_export_ca_cert_btn">Guárdalo ahora</string>
<string name="guide_export_ca_cert_btn_save">Guárdalo ahora</string>
<string name="guide_export_ca_cert_btn_install">Instalación de certificados CA</string>
<!-- Guide How Install CA Cert ↓ -->
<string name="guide_how_install_ca_cert_title">¿Cómo se instala un certificado?</string>
<string name="guide_how_install_ca_cert_desc1">1. Abrir la configuración de [Seguridad]</string>
@@ -8,7 +8,8 @@
<string name="guide_export_ca_cert_desc1">Questo certificato è necessario per far funzionare correttamente Network Acceleration.</string>
<string name="guide_export_ca_cert_desc2">Il certificato è unico, generato dal tuo dispositivo e memorizzato localmente sul dispositivo.</string>
<string name="guide_export_ca_cert_desc3">Si prega di salvare il certificato. Il prossimo passo descrive come installarlo manualmente.</string>
<string name="guide_export_ca_cert_btn">Salvare ora</string>
<string name="guide_export_ca_cert_btn_save">Salvare ora</string>
<string name="guide_export_ca_cert_btn_install">Installazione di certificati CA</string>
<!-- Guide How Install CA Cert ↓ -->
<string name="guide_how_install_ca_cert_title">Come si installa un certificato?</string>
<string name="guide_how_install_ca_cert_desc1">1. Aprire le impostazioni [Sicurezza].</string>
@@ -8,7 +8,8 @@
<string name="guide_export_ca_cert_desc1">この証明書は、ネットワークアクセラレーションが正常に動作するために必要です。</string>
<string name="guide_export_ca_cert_desc2">証明書はユニークで、デバイスによって生成され、デバイスにローカルに保存されます。</string>
<string name="guide_export_ca_cert_desc3">証明書を保存してください。 次のステップでは、手動でインストールする方法を説明します。</string>
<string name="guide_export_ca_cert_btn">今すぐ保存する</string>
<string name="guide_export_ca_cert_btn_save">今すぐ保存する</string>
<string name="guide_export_ca_cert_btn_install">CA 証明書のインストール</string>
<!-- Guide How Install CA Cert ↓ -->
<string name="guide_how_install_ca_cert_title">証明書をインストールするにはどうすればよいですか?</string>
<string name="guide_how_install_ca_cert_desc1">1. [セキュリティ]設定を開く</string>
@@ -8,7 +8,8 @@
<string name="guide_export_ca_cert_desc1">네트워크가 정상적으로 작동하도록 하기 위해서 이 인증서를 설치해야 합니다.</string>
<string name="guide_export_ca_cert_desc2">인증서는 고유하며 디바이스에서 생성되어 로컬 디바이스에 저장됩니다.</string>
<string name="guide_export_ca_cert_desc3">인증서를 저장하십시오.다음 단계에서는 수동으로 설치하는 방법을 소개합니다.</string>
<string name="guide_export_ca_cert_btn">지금 저장</string>
<string name="guide_export_ca_cert_btn_save">지금 저장</string>
<string name="guide_export_ca_cert_btn_install">CA 인증서 설치</string>
<!-- Guide How Install CA Cert ↓ -->
<string name="guide_how_install_ca_cert_title">인증서는 어떻게 설치합니까?</string>
<string name="guide_how_install_ca_cert_desc1">1. [안전] 설정 열기</string>
@@ -8,7 +8,8 @@
<string name="guide_export_ca_cert_desc1">Этот сертификат необходим для правильной работы Network Acceleration.</string>
<string name="guide_export_ca_cert_desc2">Сертификат является уникальным, генерируется вашим устройством и хранится локально на устройстве.</string>
<string name="guide_export_ca_cert_desc3">Пожалуйста, сохраните сертификат. Следующий шаг описывает, как установить его вручную.</string>
<string name="guide_export_ca_cert_btn">Сохранить сейчас</string>
<string name="guide_export_ca_cert_btn_save">Сохранить сейчас</string>
<string name="guide_export_ca_cert_btn_install">Установка сертификатов ЦС</string>
<!-- Guide How Install CA Cert ↓ -->
<string name="guide_how_install_ca_cert_title">Как установить сертификат?</string>
<string name="guide_how_install_ca_cert_desc1">1. Откройте настройки [Безопасность]</string>
@@ -8,7 +8,8 @@
<string name="guide_export_ca_cert_desc1">為了使 網絡加速 正常工作,需要安裝此證書。</string>
<string name="guide_export_ca_cert_desc2">該證書是唯一的,由您的設備生成,並存儲在本地設備上。</string>
<string name="guide_export_ca_cert_desc3">請保存證書。 下一步介紹如何手動安裝它。</string>
<string name="guide_export_ca_cert_btn">現在保存它</string>
<string name="guide_export_ca_cert_btn_save">現在保存它</string>
<string name="guide_export_ca_cert_btn_install">安装 CA 證書</string>
<!-- Guide How Install CA Cert ↓ -->
<string name="guide_how_install_ca_cert_title">如何安裝證書?</string>
<string name="guide_how_install_ca_cert_desc1">1. 打開【安全】設置</string>
@@ -17,7 +17,8 @@
<string name="guide_export_ca_cert_desc1">为了使 网络加速 正常工作,需要安装此证书。</string>
<string name="guide_export_ca_cert_desc2">该证书是唯一的,由您的设备生成,并存储在本地设备上。</string>
<string name="guide_export_ca_cert_desc3">请保存证书。下一步介绍如何手动安装它。</string>
<string name="guide_export_ca_cert_btn">现在保存它</string>
<string name="guide_export_ca_cert_btn_save">现在保存它</string>
<string name="guide_export_ca_cert_btn_install">安装 CA 证书</string>
<!-- Guide How Install CA Cert ↓ -->
<string name="guide_how_install_ca_cert_title">如何安装证书?</string>
<string name="guide_how_install_ca_cert_desc1">1. 打开【安全】设置</string>
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography.X509Certificates;
namespace System.Application.Services
{
partial interface IPlatformService
{
bool IsCertificateInstalled(X509Certificate2 certificate2)
{
return true;
}
}
}
@@ -807,24 +807,36 @@ namespace System.Application.Services.Implementation
{
if (proxyServer.CertificateManager.RootCertificate == null)
if (GetCerFilePathGeneratedWhenNoFileExists() == null) return false;
return IsCertificateInstalled(proxyServer.CertificateManager.RootCertificate);
return IsCertificateInstalled(proxyServer.CertificateManager.RootCertificate, usePlatformCheck: true);
}
}
public bool IsCertificateInstalled(X509Certificate2? certificate2)
public bool IsCertificateInstalled(X509Certificate2? certificate2) => IsCertificateInstalled(certificate2, false);
public bool IsCertificateInstalled(X509Certificate2? certificate2, bool usePlatformCheck)
{
if (certificate2 == null)
return false;
if (certificate2.NotAfter <= DateTime.Now)
return false;
if (OperatingSystem2.IsLinux)
if (!OperatingSystem2.IsAndroid && OperatingSystem2.IsLinux)
{
return true;
}
using var store = new X509Store(OperatingSystem2.IsMacOS ? StoreName.My : StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
return store.Certificates.Contains(certificate2);
bool result;
if (usePlatformCheck && OperatingSystem2.IsAndroid)
{
result = platformService.IsCertificateInstalled(certificate2);
}
else
{
using var store = new X509Store(OperatingSystem2.IsMacOS ? StoreName.My : StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
result = store.Certificates.Contains(certificate2);
}
return result;
}
void Dispose(bool disposing)