| File: Accelerometer\Accelerometer.shared.cs | Web Access |
| Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials) |
#nullable enable using System; using System.Numerics; using Microsoft.Maui.ApplicationModel; namespace Microsoft.Maui.Devices.Sensors { /// <summary> /// Accelerometer data of the acceleration of the device in three-dimensional space. /// </summary> public interface IAccelerometer { /// <summary> /// Occurs when the sensor reading changes. /// </summary> event EventHandler<AccelerometerChangedEventArgs>? ReadingChanged; /// <summary> /// Occurs when the accelerometer detects that the device has been shaken. /// </summary> event EventHandler? ShakeDetected; /// <summary> /// Gets a value indicating whether reading the accelerometer is supported on this device. /// </summary> bool IsSupported { get; } /// <summary> /// Gets a value indicating whether the accelerometer is being monitored. /// </summary> bool IsMonitoring { get; } /// <summary> /// Start monitoring for changes to accelerometer. /// </summary> /// <remarks> /// Will throw <see cref="FeatureNotSupportedException"/> if <see cref="IsSupported"/> is <see langword="false"/>. /// Will throw <see cref="InvalidOperationException"/> if <see cref="IsMonitoring"/> is <see langword="true"/>.</remarks> /// <param name="sensorSpeed">Speed to monitor the sensor.</param> void Start(SensorSpeed sensorSpeed); /// <summary> /// Stop monitoring for changes to accelerometer. /// </summary> void Stop(); } /// <summary> /// Accelerometer data of the acceleration of the device in three dimensional space. /// </summary> public static class Accelerometer { /// <summary> /// Occurs when the accelerometer reading changes. /// </summary> public static event EventHandler<AccelerometerChangedEventArgs> ReadingChanged { add => Default.ReadingChanged += value; remove => Default.ReadingChanged -= value; } /// <summary> /// Occurs when the accelerometer detects that the device has been shaken. /// </summary> public static event EventHandler ShakeDetected { add => Default.ShakeDetected += value; remove => Default.ShakeDetected -= value; } /// <summary> /// Gets a value indicating whether reading the accelerometer is supported on this device. /// </summary> public static bool IsSupported => Default.IsSupported; /// <summary> /// Gets a value indicating whether the accelerometer is being monitored. /// </summary> public static bool IsMonitoring => Default.IsMonitoring; /// <summary> /// Start monitoring for changes to accelerometer. /// </summary> /// <remarks>Will throw <see cref="FeatureNotSupportedException"/> if not supported on device. Will throw <see cref="ArgumentNullException"/> if handler is null.</remarks> /// <param name="sensorSpeed">Speed to monitor the sensor.</param> public static void Start(SensorSpeed sensorSpeed) => Default.Start(sensorSpeed); /// <summary> /// Stop monitoring for changes to accelerometer. /// </summary> public static void Stop() => Default.Stop(); static IAccelerometer? defaultImplementation; /// <summary> /// Provides the default implementation for static usage of this API. /// </summary> public static IAccelerometer Default => defaultImplementation ??= new AccelerometerImplementation(); internal static void SetDefault(IAccelerometer? implementation) => defaultImplementation = implementation; } /// <summary> /// Event arguments containing the current reading of <see cref="IAccelerometer"/>. /// </summary> public class AccelerometerChangedEventArgs : EventArgs { /// <summary> /// Public constructor that takes in a reading for event arguments. /// </summary> /// <param name="reading">The accelerometer data reading.</param> public AccelerometerChangedEventArgs(AccelerometerData reading) => Reading = reading; /// <summary> /// The current values of accelerometer. /// </summary> public AccelerometerData Reading { get; } } /// <summary> /// Data representing the devices' three accelerometers. /// </summary> public readonly struct AccelerometerData : IEquatable<AccelerometerData> { /// <summary> /// Public constructor for accelerometer data. /// </summary> /// <param name="x">X data</param> /// <param name="y">Y data</param> /// <param name="z">Z data</param> public AccelerometerData(double x, double y, double z) : this((float)x, (float)y, (float)z) { } /// <summary> /// Public constructor for accelerometer data. /// </summary> /// <param name="x">X data</param> /// <param name="y">Y data</param> /// <param name="z">Z data</param> public AccelerometerData(float x, float y, float z) => Acceleration = new Vector3(x, y, z); /// <summary> /// Gets the acceleration vector in G's (gravitational force). /// </summary> public Vector3 Acceleration { get; } /// <inheritdoc cref="IEquatable{T}.Equals(T)"/> public override bool Equals(object? obj) => (obj is AccelerometerData data) && Equals(data); /// <summary> /// Compares the underlying <see cref="Vector3"/> instances. /// </summary> /// <param name="other"><see cref="AccelerometerData"/> object to compare with.</param> /// <returns><see langword="true"/> if they are equal, otherwise <see langword="false"/>.</returns> public bool Equals(AccelerometerData other) => Acceleration.Equals(other.Acceleration); /// <summary> /// Equality operator for equals. /// </summary> /// <param name="left">Left to compare.</param> /// <param name="right">Right to compare.</param> /// <returns><see langword="true"/> if objects are equal, otherwise <see langword="false"/>.</returns> public static bool operator ==(AccelerometerData left, AccelerometerData right) => left.Equals(right); /// <summary> /// Inequality operator. /// </summary> /// <param name="left">Left to compare.</param> /// <param name="right">Right to compare.</param> /// <returns><see langword="true"/> if objects are not equal, otherwise <see langword="false"/>.</returns> public static bool operator !=(AccelerometerData left, AccelerometerData right) => !left.Equals(right); /// <inheritdoc cref="object.GetHashCode"/> public override int GetHashCode() => Acceleration.GetHashCode(); /// <summary> /// Returns a string representation of the current values of <see cref="Acceleration"/>. /// </summary> /// <returns>A string representation of this instance in the format of <c>X: x, Y: y, Z: z</c>.</returns> public override string ToString() => $"{nameof(Acceleration.X)}: {Acceleration.X}, " + $"{nameof(Acceleration.Y)}: {Acceleration.Y}, " + $"{nameof(Acceleration.Z)}: {Acceleration.Z}"; } partial class AccelerometerImplementation : IAccelerometer { const double accelerationThreshold = 169; const double gravity = 9.81; static readonly AccelerometerQueue queue = new AccelerometerQueue(); static bool useSyncContext; /// <inheritdoc/> public event EventHandler<AccelerometerChangedEventArgs>? ReadingChanged; /// <inheritdoc/> public event EventHandler? ShakeDetected; /// <inheritdoc/> public bool IsMonitoring { get; private set; } /// <inheritdoc/> /// <exception cref="FeatureNotSupportedException">Thrown if <see cref="IsSupported"/> returns <see langword="false"/>.</exception> /// <exception cref="InvalidOperationException">Thrown if <see cref="IsMonitoring"/> returns <see langword="true"/>.</exception> public void Start(SensorSpeed sensorSpeed) { if (!IsSupported) throw new FeatureNotSupportedException(); if (IsMonitoring) throw new InvalidOperationException("Accelerometer has already been started."); IsMonitoring = true; useSyncContext = sensorSpeed == SensorSpeed.Default || sensorSpeed == SensorSpeed.UI; try { PlatformStart(sensorSpeed); } catch { IsMonitoring = false; throw; } } /// <inheritdoc/> /// <exception cref="FeatureNotSupportedException">Thrown if <see cref="IsSupported"/> returns <see langword="false"/>.</exception> public void Stop() { if (!IsSupported) throw new FeatureNotSupportedException(); if (!IsMonitoring) return; IsMonitoring = false; try { PlatformStop(); } catch { IsMonitoring = true; throw; } } internal void OnChanged(AccelerometerData reading) => OnChanged(new AccelerometerChangedEventArgs(reading)); internal void OnChanged(AccelerometerChangedEventArgs e) { if (useSyncContext) MainThread.BeginInvokeOnMainThread(() => ReadingChanged?.Invoke(null, e)); else ReadingChanged?.Invoke(null, e); if (ShakeDetected != null) ProcessShakeEvent(e.Reading.Acceleration); } void ProcessShakeEvent(Vector3 acceleration) { var now = Nanoseconds(DateTime.UtcNow); var x = acceleration.X * gravity; var y = acceleration.Y * gravity; var z = acceleration.Z * gravity; var g = x * x + y * y + z * z; queue.Add(now, g > accelerationThreshold); if (queue.IsShaking) { queue.Clear(); var args = new EventArgs(); if (useSyncContext) MainThread.BeginInvokeOnMainThread(() => ShakeDetected?.Invoke(null, args)); else ShakeDetected?.Invoke(null, args); } static long Nanoseconds(DateTime time) => (time.Ticks / TimeSpan.TicksPerMillisecond) * 1_000_000; } } }