зеркало из https://github.com/DeGsoft/maui-linux.git
544 строки
16 KiB
C#
544 строки
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.IsolatedStorage;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Net.Http;
|
|
using System.Reflection;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Android.App;
|
|
using Android.Content;
|
|
using Android.Content.Res;
|
|
using Android.OS;
|
|
using Android.Util;
|
|
using Android.Views;
|
|
using Xamarin.Forms.Internals;
|
|
using Xamarin.Forms.Platform.Android;
|
|
using Resource = Android.Resource;
|
|
using Trace = System.Diagnostics.Trace;
|
|
|
|
namespace Xamarin.Forms
|
|
{
|
|
public static class Forms
|
|
{
|
|
const int TabletCrossover = 600;
|
|
|
|
static bool? s_supportsProgress;
|
|
|
|
static bool? s_isLollipopOrNewer;
|
|
|
|
public static Context Context { get; internal set; }
|
|
|
|
public static bool IsInitialized { get; private set; }
|
|
|
|
internal static bool IsLollipopOrNewer
|
|
{
|
|
get
|
|
{
|
|
if (!s_isLollipopOrNewer.HasValue)
|
|
s_isLollipopOrNewer = (int)Build.VERSION.SdkInt >= 21;
|
|
return s_isLollipopOrNewer.Value;
|
|
}
|
|
}
|
|
|
|
internal static bool SupportsProgress
|
|
{
|
|
get
|
|
{
|
|
var activity = Context as Activity;
|
|
if (!s_supportsProgress.HasValue)
|
|
{
|
|
int progressCircularId = Context.Resources.GetIdentifier("progress_circular", "id", "android");
|
|
if (progressCircularId > 0 && activity != null)
|
|
s_supportsProgress = activity.FindViewById(progressCircularId) != null;
|
|
else
|
|
s_supportsProgress = true;
|
|
}
|
|
return s_supportsProgress.Value;
|
|
}
|
|
}
|
|
|
|
internal static AndroidTitleBarVisibility TitleBarVisibility { get; set; }
|
|
|
|
// Provide backwards compat for Forms.Init and AndroidActivity
|
|
// Why is bundle a param if never used?
|
|
public static void Init(Context activity, Bundle bundle)
|
|
{
|
|
Assembly resourceAssembly = Assembly.GetCallingAssembly();
|
|
SetupInit(activity, resourceAssembly);
|
|
}
|
|
|
|
public static void Init(Context activity, Bundle bundle, Assembly resourceAssembly)
|
|
{
|
|
SetupInit(activity, resourceAssembly);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets title bar visibility programmatically. Must be called after Xamarin.Forms.Forms.Init() method
|
|
/// </summary>
|
|
/// <param name="visibility">Title bar visibility enum</param>
|
|
public static void SetTitleBarVisibility(AndroidTitleBarVisibility visibility)
|
|
{
|
|
if((Activity)Context == null)
|
|
throw new NullReferenceException("Must be called after Xamarin.Forms.Forms.Init() method");
|
|
|
|
TitleBarVisibility = visibility;
|
|
|
|
if (TitleBarVisibility == AndroidTitleBarVisibility.Never)
|
|
{
|
|
if (!((Activity)Context).Window.Attributes.Flags.HasFlag(WindowManagerFlags.Fullscreen))
|
|
((Activity)Context).Window.AddFlags(WindowManagerFlags.Fullscreen);
|
|
}
|
|
else
|
|
{
|
|
if (((Activity)Context).Window.Attributes.Flags.HasFlag(WindowManagerFlags.Fullscreen))
|
|
((Activity)Context).Window.ClearFlags(WindowManagerFlags.Fullscreen);
|
|
}
|
|
}
|
|
|
|
public static event EventHandler<ViewInitializedEventArgs> ViewInitialized;
|
|
|
|
internal static void SendViewInitialized(this VisualElement self, global::Android.Views.View nativeView)
|
|
{
|
|
EventHandler<ViewInitializedEventArgs> viewInitialized = ViewInitialized;
|
|
if (viewInitialized != null)
|
|
viewInitialized(self, new ViewInitializedEventArgs { View = self, NativeView = nativeView });
|
|
}
|
|
|
|
static void SetupInit(Context activity, Assembly resourceAssembly)
|
|
{
|
|
Context = activity;
|
|
|
|
ResourceManager.Init(resourceAssembly);
|
|
|
|
Color.Accent = GetAccentColor();
|
|
|
|
if (!IsInitialized)
|
|
Log.Listeners.Add(new DelegateLogListener((c, m) => Trace.WriteLine(m, c)));
|
|
|
|
Device.OS = TargetPlatform.Android;
|
|
Device.PlatformServices = new AndroidPlatformServices();
|
|
|
|
// use field and not property to avoid exception in getter
|
|
if (Device.info != null)
|
|
{
|
|
((AndroidDeviceInfo)Device.info).Dispose();
|
|
Device.info = null;
|
|
}
|
|
|
|
// probably could be done in a better way
|
|
var deviceInfoProvider = activity as IDeviceInfoProvider;
|
|
if (deviceInfoProvider != null)
|
|
Device.Info = new AndroidDeviceInfo(deviceInfoProvider);
|
|
|
|
var ticker = Ticker.Default as AndroidTicker;
|
|
if (ticker != null)
|
|
ticker.Dispose();
|
|
Ticker.Default = new AndroidTicker();
|
|
|
|
if (!IsInitialized)
|
|
{
|
|
Registrar.RegisterAll(new[] { typeof(ExportRendererAttribute), typeof(ExportCellAttribute), typeof(ExportImageSourceHandlerAttribute) });
|
|
}
|
|
|
|
int minWidthDp = Context.Resources.Configuration.SmallestScreenWidthDp;
|
|
|
|
Device.Idiom = minWidthDp >= TabletCrossover ? TargetIdiom.Tablet : TargetIdiom.Phone;
|
|
|
|
if (ExpressionSearch.Default == null)
|
|
ExpressionSearch.Default = new AndroidExpressionSearch();
|
|
|
|
IsInitialized = true;
|
|
}
|
|
|
|
static Color GetAccentColor()
|
|
{
|
|
Color rc;
|
|
using (var value = new TypedValue())
|
|
{
|
|
if (Context.Theme.ResolveAttribute(global::Android.Resource.Attribute.ColorAccent, value, true)) // Android 5.0+
|
|
{
|
|
rc = Color.FromUint((uint)value.Data);
|
|
}
|
|
else if(Context.Theme.ResolveAttribute(Context.Resources.GetIdentifier("colorAccent", "attr", Context.PackageName), value, true)) // < Android 5.0
|
|
{
|
|
rc = Color.FromUint((uint)value.Data);
|
|
}
|
|
else // fallback to old code if nothing works (don't know if that ever happens)
|
|
{
|
|
// Detect if legacy device and use appropriate accent color
|
|
// Hardcoded because could not get color from the theme drawable
|
|
var sdkVersion = (int)Build.VERSION.SdkInt;
|
|
if (sdkVersion <= 10)
|
|
{
|
|
// legacy theme button pressed color
|
|
rc = Color.FromHex("#fffeaa0c");
|
|
}
|
|
else
|
|
{
|
|
// Holo dark light blue
|
|
rc = Color.FromHex("#ff33b5e5");
|
|
}
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
class AndroidDeviceInfo : DeviceInfo
|
|
{
|
|
readonly IDeviceInfoProvider _formsActivity;
|
|
readonly Size _pixelScreenSize;
|
|
readonly double _scalingFactor;
|
|
|
|
Orientation _previousOrientation = Orientation.Undefined;
|
|
|
|
public AndroidDeviceInfo(IDeviceInfoProvider formsActivity)
|
|
{
|
|
_formsActivity = formsActivity;
|
|
CheckOrientationChanged(_formsActivity.Resources.Configuration.Orientation);
|
|
formsActivity.ConfigurationChanged += ConfigurationChanged;
|
|
|
|
using (DisplayMetrics display = formsActivity.Resources.DisplayMetrics)
|
|
{
|
|
_scalingFactor = display.Density;
|
|
_pixelScreenSize = new Size(display.WidthPixels, display.HeightPixels);
|
|
ScaledScreenSize = new Size(_pixelScreenSize.Width / _scalingFactor, _pixelScreenSize.Width / _scalingFactor);
|
|
}
|
|
}
|
|
|
|
public override Size PixelScreenSize
|
|
{
|
|
get { return _pixelScreenSize; }
|
|
}
|
|
|
|
public override Size ScaledScreenSize { get; }
|
|
|
|
public override double ScalingFactor
|
|
{
|
|
get { return _scalingFactor; }
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
_formsActivity.ConfigurationChanged -= ConfigurationChanged;
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
void CheckOrientationChanged(Orientation orientation)
|
|
{
|
|
if (!_previousOrientation.Equals(orientation))
|
|
CurrentOrientation = orientation.ToDeviceOrientation();
|
|
|
|
_previousOrientation = orientation;
|
|
}
|
|
|
|
void ConfigurationChanged(object sender, EventArgs e)
|
|
{
|
|
CheckOrientationChanged(_formsActivity.Resources.Configuration.Orientation);
|
|
}
|
|
}
|
|
|
|
class AndroidExpressionSearch : ExpressionVisitor, IExpressionSearch
|
|
{
|
|
List<object> _results;
|
|
Type _targetType;
|
|
|
|
public List<T> FindObjects<T>(Expression expression) where T : class
|
|
{
|
|
_results = new List<object>();
|
|
_targetType = typeof(T);
|
|
Visit(expression);
|
|
return _results.Select(o => o as T).ToList();
|
|
}
|
|
|
|
protected override Expression VisitMember(MemberExpression node)
|
|
{
|
|
if (node.Expression is ConstantExpression && node.Member is FieldInfo)
|
|
{
|
|
object container = ((ConstantExpression)node.Expression).Value;
|
|
object value = ((FieldInfo)node.Member).GetValue(container);
|
|
|
|
if (_targetType.IsInstanceOfType(value))
|
|
_results.Add(value);
|
|
}
|
|
return base.VisitMember(node);
|
|
}
|
|
}
|
|
|
|
class AndroidPlatformServices : IPlatformServices
|
|
{
|
|
static readonly MD5CryptoServiceProvider Checksum = new MD5CryptoServiceProvider();
|
|
double _buttonDefaultSize;
|
|
double _editTextDefaultSize;
|
|
double _labelDefaultSize;
|
|
double _largeSize;
|
|
double _mediumSize;
|
|
|
|
double _microSize;
|
|
double _smallSize;
|
|
|
|
static Handler s_handler;
|
|
|
|
public void BeginInvokeOnMainThread(Action action)
|
|
{
|
|
if (s_handler == null || s_handler.Looper != Looper.MainLooper)
|
|
{
|
|
s_handler = new Handler(Looper.MainLooper);
|
|
}
|
|
|
|
s_handler.Post(action);
|
|
}
|
|
|
|
public Ticker CreateTicker()
|
|
{
|
|
return new AndroidTicker();
|
|
}
|
|
|
|
public Assembly[] GetAssemblies()
|
|
{
|
|
return AppDomain.CurrentDomain.GetAssemblies();
|
|
}
|
|
|
|
public string GetMD5Hash(string input)
|
|
{
|
|
byte[] bytes = Checksum.ComputeHash(Encoding.UTF8.GetBytes(input));
|
|
var ret = new char[32];
|
|
for (var i = 0; i < 16; i++)
|
|
{
|
|
ret[i * 2] = (char)Hex(bytes[i] >> 4);
|
|
ret[i * 2 + 1] = (char)Hex(bytes[i] & 0xf);
|
|
}
|
|
return new string(ret);
|
|
}
|
|
|
|
public double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes)
|
|
{
|
|
if (_smallSize == 0)
|
|
{
|
|
_smallSize = ConvertTextAppearanceToSize(Resource.Attribute.TextAppearanceSmall, Resource.Style.TextAppearanceDeviceDefaultSmall, 12);
|
|
_mediumSize = ConvertTextAppearanceToSize(Resource.Attribute.TextAppearanceMedium, Resource.Style.TextAppearanceDeviceDefaultMedium, 14);
|
|
_largeSize = ConvertTextAppearanceToSize(Resource.Attribute.TextAppearanceLarge, Resource.Style.TextAppearanceDeviceDefaultLarge, 18);
|
|
_buttonDefaultSize = ConvertTextAppearanceToSize(Resource.Attribute.TextAppearanceButton, Resource.Style.TextAppearanceDeviceDefaultWidgetButton, 14);
|
|
_editTextDefaultSize = ConvertTextAppearanceToSize(Resource.Style.TextAppearanceWidgetEditText, Resource.Style.TextAppearanceDeviceDefaultWidgetEditText, 18);
|
|
_labelDefaultSize = _smallSize;
|
|
// as decreed by the android docs, ALL HAIL THE ANDROID DOCS, ALL GLORY TO THE DOCS, PRAISE HYPNOTOAD
|
|
_microSize = Math.Max(1, _smallSize - (_mediumSize - _smallSize));
|
|
}
|
|
|
|
if (useOldSizes)
|
|
{
|
|
switch (size)
|
|
{
|
|
case NamedSize.Default:
|
|
if (typeof(Button).IsAssignableFrom(targetElementType))
|
|
return _buttonDefaultSize;
|
|
if (typeof(Label).IsAssignableFrom(targetElementType))
|
|
return _labelDefaultSize;
|
|
if (typeof(Editor).IsAssignableFrom(targetElementType) || typeof(Entry).IsAssignableFrom(targetElementType) || typeof(SearchBar).IsAssignableFrom(targetElementType))
|
|
return _editTextDefaultSize;
|
|
return 14;
|
|
case NamedSize.Micro:
|
|
return 10;
|
|
case NamedSize.Small:
|
|
return 12;
|
|
case NamedSize.Medium:
|
|
return 14;
|
|
case NamedSize.Large:
|
|
return 18;
|
|
default:
|
|
throw new ArgumentOutOfRangeException("size");
|
|
}
|
|
}
|
|
switch (size)
|
|
{
|
|
case NamedSize.Default:
|
|
if (typeof(Button).IsAssignableFrom(targetElementType))
|
|
return _buttonDefaultSize;
|
|
if (typeof(Label).IsAssignableFrom(targetElementType))
|
|
return _labelDefaultSize;
|
|
if (typeof(Editor).IsAssignableFrom(targetElementType) || typeof(Entry).IsAssignableFrom(targetElementType))
|
|
return _editTextDefaultSize;
|
|
return _mediumSize;
|
|
case NamedSize.Micro:
|
|
return _microSize;
|
|
case NamedSize.Small:
|
|
return _smallSize;
|
|
case NamedSize.Medium:
|
|
return _mediumSize;
|
|
case NamedSize.Large:
|
|
return _largeSize;
|
|
default:
|
|
throw new ArgumentOutOfRangeException("size");
|
|
}
|
|
}
|
|
|
|
public async Task<Stream> GetStreamAsync(Uri uri, CancellationToken cancellationToken)
|
|
{
|
|
using (var client = new HttpClient())
|
|
using (HttpResponseMessage response = await client.GetAsync(uri, cancellationToken))
|
|
return await response.Content.ReadAsStreamAsync();
|
|
}
|
|
|
|
public IIsolatedStorageFile GetUserStoreForApplication()
|
|
{
|
|
return new _IsolatedStorageFile(IsolatedStorageFile.GetUserStoreForApplication());
|
|
}
|
|
|
|
public bool IsInvokeRequired
|
|
{
|
|
get
|
|
{
|
|
return Looper.MainLooper != Looper.MyLooper();
|
|
}
|
|
}
|
|
|
|
public void OpenUriAction(Uri uri)
|
|
{
|
|
global::Android.Net.Uri aUri = global::Android.Net.Uri.Parse(uri.ToString());
|
|
var intent = new Intent(Intent.ActionView, aUri);
|
|
Context.StartActivity(intent);
|
|
}
|
|
|
|
public void StartTimer(TimeSpan interval, Func<bool> callback)
|
|
{
|
|
Timer timer = null;
|
|
bool invoking = false;
|
|
TimerCallback onTimeout = o =>
|
|
{
|
|
if (!invoking)
|
|
{
|
|
invoking = true;
|
|
BeginInvokeOnMainThread(() =>
|
|
{
|
|
if (!callback())
|
|
timer.Dispose();
|
|
invoking = false;
|
|
});
|
|
}
|
|
};
|
|
timer = new Timer(onTimeout, null, interval, interval);
|
|
}
|
|
|
|
double ConvertTextAppearanceToSize(int themeDefault, int deviceDefault, double defaultValue)
|
|
{
|
|
double myValue;
|
|
|
|
if (TryGetTextAppearance(themeDefault, out myValue))
|
|
return myValue;
|
|
if (TryGetTextAppearance(deviceDefault, out myValue))
|
|
return myValue;
|
|
return defaultValue;
|
|
}
|
|
|
|
static int Hex(int v)
|
|
{
|
|
if (v < 10)
|
|
return '0' + v;
|
|
return 'a' + v - 10;
|
|
}
|
|
|
|
static bool TryGetTextAppearance(int appearance, out double val)
|
|
{
|
|
val = 0;
|
|
try
|
|
{
|
|
using (var value = new TypedValue())
|
|
{
|
|
if (Context.Theme.ResolveAttribute(appearance, value, true))
|
|
{
|
|
var textSizeAttr = new[] { Resource.Attribute.TextSize };
|
|
const int indexOfAttrTextSize = 0;
|
|
using (TypedArray array = Context.ObtainStyledAttributes(value.Data, textSizeAttr))
|
|
{
|
|
val = Context.FromPixels(array.GetDimensionPixelSize(indexOfAttrTextSize, -1));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warning("Xamarin.Forms.Platform.Android.AndroidPlatformServices", "Error retrieving text appearance: {0}", ex);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public class _Timer : ITimer
|
|
{
|
|
readonly Timer _timer;
|
|
|
|
public _Timer(Timer timer)
|
|
{
|
|
_timer = timer;
|
|
}
|
|
|
|
public void Change(int dueTime, int period)
|
|
{
|
|
_timer.Change(dueTime, period);
|
|
}
|
|
|
|
public void Change(long dueTime, long period)
|
|
{
|
|
_timer.Change(dueTime, period);
|
|
}
|
|
|
|
public void Change(TimeSpan dueTime, TimeSpan period)
|
|
{
|
|
_timer.Change(dueTime, period);
|
|
}
|
|
|
|
public void Change(uint dueTime, uint period)
|
|
{
|
|
_timer.Change(dueTime, period);
|
|
}
|
|
}
|
|
|
|
public class _IsolatedStorageFile : IIsolatedStorageFile
|
|
{
|
|
readonly IsolatedStorageFile _isolatedStorageFile;
|
|
|
|
public _IsolatedStorageFile(IsolatedStorageFile isolatedStorageFile)
|
|
{
|
|
_isolatedStorageFile = isolatedStorageFile;
|
|
}
|
|
|
|
public Task CreateDirectoryAsync(string path)
|
|
{
|
|
_isolatedStorageFile.CreateDirectory(path);
|
|
return Task.FromResult(true);
|
|
}
|
|
|
|
public Task<bool> GetDirectoryExistsAsync(string path)
|
|
{
|
|
return Task.FromResult(_isolatedStorageFile.DirectoryExists(path));
|
|
}
|
|
|
|
public Task<bool> GetFileExistsAsync(string path)
|
|
{
|
|
return Task.FromResult(_isolatedStorageFile.FileExists(path));
|
|
}
|
|
|
|
public Task<DateTimeOffset> GetLastWriteTimeAsync(string path)
|
|
{
|
|
return Task.FromResult(_isolatedStorageFile.GetLastWriteTime(path));
|
|
}
|
|
|
|
public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access)
|
|
{
|
|
Stream stream = _isolatedStorageFile.OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access);
|
|
return Task.FromResult(stream);
|
|
}
|
|
|
|
public Task<Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share)
|
|
{
|
|
Stream stream = _isolatedStorageFile.OpenFile(path, (System.IO.FileMode)mode, (System.IO.FileAccess)access, (System.IO.FileShare)share);
|
|
return Task.FromResult(stream);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |