Skip to content

Commit

Permalink
create AndroidAppMainActivityHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
trudyhood committed Feb 28, 2024
1 parent 4128e71 commit 6f43d6b
Show file tree
Hide file tree
Showing 37 changed files with 324 additions and 216 deletions.
4 changes: 2 additions & 2 deletions Pub/PubVersion.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Version": "3.3.470",
"BumpTime": "2024-02-16T20:19:54.5087389Z",
"Version": "3.3.471",
"BumpTime": "2024-02-28T07:21:16.2620690Z",
"Prerelease": false,
"DeprecatedVersion": "3.0.416"
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="VpnHood.Client" Version="3.3.456" />
<PackageReference Include="VpnHood.Client.Device.WinDivert" Version="3.3.456" />
<PackageReference Include="VpnHood.Client" Version="3.3.470" />
<PackageReference Include="VpnHood.Client.Device.WinDivert" Version="3.3.470" />
</ItemGroup>

</Project>
8 changes: 4 additions & 4 deletions Tests/VpnHood.Test/VpnHood.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
<ItemGroup>
<PackageReference Include="EmbedIO" Version="3.5.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.2.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.2.2" />
<PackageReference Include="coverlet.collector" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
2 changes: 1 addition & 1 deletion Tests/VpnHood.UdpTrafficTest/VpnHood.UdpTrafficTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="VpnHood.Common" Version="3.3.456" />
<PackageReference Include="VpnHood.Common" Version="3.3.470" />
</ItemGroup>

</Project>
121 changes: 17 additions & 104 deletions VpnHood.Client.App.Android.Common/Activities/AndroidAppMainActivity.cs
Original file line number Diff line number Diff line change
@@ -1,135 +1,48 @@
using Android;
using Android.Content;
using Android.Content;
using Android.Content.PM;
using Android.Runtime;
using VpnHood.Client.App.Abstractions;
using VpnHood.Client.Device.Droid;
using VpnHood.Client.Device.Droid.Utils;
using Android.Views;

namespace VpnHood.Client.App.Droid.Common.Activities;

public abstract class AndroidAppMainActivity : ActivityEvent
public abstract class AndroidAppMainActivity : Activity
{
private TaskCompletionSource<Permission>? _requestPostNotificationsCompletionTask;
protected const int RequestPostNotificationId = 11;
protected AndroidDevice VpnDevice => AndroidDevice.Current ?? throw new InvalidOperationException($"{nameof(AndroidDevice)} has not been initialized.");
protected string[] AccessKeySchemes { get; set; } = Array.Empty<string>();
protected string[] AccessKeyMimes { get; set; } = Array.Empty<string>();
protected abstract IAppUpdaterService? CreateAppUpdaterService();
private AndroidAppMainActivityHandler _mainActivityHandler = default!;
protected abstract AndroidAppMainActivityHandler CreateMainActivityHandler();

protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
VpnDevice.Prepare(this);

// process intent
ProcessIntent(Intent);
}

protected async Task RequestFeatures()
{
// request for adding tile
if (!VpnHoodApp.Instance.Settings.IsQuickLaunchRequested &&
OperatingSystem.IsAndroidVersionAtLeast(33))
{
VpnHoodApp.Instance.Settings.IsQuickLaunchRequested = true;
VpnHoodApp.Instance.Settings.Save();
await QuickLaunchTileService.RequestAddTile(this);
}

// request for notification
if (OperatingSystem.IsAndroidVersionAtLeast(33) && CheckSelfPermission(Manifest.Permission.PostNotifications) != Permission.Granted)
{
_requestPostNotificationsCompletionTask = new TaskCompletionSource<Permission>();
RequestPermissions([Manifest.Permission.PostNotifications], RequestPostNotificationId);
await _requestPostNotificationsCompletionTask.Task;
}

// Check for update
var appUpdaterService = CreateAppUpdaterService();
VpnHoodApp.Instance.AppUpdaterService = appUpdaterService;
if (VpnHoodApp.Instance.VersionCheckRequired && appUpdaterService != null && await appUpdaterService.Update())
VpnHoodApp.Instance.VersionCheckPostpone(); // postpone check if check succeeded
_mainActivityHandler = CreateMainActivityHandler();
_mainActivityHandler.OnCreate(savedInstanceState);
}

protected override void OnNewIntent(Intent? intent)
{
if (!ProcessIntent(intent))
if (!_mainActivityHandler.OnNewIntent(intent))
base.OnNewIntent(intent);
}

private bool ProcessIntent(Intent? intent)
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
if (intent?.Data == null || ContentResolver == null)
return false;

// try to add the access key
try
{
var uri = intent.Data;
if (AccessKeySchemes.Contains(uri.Scheme, StringComparer.OrdinalIgnoreCase))
{
ImportAccessKey(uri.ToString()!);
return true;
}

// check mime
var mimeType = ContentResolver.GetType(uri);
if (!AccessKeyMimes.Contains(mimeType, StringComparer.OrdinalIgnoreCase))
{
Toast.MakeText(this, VpnHoodApp.Instance.Resources.Strings.MsgUnsupportedContent, ToastLength.Long)?.Show();
return false;
}

// open stream
using var inputStream = ContentResolver.OpenInputStream(uri);
if (inputStream == null)
throw new Exception("Can not open the intent file stream.");

// read string into buffer maximum 5k
var buffer = new byte[5 * 1024];
var length = inputStream.Read(buffer);
using var memoryStream = new MemoryStream(buffer, 0, length);
var streamReader = new StreamReader(memoryStream);
var accessKey = streamReader.ReadToEnd();

ImportAccessKey(accessKey);
}
catch
{
Toast.MakeText(this, VpnHoodApp.Instance.Resources.Strings.MsgCantReadAccessKey, ToastLength.Long)?.Show();
}

return true;
_mainActivityHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}

protected void ImportAccessKey(string accessKey)
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent? data)
{
var profiles = VpnHoodApp.Instance.ClientProfileService.List();
var profile = VpnHoodApp.Instance.ClientProfileService.ImportAccessKey(accessKey).ToInfo();
_ = VpnHoodApp.Instance.Disconnect(true);
VpnHoodApp.Instance.UserSettings.DefaultClientProfileId = profile.ClientProfileId;

var isNew = profiles.Any(x => x.ClientProfileId == profile.ClientProfileId);
var message = isNew
? string.Format(VpnHoodApp.Instance.Resources.Strings.MsgAccessKeyAdded, profile.ClientProfileName)
: string.Format(VpnHoodApp.Instance.Resources.Strings.MsgAccessKeyUpdated, profile.ClientProfileName);

Toast.MakeText(this, message, ToastLength.Long)?.Show();
_mainActivityHandler.OnActivityResult(requestCode, resultCode, data);
base.OnActivityResult(requestCode, resultCode, data);
}

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent? e)
{
var postNotificationsIndex = Array.IndexOf(permissions, Manifest.Permission.PostNotifications);
if (postNotificationsIndex != -1 && _requestPostNotificationsCompletionTask != null)
_requestPostNotificationsCompletionTask.TrySetResult(grantResults[postNotificationsIndex]);

base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
return _mainActivityHandler.OnKeyDown(keyCode, e) || base.OnKeyDown(keyCode, e);
}

protected override void OnDestroy()
{
VpnHoodApp.Instance.AppUpdaterService = null;
_mainActivityHandler.OnDestroy();
base.OnDestroy();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using Android;
using Android.Content;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using VpnHood.Client.App.Abstractions;
using VpnHood.Client.Device.Droid;
using VpnHood.Client.Device.Droid.Utils;

namespace VpnHood.Client.App.Droid.Common.Activities;

public class AndroidAppMainActivityHandler(
Activity activity,
AndroidMainActivityOptions options)
: IActivityEvent
{
private TaskCompletionSource<Permission>? _requestPostNotificationsCompletionTask;
private readonly string[] _accessKeySchemes = options.AccessKeySchemes;
private readonly string[] _accessKeyMimes = options.AccessKeySchemes;
private readonly IAppUpdaterService? _appUpdaterService = options.AppUpdaterService;
private const int RequestPostNotificationId = 11;
protected AndroidDevice VpnDevice => AndroidDevice.Current ?? throw new InvalidOperationException($"{nameof(AndroidDevice)} has not been initialized.");
protected Activity Activity { get; } = activity;
public event EventHandler<ActivityResultEventArgs>? OnActivityResultEvent;
public event EventHandler? OnDestroyEvent;

public virtual void OnCreate(Bundle? savedInstanceState)
{
VpnDevice.Prepare(Activity, this);

// process intent
ProcessIntent(Activity.Intent);
}

protected async Task RequestFeatures()
{
// request for adding tile
if (!VpnHoodApp.Instance.Settings.IsQuickLaunchRequested &&
OperatingSystem.IsAndroidVersionAtLeast(33))
{
VpnHoodApp.Instance.Settings.IsQuickLaunchRequested = true;
VpnHoodApp.Instance.Settings.Save();
await QuickLaunchTileService.RequestAddTile(Activity);
}

// request for notification
if (OperatingSystem.IsAndroidVersionAtLeast(33) && Activity.CheckSelfPermission(Manifest.Permission.PostNotifications) != Permission.Granted)
{
_requestPostNotificationsCompletionTask = new TaskCompletionSource<Permission>();
Activity.RequestPermissions([Manifest.Permission.PostNotifications], RequestPostNotificationId);
await _requestPostNotificationsCompletionTask.Task;
}

// Check for update
VpnHoodApp.Instance.AppUpdaterService = _appUpdaterService;
if (VpnHoodApp.Instance.VersionCheckRequired && _appUpdaterService != null && await _appUpdaterService.Update())
VpnHoodApp.Instance.VersionCheckPostpone(); // postpone check if check succeeded
}

public bool OnNewIntent(Intent? intent)
{
return ProcessIntent(intent);
}

private bool ProcessIntent(Intent? intent)
{
if (intent?.Data == null || Activity.ContentResolver == null)
return false;

// try to add the access key
try
{
var uri = intent.Data;
if (_accessKeySchemes.Contains(uri.Scheme, StringComparer.OrdinalIgnoreCase))
{
ImportAccessKey(uri.ToString()!);
return true;
}

// check mime
var mimeType = Activity.ContentResolver.GetType(uri);
if (!_accessKeyMimes.Contains(mimeType, StringComparer.OrdinalIgnoreCase))
{
Toast.MakeText(Activity, VpnHoodApp.Instance.Resources.Strings.MsgUnsupportedContent, ToastLength.Long)?.Show();
return false;
}

// open stream
using var inputStream = Activity.ContentResolver.OpenInputStream(uri);
if (inputStream == null)
throw new Exception("Can not open the intent file stream.");

// read string into buffer maximum 5k
var buffer = new byte[5 * 1024];
var length = inputStream.Read(buffer);
using var memoryStream = new MemoryStream(buffer, 0, length);
var streamReader = new StreamReader(memoryStream);
var accessKey = streamReader.ReadToEnd();

ImportAccessKey(accessKey);
}
catch
{
Toast.MakeText(Activity, VpnHoodApp.Instance.Resources.Strings.MsgCantReadAccessKey, ToastLength.Long)?.Show();
}

return true;
}

protected void ImportAccessKey(string accessKey)
{
var profiles = VpnHoodApp.Instance.ClientProfileService.List();
var profile = VpnHoodApp.Instance.ClientProfileService.ImportAccessKey(accessKey).ToInfo();
_ = VpnHoodApp.Instance.Disconnect(true);
VpnHoodApp.Instance.UserSettings.DefaultClientProfileId = profile.ClientProfileId;

var isNew = profiles.Any(x => x.ClientProfileId == profile.ClientProfileId);
var message = isNew
? string.Format(VpnHoodApp.Instance.Resources.Strings.MsgAccessKeyAdded, profile.ClientProfileName)
: string.Format(VpnHoodApp.Instance.Resources.Strings.MsgAccessKeyUpdated, profile.ClientProfileName);

Toast.MakeText(Activity, message, ToastLength.Long)?.Show();
}

public void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
var postNotificationsIndex = Array.IndexOf(permissions, Manifest.Permission.PostNotifications);
if (postNotificationsIndex != -1 && _requestPostNotificationsCompletionTask != null)
_requestPostNotificationsCompletionTask.TrySetResult(grantResults[postNotificationsIndex]);
}

public virtual bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent? e)
{
return false;
}

public virtual void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent? data)
{
OnActivityResultEvent?.Invoke(this, new ActivityResultEventArgs
{
RequestCode = requestCode,
ResultCode = resultCode,
Data = data
});
}

public virtual void OnDestroy()
{
OnDestroyEvent?.Invoke(this, EventArgs.Empty);
VpnHoodApp.Instance.AppUpdaterService = null;
}
}
Loading

0 comments on commit 6f43d6b

Please sign in to comment.