diff --git a/DeviceCore.sln b/DeviceCore.sln new file mode 100644 index 0000000..6fe069d --- /dev/null +++ b/DeviceCore.sln @@ -0,0 +1,67 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36301.6 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "app", "app", "{AD02B423-808C-484C-B74C-8BD7D1A4A6F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCore.Abstractions", "src\lib\DeviceCore.Abstractions\DeviceCore.Abstractions.csproj", "{26E9BF70-760F-DFCB-CBD5-E521C59D98FD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCore.Uno.WinUI", "src\lib\DeviceCore.Uno.WinUI\DeviceCore.Uno.WinUI.csproj", "{D1516BF4-7E73-CC38-0C50-ABF8C0B4F447}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{FA423D9C-B957-4517-9D09-0BF8E8991008}" + ProjectSection(SolutionItems) = preProject + build\azure-pipelines.yml = build\azure-pipelines.yml + build\gitversion.yml = build\gitversion.yml + build\stage-build.yml = build\stage-build.yml + build\stage-release.yml = build\stage-release.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "root", "root", "{FD0DD8AF-C1A2-4FB4-9FE3-D039FA20D310}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + .mergify.yml = .mergify.yml + BREAKING_CHANGES.md = BREAKING_CHANGES.md + CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md + CONTRIBUTING.md = CONTRIBUTING.md + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{DCAB9D57-69DC-4C5D-8703-43C374CB0387}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCore.Fakes", "src\lib\DeviceCore.Fakes\DeviceCore.Fakes.csproj", "{F50ABEC6-6C96-421D-BF40-67B74CBBB90E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {26E9BF70-760F-DFCB-CBD5-E521C59D98FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26E9BF70-760F-DFCB-CBD5-E521C59D98FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26E9BF70-760F-DFCB-CBD5-E521C59D98FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26E9BF70-760F-DFCB-CBD5-E521C59D98FD}.Release|Any CPU.Build.0 = Release|Any CPU + {D1516BF4-7E73-CC38-0C50-ABF8C0B4F447}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1516BF4-7E73-CC38-0C50-ABF8C0B4F447}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1516BF4-7E73-CC38-0C50-ABF8C0B4F447}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1516BF4-7E73-CC38-0C50-ABF8C0B4F447}.Release|Any CPU.Build.0 = Release|Any CPU + {F50ABEC6-6C96-421D-BF40-67B74CBBB90E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F50ABEC6-6C96-421D-BF40-67B74CBBB90E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F50ABEC6-6C96-421D-BF40-67B74CBBB90E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F50ABEC6-6C96-421D-BF40-67B74CBBB90E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {26E9BF70-760F-DFCB-CBD5-E521C59D98FD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {D1516BF4-7E73-CC38-0C50-ABF8C0B4F447} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {F50ABEC6-6C96-421D-BF40-67B74CBBB90E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {483215CF-95DE-4CCF-A682-832BB70A63F4} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 842ec7d..b652a7e 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,116 @@ -# Open Source Project Template +# Device Core -This repository contains a template to seed a repository for an Open Source -project. +A cross-platform device services library for .NET 8+ and Uno Platform. -## How to use this template +Device Core provides a unified API abstraction to access device hardware features such as accelerometer, ambient light sensor, battery information, flashlight, and screen wake lock across Windows, Android, and iOS. -1. Check out this repository -2. Delete the `.git` folder -3. Git init this repository and start working on your project! -4. Prior to submitting your request for publication, make sure to review the - [Open Source guidelines for publications](https://nventive.visualstudio.com/Internal/_wiki/wikis/Internal_wiki?wikiVersion=GBwikiMaster&pagePath=%2FOpen%20Source%2FPublishing&pageId=7120). +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) -## Features (to keep as-is, configure or remove) -- [Mergify](https://mergify.io/) is configured. You can edit or remove [.mergify.yml](/.mergify.yml). +## Getting Started -The following is the template for the final README.md file: +1. Add the `DeviceCore.Uno.WinUI` NuGet package to your projects (Windows, Android and iOS). + > 💡 If you need to implement more platforms or create custom implementations, you can use the `DeviceCore.Abstractions` NuGet package. ---- +1. Create an instance of any service. We'll cover dependency injection in details later on in this documentation. -# Project Title + ```cs + using DeviceCore; -{Project tag line} + var flashlightService = new FlashlightService(); + ``` -{Small description of the purpose of the project} +1. Use the service. -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) + ```cs + flashlightService.Brightness = 0.5f; + flashlightService.Toggle(); + ``` -## Getting Started +## Next Step + +### Using Dependency Injection -{Instructions to quickly get started using the project: pre-requisites, packages -to install, sample code, etc.} +Here is a simple code that does dependency injection using `Microsoft.Extensions.DependencyInjection` and `Microsoft.Extensions.Hosting`. + +```cs +using DeviceCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var host = new HostBuilder() + .ConfigureServices(serviceCollection => serviceCollection + .AddSingleton(_ => DispatcherQueue.GetForCurrentThread()) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + ) + .Build(); +``` ## Features -{More details/listing of features of the project} +Now that everything is setup, Let's see what else we can do! + +### Accelerometer + +The `AccelerometerService` provides access to the device's accelerometer sensor, allowing you to read acceleration data in three dimensions (X, Y, Z). + +> 💡 The `ObserveAcceleration` and `ObserveDeviceShaken` methods and will return an observable yielding `null` on devices that do not support `Accelerometer` or devices that do not have such a sensor. + +> 💡 Unsubscribe from the `ObserveAcceleration` and `ObserveDeviceShaken` observables when you no longer need the readings to avoid unnecessary battery consumption. + +> 💡 On iOS features built-in shake gesture recognition. Android use a common implementation to approximate shake detection. You can implement custom shake detection using the `ObserveAcceleration` method if needed. + +> 💡 On Android, if both `ObserveAcceleration` and `ObserveDeviceShaken` observables are used and `ReportInterval` is set high, they may be yielding reading more often than requested due to multiple subscribers. + +### Light Sensor + +The `AmbientLightProvider` provides access to the device's ambient light sensor, allowing you to read the current light level. + +> 💡 The `ObserveCurrentReading` method and will return an observable yielding `null` on devices that do not support `Accelerometer`, on devices that do not have such a sensor, or on iOS. + +> 💡 Unsubscribe from the `ObserveCurrentReading` observable when you no longer need the readings to avoid unnecessary battery consumption. + +### Battery Information + +The `BatteryInformationProvider` provides access to the device's battery information, allowing you to read the current battery level and status. + +> 💡 On Android, the `GetAndObserveRemainingChargePercent` observable is not updated continuously as there is no API that provides such events. It is triggered by system `Low` and `Ok` battery state broadcasts only. The `RemainingChargePercent` property always returns the up-to-date value. For continuous monitoring you can set up periodic polling. + +#### Android + +To use the battery information on Android, ensure you have the correct permissions in your `AndroidManifest.xml`. + +```xml + +``` + +### Flashlight + +The `FlashlightService` allows you to turn the phone's camera flashlight on and off. + +> 💡 On Android, flashlight brightness cannot be controlled, hence any non-zero brightness level results in the full brightness of the flashlight. + +> 💡 On iOS, in case the device supports the torch, brightness level is fully supported. In case the device has only flash, any non-zero brightness level will result in the full brightness of the flashlight. + +#### Android + +To use the flashlight on Android, ensure you have the correct permissions in your `AndroidManifest.xml`. + +```xml + + +``` + +### Keeping Screen On + +To enables an application to request to keep the device's screen on, use the `ScreenWakeLockService`. + +## Acknowledgements + +Take a look at [Uno.WinRT](https://platform.uno/docs/articles/features/using-winrt.html) that we use for the mobile platforms implementation. ## Breaking Changes diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml new file mode 100644 index 0000000..324f1e4 --- /dev/null +++ b/build/azure-pipelines.yml @@ -0,0 +1,64 @@ +trigger: + branches: + include: + - main + +variables: + # Pipeline configuration (Disable shallow fetch). + # See https://dev.to/kkazala/azure-devops-pipelines-shallow-fetch-1-is-now-default-4656 for more details. + # See https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/pipeline-options-for-git?view=azure-devops&tabs=yaml#shallow-fetch for more details. +- name: Agent.Source.Git.ShallowFetchDepth + value: 0 + +- name: NUGET_VERSION + value: 6.14.0 +- name: windowsHostedAgentImage + value: 'windows-2025' +- name: ArtifactName + value: Packages +- name: SolutionFileName + value: DeviceCore.sln +- name: IsReleaseBranch # Should this branch name use the release stage. + value: $[or(eq(variables['Build.SourceBranch'], 'refs/heads/main'), startsWith(variables['Build.SourceBranch'], 'refs/heads/feature/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'))] + +stages: +- stage: Build + jobs: + - job: Windows + strategy: + maxParallel: 3 + matrix: + Packages: + ApplicationConfiguration: Release + ApplicationPlatform: Any CPU + GeneratePackageOnBuild: true + + pool: + vmImage: $(windowsHostedAgentImage) + + variables: + - name: PackageOutputPath # Path where NuGet packages will be copied to. + value: $(Build.ArtifactStagingDirectory) + + workspace: + clean: all # Cleanup the workspace before starting. + + steps: + - checkout: self + fetchDepth: 0 + + - template: stage-build.yml + +- stage: Release + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['IsReleaseBranch'], 'true')) # Only release when the build is not for a Pull Request and branch name fits. + jobs: + - job: Publish_NuGet_External + + pool: + vmImage: $(windowsHostedAgentImage) + + workspace: + clean: all # Cleanup the workspace before starting. + + steps: + - template: stage-release.yml diff --git a/build/gitversion.yml b/build/gitversion.yml new file mode 100644 index 0000000..078ad37 --- /dev/null +++ b/build/gitversion.yml @@ -0,0 +1,27 @@ +# The version is driven by conventional commits via xxx-version-bump-message. +# Anything merged to main creates a new stable version. +# Only builds from main and feature/* are pushed to nuget.org. + +assembly-versioning-scheme: MajorMinorPatch +mode: MainLine +next-version: '' # Use git tags to set the base version. +continuous-delivery-fallback-tag: "" +commit-message-incrementing: Enabled +major-version-bump-message: "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?(!:|:.*\\n\\n((.+\\n)+\\n)?BREAKING CHANGE:\\s.+)" +minor-version-bump-message: "^(feat)(\\([\\w\\s-]*\\))?:" +patch-version-bump-message: "^(build|chore|docs|fix|perf|refactor|revert|style|test)(\\([\\w\\s-]*\\))?:" +no-bump-message: "^(ci)(\\([\\w\\s-]*\\))?:" # You can use the "ci" type to avoid bumping the version when your changes are limited to the build or .github folders. +branches: + main: + regex: ^master$|^main$ + tag: '' + dev: + regex: dev/.*?/(.*?) + tag: dev.{BranchName} + source-branches: [main] + feature: + tag: feature.{BranchName} + regex: feature/(.*?) + source-branches: [main] +ignore: + sha: [] \ No newline at end of file diff --git a/build/stage-build.yml b/build/stage-build.yml new file mode 100644 index 0000000..01f1a6f --- /dev/null +++ b/build/stage-build.yml @@ -0,0 +1,78 @@ +parameters: + DotNetVersion: '8.0.401' + UnoCheck_Version: '1.30.1' + UnoCheck_Manifest: 'https://raw.githubusercontent.com/unoplatform/uno.check/0ca039bef4097295fc6c2c5c282ae18a797160c1/manifests/uno.ui.manifest.json' + +steps: +- task: GitVersion/setup@0 + inputs: + versionSpec: '5.10.1' + displayName: 'Install GitVersion' + +- task: GitVersion/execute@0 + inputs: + useConfigFile: true + configFilePath: $(Build.SourcesDirectory)/build/gitversion.yml + displayName: 'Calculate version' + +- task: UseDotNet@2 + displayName: 'Use .NET SDK ${{ parameters.DotNetVersion }}' + retryCountOnTaskFailure: 3 + inputs: + packageType: sdk + version: ${{ parameters.DotNetVersion }} + includePreviewVersions: true + +- powershell: | + & dotnet tool update --global uno.check --version ${{ parameters.UnoCheck_Version }} --add-source https://api.nuget.org/v3/index.json + & uno-check -v --ci --non-interactive --fix --skip xcode --skip gtk3 --skip vswin --skip vsmac --skip androidsdk --skip androidemulator --manifest ${{ parameters.UnoCheck_Manifest }} + displayName: Install .NET Workloads | Uno-check + errorActionPreference: continue + ignoreLASTEXITCODE: true + retryCountOnTaskFailure: 3 + +- task: MSBuild@1 + displayName: 'Restore Solution Packages' + inputs: + solution: $(Build.SourcesDirectory)/$(SolutionFileName) + msbuildLocationMethod: version + msbuildVersion: latest + msbuildArchitecture: x64 + msbuildArguments: > + /t:restore + configuration: $(ApplicationConfiguration) + platform: $(ApplicationPlatform) + clean: false + maximumCpuCount: true + restoreNugetPackages: false + logProjectEvents: false + createLogFile: false + +- task: MSBuild@1 + displayName: 'Build solution in $(ApplicationConfiguration) | $(ApplicationPlatform)' + inputs: + solution: $(Build.SourcesDirectory)/$(SolutionFileName) + msbuildLocationMethod: version + msbuildVersion: latest + msbuildArchitecture: x64 + configuration: $(ApplicationConfiguration) + platform: $(ApplicationPlatform) + clean: false + maximumCpuCount: true + restoreNugetPackages: false + logProjectEvents: false + createLogFile: false + msbuildArguments: > # Set the version of the packages, will have no effect on application projects (Heads). + /p:PackageVersion=$(GitVersion.SemVer) + /p:ContinuousIntegrationBuild=true + +- task: PublishBuildArtifacts@1 + displayName: 'Publish artifact $(ApplicationConfiguration)' + inputs: + PathtoPublish: $(PackageOutputPath) + ArtifactName: $(ArtifactName) + ArtifactType: Container + +- task: PostBuildCleanup@3 + displayName: 'Post-Build cleanup : Cleanup files to keep build server clean!' + condition: always() diff --git a/build/stage-release.yml b/build/stage-release.yml new file mode 100644 index 0000000..54e762e --- /dev/null +++ b/build/stage-release.yml @@ -0,0 +1,26 @@ +steps: +- checkout: none + +- task: DownloadBuildArtifacts@0 + inputs: + buildType: current + downloadType: single + artifactName: $(ArtifactName) + +- task: NuGetToolInstaller@1 + displayName: 'Install NuGet $(NUGET_VERSION)' + inputs: + versionSpec: $(NUGET_VERSION) + checkLatest: false + +- task: NuGetCommand@2 + displayName: 'Push to Nuget.org' + inputs: + command: 'push' + packagesToPush: '$(Build.ArtifactStagingDirectory)/$(ArtifactName)/*.nupkg' + nuGetFeedType: 'external' + publishFeedCredentials: 'NuGet.org - nventive' + +- task: PostBuildCleanup@3 + displayName: 'Post-Build cleanup : Cleanup files to keep build server clean!' + condition: always() \ No newline at end of file diff --git a/src/lib/DeviceCore.Abstractions/Accelerometer/AccelerometerReading.cs b/src/lib/DeviceCore.Abstractions/Accelerometer/AccelerometerReading.cs new file mode 100644 index 0000000..1a7fcde --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/Accelerometer/AccelerometerReading.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace DeviceCore; + +/// +/// Represents an accelerometer reading. +/// +public sealed class AccelerometerReading +{ + public AccelerometerReading(double accelerationX, double accelerationY, double accelerationZ, TimeSpan? performanceCount, IReadOnlyDictionary? properties, DateTimeOffset timestamp) + { + AccelerationX = accelerationX; + AccelerationY = accelerationY; + AccelerationZ = accelerationZ; + PerformanceCount = performanceCount; + Properties = properties; + Timestamp = timestamp; + } + + /// + /// Gets the g-force acceleration along the x-axis. + /// + public double AccelerationX { get; } + + /// + /// Gets the g-force acceleration along the y-axis. + /// + public double AccelerationY { get; } + + /// + /// Gets the g-force acceleration along the z-axis. + /// + public double AccelerationZ { get; } + + /// + /// Gets the performance count associated with the reading. + /// This allows the reading to be synchronized with other devices and processes on the system. + /// + public TimeSpan? PerformanceCount { get; } + + /// + /// Gets the data properties reported by the sensor. + /// + public IReadOnlyDictionary? Properties { get; } + + /// + /// Gets the time at which the sensor reported the reading. + /// + public DateTimeOffset Timestamp { get; } +} diff --git a/src/lib/DeviceCore.Abstractions/Accelerometer/IAccelerometerService.cs b/src/lib/DeviceCore.Abstractions/Accelerometer/IAccelerometerService.cs new file mode 100644 index 0000000..37117d0 --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/Accelerometer/IAccelerometerService.cs @@ -0,0 +1,36 @@ +using System; + +namespace DeviceCore; + +/// +/// Provides functionality to observe accelerometer readings and detect device shake events. +/// +/// +/// On Android, when both ReadingChanged and Shaken events are attached and the user sets the ReportInterval to a high value, the ReadingChanged event may be raised more often than requested. +/// This is because for multiple subscribers to the same sensor, the system may raise the sensor events with the frequency of the one with the lowest requested report delay. +/// This is, however, in line with the behavior of the WinUI Accelerometer, and you can filter the events as necessary for your use case. +/// +public interface IAccelerometerService +{ + /// + /// Gets or sets the current report interval for the accelerometer. + /// + uint ReportInterval { get; set; } + + /// + /// Observes the acceleration data from the device's accelerometer. + /// + /// + /// If the device does not support an accelerometer sensor, this method will return an observable sequence that yields null. + /// + IObservable ObserveAcceleration(); + + /// + /// Observes when the device is shaken. + /// + /// + /// If the device does not support an accelerometer sensor, this method will return an observable sequence that yields null. + /// + /// An observable sequence yielding a timestamp when the device has been shaken. + IObservable ObserveDeviceShaken(); +} diff --git a/src/lib/DeviceCore.Abstractions/AmbientLight/AmbientLightReading.cs b/src/lib/DeviceCore.Abstractions/AmbientLight/AmbientLightReading.cs new file mode 100644 index 0000000..ded29b7 --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/AmbientLight/AmbientLightReading.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace DeviceCore; + +/// +/// Represents an ambient light–sensor reading. +/// +public sealed class AmbientLightReading +{ + public AmbientLightReading( + float illuminanceInLux, + TimeSpan? performanceCount, + IReadOnlyDictionary? properties, + DateTimeOffset timestamp + ) + { + IlluminanceInLux = illuminanceInLux; + PerformanceCount = performanceCount; + Properties = properties; + Timestamp = timestamp; + } + + /// + /// Gets the illuminance level in lux. + /// + public float IlluminanceInLux { get; } + + /// + /// Gets the performance count associated with the reading.This allows the reading to be synchronized with other devices and processes on the system. + /// + public TimeSpan? PerformanceCount { get; } + + /// + /// Gets the data properties reported by the sensor. + /// + public IReadOnlyDictionary? Properties { get; } + + /// + /// Gets the time at which the sensor reported the reading. + /// + public DateTimeOffset Timestamp { get; } +} diff --git a/src/lib/DeviceCore.Abstractions/AmbientLight/IAmbientLightProvider.cs b/src/lib/DeviceCore.Abstractions/AmbientLight/IAmbientLightProvider.cs new file mode 100644 index 0000000..ce39375 --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/AmbientLight/IAmbientLightProvider.cs @@ -0,0 +1,21 @@ +using System; + +namespace DeviceCore; + +/// +/// Provides access to the current ambient light (illuminance level) reading in lux and detects changes. +/// +/// +/// The device or emulator that you're using must support an ambient light sensor. +/// +public interface IAmbientLightProvider +{ + /// + /// Observes the current ambient light reading. + /// + /// + /// If the device does not support an ambient light sensor, this method will return an observable sequence that yields null. + /// + /// An observable sequence yielding the current ambient light reading. + IObservable ObserveCurrentReading(); +} diff --git a/src/lib/DeviceCore.Abstractions/BatteryInformation/BatteryStatus.cs b/src/lib/DeviceCore.Abstractions/BatteryInformation/BatteryStatus.cs new file mode 100644 index 0000000..5064c8f --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/BatteryInformation/BatteryStatus.cs @@ -0,0 +1,27 @@ +namespace DeviceCore; + +/// +/// Indicates the status of the battery. +/// +public enum BatteryStatus +{ + /// + /// The battery or battery controller is not present. + /// + NotPresent, + + /// + /// The battery is discharging. + /// + Discharging, + + /// + /// The battery is idle. + /// + Idle, + + /// + /// The battery is charging. + /// + Charging, +} diff --git a/src/lib/DeviceCore.Abstractions/BatteryInformation/EnergySaverStatus.cs b/src/lib/DeviceCore.Abstractions/BatteryInformation/EnergySaverStatus.cs new file mode 100644 index 0000000..0225b79 --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/BatteryInformation/EnergySaverStatus.cs @@ -0,0 +1,22 @@ +namespace DeviceCore; + +/// +/// Specifies the status of battery saver. +/// +public enum EnergySaverStatus +{ + /// + /// Battery saver is off permanently or the device is plugged in. + /// + Disabled, + + /// + /// Battery saver is off now, but ready to turn on automatically. + /// + Off, + + /// + /// Battery saver is on. Save energy where possible. + /// + On, +} diff --git a/src/lib/DeviceCore.Abstractions/BatteryInformation/IBatteryInformationProvider.cs b/src/lib/DeviceCore.Abstractions/BatteryInformation/IBatteryInformationProvider.cs new file mode 100644 index 0000000..08df591 --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/BatteryInformation/IBatteryInformationProvider.cs @@ -0,0 +1,53 @@ +using System; + +namespace DeviceCore; + +/// +/// Provides access to battery information on the device. +/// +public interface IBatteryInformationProvider +{ + /// + /// Gets the device's battery status. + /// + BatteryStatus BatteryStatus { get; } + + /// + /// Gets the devices's battery saver status, indicating when to save energy. + /// + EnergySaverStatus EnergySaverStatus { get; } + + /// + /// Gets the device's power supply status. + /// + PowerSupplyStatus PowerSupplyStatus { get; } + + /// + /// Gets the total percentage of charge remaining from all batteries connected to the device. + /// + int RemainingChargePercent { get; } + + /// + /// Gets and observes the current battery status. + /// + /// An observable sequence yielding the curent battery status. + IObservable GetAndObserveBatteryStatus(); + + /// + /// Gets and observes the current energy saver status. + /// + /// An observable sequence yielding the curent energy saver status. + IObservable GetAndObserveEnergySaverStatus(); + + /// + /// Gets and observes the current power supply status. + /// + /// An observable sequence yielding the curent power supply status. + IObservable GetAndObservePowerSupplyStatus(); + + /// + /// Gets and observes the remaining charge percentage from all batteries connected to the device. + /// + /// An observable sequence yielding the remaining charge time. + IObservable GetAndObserveRemainingChargePercent(); +} diff --git a/src/lib/DeviceCore.Abstractions/BatteryInformation/PowerSupplyStatus.cs b/src/lib/DeviceCore.Abstractions/BatteryInformation/PowerSupplyStatus.cs new file mode 100644 index 0000000..8053a13 --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/BatteryInformation/PowerSupplyStatus.cs @@ -0,0 +1,22 @@ +namespace DeviceCore; + +/// +/// Represents the device's power supply status. +/// +public enum PowerSupplyStatus +{ + /// + /// The device has no power supply. + /// + NotPresent, + + /// + /// The device has an inadequate power supply. + /// + Inadequate, + + /// + /// The device has an adequate power supply. + /// + Adequate, +} diff --git a/src/lib/DeviceCore.Abstractions/DeviceCore.Abstractions.csproj b/src/lib/DeviceCore.Abstractions/DeviceCore.Abstractions.csproj new file mode 100644 index 0000000..aa7dd85 --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/DeviceCore.Abstractions.csproj @@ -0,0 +1,33 @@ + + + net8.0 + enable + DeviceCore + nventive + nventive + DeviceCore.Abstractions + DeviceCore.Abstractions + Abstractions for device-related APIs, providing a common interface for device features across multiple platforms. + Apache-2.0 + https://github.com/nventive/DeviceCore + true + review;maui;winui;ios;android + https://github.com/nventive/DeviceCore/blob/main/LICENSE + README.md + https://github.com/nventive/DeviceCore + True + + + + + + True + \ + + + + + + + + diff --git a/src/lib/DeviceCore.Abstractions/IFlashlightService.cs b/src/lib/DeviceCore.Abstractions/IFlashlightService.cs new file mode 100644 index 0000000..fa6ad92 --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/IFlashlightService.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; + +namespace DeviceCore; + +/// +/// Provides access to the device's flashlight functionality. +/// +public interface IFlashlightService +{ + /// + /// Gets or sets the brightness of the flashlight with a value in the [0.0, 1.0] range. + /// + /// + /// On Android, flashlight brightness cannot be controlled, hence any non-zero BrightnessLevel results in the full brightness of the flashlight. + /// On iOS, in case the device supports the torch, BrightnessLevel is fully supported. In case the device has only flash, any non-zero BrightnessLevel will result in the full brightness of the flashlight. + /// + float Brightness { get; set; } + + /// + /// Initializes the flashlight service. + /// + Task Initialize(); + + /// + /// Toggles the flashlight state on or off. + /// + /// + /// You must set the property before or after calling this method to ensure the desired brightness level is applied when the flashlight is turned on. + /// + void Toggle(); +} diff --git a/src/lib/DeviceCore.Abstractions/IScreenWakeLockService.cs b/src/lib/DeviceCore.Abstractions/IScreenWakeLockService.cs new file mode 100644 index 0000000..f915c1e --- /dev/null +++ b/src/lib/DeviceCore.Abstractions/IScreenWakeLockService.cs @@ -0,0 +1,17 @@ +namespace DeviceCore; + +/// +/// Provides a way for keeping the device's display on. +/// +public interface IScreenWakeLockService +{ + /// + /// Enables the device's keep screen on feature. + /// + void Enable(); + + /// + /// Disables the device's keep screen on feature. + /// + void Disable(); +} diff --git a/src/lib/DeviceCore.Fakes/DeviceCore.Fakes.csproj b/src/lib/DeviceCore.Fakes/DeviceCore.Fakes.csproj new file mode 100644 index 0000000..15d867f --- /dev/null +++ b/src/lib/DeviceCore.Fakes/DeviceCore.Fakes.csproj @@ -0,0 +1,38 @@ + + + net8.0 + 12.0 + enable + DeviceCore + nventive + nventive + DeviceCore.Fakes + DeviceCore.Fakes + Fakes and mock implementations for DeviceCore.Abstractions, intended for use in unit testing and development scenarios. + Apache-2.0 + https://github.com/nventive/DeviceCore + true + review;maui;winui;ios;android + https://github.com/nventive/DeviceCore/blob/main/LICENSE + README.md + https://github.com/nventive/DeviceCore + True + + + + + + True + \ + + + + + + + + + + + + diff --git a/src/lib/DeviceCore.Fakes/FakeAccelerometerService.cs b/src/lib/DeviceCore.Fakes/FakeAccelerometerService.cs new file mode 100644 index 0000000..a430758 --- /dev/null +++ b/src/lib/DeviceCore.Fakes/FakeAccelerometerService.cs @@ -0,0 +1,25 @@ +using System; +using System.Reactive.Linq; + +namespace DeviceCore; + +/// +/// The fake implementation of for testing purposes. +/// +public sealed class FakeAccelerometerService : IAccelerometerService +{ + /// + public uint ReportInterval { get; set; } + + /// + public IObservable ObserveAcceleration() + { + return Observable.Return(new AccelerometerReading(0d, 0d, 0d, null, null, DateTimeOffset.Now)); + } + + /// + public IObservable ObserveDeviceShaken() + { + return Observable.Return(DateTimeOffset.Now); + } +} diff --git a/src/lib/DeviceCore.Fakes/FakeAmbientLightProvider.cs b/src/lib/DeviceCore.Fakes/FakeAmbientLightProvider.cs new file mode 100644 index 0000000..c36011f --- /dev/null +++ b/src/lib/DeviceCore.Fakes/FakeAmbientLightProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Reactive.Linq; + +namespace DeviceCore; + +/// +/// The fake implementation of for testing purposes. +/// +public sealed class FakeAmbientLightProvider : IAmbientLightProvider +{ + /// + public IObservable ObserveCurrentReading() + { + return Observable.Return(new AmbientLightReading(0f, null, null, DateTimeOffset.Now)); + } +} diff --git a/src/lib/DeviceCore.Fakes/FakeBatteryInformationProvider.cs b/src/lib/DeviceCore.Fakes/FakeBatteryInformationProvider.cs new file mode 100644 index 0000000..6403ca7 --- /dev/null +++ b/src/lib/DeviceCore.Fakes/FakeBatteryInformationProvider.cs @@ -0,0 +1,46 @@ +using System; +using System.Reactive.Linq; + +namespace DeviceCore; + +/// +/// Fake implementation of for testing purposes. +/// +public sealed class FakeBatteryInformationProvider : IBatteryInformationProvider +{ + /// + public BatteryStatus BatteryStatus { get; } + + /// + public EnergySaverStatus EnergySaverStatus { get; } + + /// + public PowerSupplyStatus PowerSupplyStatus { get; } + + /// + public int RemainingChargePercent { get; } + + /// + public IObservable GetAndObserveBatteryStatus() + { + return Observable.Return(BatteryStatus); + } + + /// + public IObservable GetAndObserveEnergySaverStatus() + { + return Observable.Return(EnergySaverStatus); + } + + /// + public IObservable GetAndObservePowerSupplyStatus() + { + return Observable.Return(PowerSupplyStatus); + } + + /// + public IObservable GetAndObserveRemainingChargePercent() + { + return Observable.Return(RemainingChargePercent); + } +} diff --git a/src/lib/DeviceCore.Fakes/FakeFlashlightService.cs b/src/lib/DeviceCore.Fakes/FakeFlashlightService.cs new file mode 100644 index 0000000..65f8c1f --- /dev/null +++ b/src/lib/DeviceCore.Fakes/FakeFlashlightService.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace DeviceCore; + +/// +/// The fake implementation for testing purposes. +/// +public sealed class FakeFlashlightService : IFlashlightService +{ + /// + public float Brightness { get; set; } + + /// + public Task Initialize() + { + return Task.CompletedTask; + } + + /// + public void Toggle() + { + return; + } +} diff --git a/src/lib/DeviceCore.Fakes/FakeScreenWakeLockService.cs b/src/lib/DeviceCore.Fakes/FakeScreenWakeLockService.cs new file mode 100644 index 0000000..c1e1788 --- /dev/null +++ b/src/lib/DeviceCore.Fakes/FakeScreenWakeLockService.cs @@ -0,0 +1,19 @@ +namespace DeviceCore; + +/// +/// The fake implementation for testing purposes. +/// +public sealed class FakeScreenWakeLockService : IScreenWakeLockService +{ + /// + public void Disable() + { + return; + } + + /// + public void Enable() + { + return; + } +} diff --git a/src/lib/DeviceCore.Uno.WinUI/AccelerometerService.cs b/src/lib/DeviceCore.Uno.WinUI/AccelerometerService.cs new file mode 100644 index 0000000..b4bddd6 --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/AccelerometerService.cs @@ -0,0 +1,82 @@ +#if ANDROID || IOS || WINDOWS +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Reactive.Linq; +using Windows.Devices.Sensors; +using Windows.Foundation; + +namespace DeviceCore; + +/// +/// The implementation using Uno. +/// +public sealed class AccelerometerService : IAccelerometerService +{ + private readonly ILogger _logger; + private readonly Accelerometer? _accelerometer; + + public AccelerometerService(ILogger logger) + { + _logger = logger ?? NullLogger.Instance; + _accelerometer = Accelerometer.GetDefault(); + } + + /// + public uint ReportInterval + { + get => _accelerometer?.ReportInterval ?? 0; + set + { + if (_accelerometer is null) + { + return; + } + + _accelerometer.ReportInterval = value; + } + } + + /// + public IObservable ObserveAcceleration() + { + if (_accelerometer is null) + { + return Observable.Return(null); + } + + return Observable.FromEventPattern, AccelerometerReadingChangedEventArgs>( + h => _accelerometer.ReadingChanged += h, + h => _accelerometer.ReadingChanged -= h + ) + .Select(eventPattern => + { + var reading = eventPattern.EventArgs.Reading; + + return new AccelerometerReading( + reading.AccelerationX, + reading.AccelerationY, + reading.AccelerationZ, + reading.PerformanceCount, + reading.Properties, + reading.Timestamp + ); + }); + } + + /// + public IObservable ObserveDeviceShaken() + { + if (_accelerometer is null) + { + return Observable.Return(null); + } + + return Observable.FromEventPattern, AccelerometerShakenEventArgs>( + h => _accelerometer.Shaken += h, + h => _accelerometer.Shaken -= h + ) + .Select(eventPattern => (DateTimeOffset?)eventPattern.EventArgs.Timestamp); + } +} +#endif diff --git a/src/lib/DeviceCore.Uno.WinUI/AmbientLightProvider.cs b/src/lib/DeviceCore.Uno.WinUI/AmbientLightProvider.cs new file mode 100644 index 0000000..feab746 --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/AmbientLightProvider.cs @@ -0,0 +1,47 @@ +#if ANDROID || IOS || WINDOWS +using System; +using System.Linq; +using System.Reactive.Linq; +using Windows.Devices.Sensors; +using Windows.Foundation; + +namespace DeviceCore; + +/// +/// The implementation using Uno. +/// +public sealed class AmbientLightProvider : IAmbientLightProvider +{ + private readonly LightSensor? _lightSensor; + + public AmbientLightProvider() + { + _lightSensor = LightSensor.GetDefault(); + } + + /// + public IObservable ObserveCurrentReading() + { + if (_lightSensor is null) + { + return Observable.Return(null); + } + + return Observable.FromEventPattern, LightSensorReadingChangedEventArgs>( + h => _lightSensor.ReadingChanged += h, + h => _lightSensor.ReadingChanged -= h + ) + .Select(eventPattern => + { + var reading = eventPattern.EventArgs.Reading; + + return new AmbientLightReading( + reading.IlluminanceInLux, + reading.PerformanceCount, + reading.Properties, + reading.Timestamp + ); + }); + } +} +#endif diff --git a/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/BatteryInformationProvider.cs b/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/BatteryInformationProvider.cs new file mode 100644 index 0000000..9cb14dc --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/BatteryInformationProvider.cs @@ -0,0 +1,74 @@ +#if ANDROID || IOS || WINDOWS +using System; +using System.Linq; +using System.Reactive.Linq; +using Windows.System.Power; + +namespace DeviceCore; + +/// +/// The implementation using Uno. +/// +public sealed class BatteryInformationProvider : IBatteryInformationProvider +{ + /// + public BatteryStatus BatteryStatus => PowerManager.BatteryStatus.ToInternalBatteryStatus(); + + /// + public EnergySaverStatus EnergySaverStatus => PowerManager.EnergySaverStatus.ToInternalEnergySaverStatus(); + + /// + public PowerSupplyStatus PowerSupplyStatus => PowerManager.PowerSupplyStatus.ToInternalPowerSupplyStatus(); + + /// + public int RemainingChargePercent => PowerManager.RemainingChargePercent; + + /// + public IObservable GetAndObserveBatteryStatus() + { + return Observable.FromEventPattern, object>( + h => PowerManager.BatteryStatusChanged += h, + h => PowerManager.BatteryStatusChanged -= h + ) + .Select(_ => BatteryStatus) + .StartWith(BatteryStatus) + .DistinctUntilChanged(); + } + + /// + public IObservable GetAndObserveEnergySaverStatus() + { + return Observable.FromEventPattern, object>( + h => PowerManager.EnergySaverStatusChanged += h, + h => PowerManager.EnergySaverStatusChanged -= h + ) + .Select(_ => EnergySaverStatus) + .StartWith(EnergySaverStatus) + .DistinctUntilChanged(); + } + + /// + public IObservable GetAndObservePowerSupplyStatus() + { + return Observable.FromEventPattern, object>( + h => PowerManager.PowerSupplyStatusChanged += h, + h => PowerManager.PowerSupplyStatusChanged -= h + ) + .Select(_ => PowerSupplyStatus) + .StartWith(PowerSupplyStatus) + .DistinctUntilChanged(); + } + + /// + public IObservable GetAndObserveRemainingChargePercent() + { + return Observable.FromEventPattern, object>( + h => PowerManager.RemainingChargePercentChanged += h, + h => PowerManager.RemainingChargePercentChanged -= h + ) + .Select(_ => RemainingChargePercent) + .StartWith(RemainingChargePercent) + .DistinctUntilChanged(); + } +} +#endif diff --git a/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/BatteryStatus.Extensions.cs b/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/BatteryStatus.Extensions.cs new file mode 100644 index 0000000..5d031c9 --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/BatteryStatus.Extensions.cs @@ -0,0 +1,25 @@ +#if ANDROID || IOS || WINDOWS +using System; + +namespace Windows.System.Power; + +internal static class BatteryStatusExtensions +{ + /// + /// Converts the to . + /// + /// The battery status. + /// The mapped . + public static DeviceCore.BatteryStatus ToInternalBatteryStatus(this BatteryStatus status) + { + return status switch + { + BatteryStatus.NotPresent => DeviceCore.BatteryStatus.NotPresent, + BatteryStatus.Discharging => DeviceCore.BatteryStatus.Discharging, + BatteryStatus.Idle => DeviceCore.BatteryStatus.Idle, + BatteryStatus.Charging => DeviceCore.BatteryStatus.Charging, + _ => throw new ArgumentOutOfRangeException(nameof(status), status, "Unknown battery status."), + }; + } +} +#endif diff --git a/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/EnergySaverStatus.Extensions.cs b/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/EnergySaverStatus.Extensions.cs new file mode 100644 index 0000000..52f02bd --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/EnergySaverStatus.Extensions.cs @@ -0,0 +1,24 @@ +#if ANDROID || IOS || WINDOWS +using System; + +namespace Windows.System.Power; + +internal static class EnergySaverStatusExtensions +{ + /// + /// Converts the to . + /// + /// The energy saver status. + /// The mapped . + public static DeviceCore.EnergySaverStatus ToInternalEnergySaverStatus(this EnergySaverStatus status) + { + return status switch + { + EnergySaverStatus.Disabled => DeviceCore.EnergySaverStatus.Disabled, + EnergySaverStatus.Off => DeviceCore.EnergySaverStatus.Off, + EnergySaverStatus.On => DeviceCore.EnergySaverStatus.On, + _ => throw new ArgumentOutOfRangeException(nameof(status), status, "Unknown energy saver status."), + }; + } +} +#endif diff --git a/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/PowerSupplyStatus.Extensions.cs b/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/PowerSupplyStatus.Extensions.cs new file mode 100644 index 0000000..29e2599 --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/BatteryInformation/PowerSupplyStatus.Extensions.cs @@ -0,0 +1,24 @@ +#if ANDROID || IOS || WINDOWS +using System; + +namespace Windows.System.Power; + +internal static class PowerSupplyStatusExtensions +{ + /// + /// Converts the to . + /// + /// The power supply status. + /// The mapped . + public static DeviceCore.PowerSupplyStatus ToInternalPowerSupplyStatus(this PowerSupplyStatus status) + { + return status switch + { + PowerSupplyStatus.NotPresent => DeviceCore.PowerSupplyStatus.NotPresent, + PowerSupplyStatus.Inadequate => DeviceCore.PowerSupplyStatus.Inadequate, + PowerSupplyStatus.Adequate => DeviceCore.PowerSupplyStatus.Adequate, + _ => throw new ArgumentOutOfRangeException(nameof(status), status, "Unknown power supply status."), + }; + } +} +#endif diff --git a/src/lib/DeviceCore.Uno.WinUI/DeviceCore.Uno.WinUI.csproj b/src/lib/DeviceCore.Uno.WinUI/DeviceCore.Uno.WinUI.csproj new file mode 100644 index 0000000..4763079 --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/DeviceCore.Uno.WinUI.csproj @@ -0,0 +1,51 @@ + + + net8.0;net8.0-android;net8.0-ios;net8.0-windows10.0.20348.0 + 12.0 + enable + Uno0001 + DeviceCore + nventive + nventive + DeviceCore.Uno.WinUI + DeviceCore.Uno.WinUI + Provides cross-platform device APIs for Uno Platform and WinUI, supporting .NET 8, Android, iOS, and Windows targets. + Apache-2.0 + https://github.com/nventive/DeviceCore + true + review;maui;winui;ios;android + https://github.com/nventive/DeviceCore/blob/main/LICENSE + README.md + https://github.com/nventive/DeviceCore + True + + + + + + True + \ + + + + + + + + + + + + + + + + + + 10.0.20348.38 + + + + + + diff --git a/src/lib/DeviceCore.Uno.WinUI/FlashlightService.cs b/src/lib/DeviceCore.Uno.WinUI/FlashlightService.cs new file mode 100644 index 0000000..5c2ae1c --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/FlashlightService.cs @@ -0,0 +1,63 @@ +#if ANDROID || IOS || WINDOWS +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Threading.Tasks; +using Windows.Devices.Lights; + +namespace DeviceCore; + +/// +/// The implementation using Uno. +/// +public sealed class FlashlightService : IFlashlightService, IDisposable +{ + private readonly ILogger _logger; + + private Lamp? _lamp; + + public FlashlightService(ILogger? logger) + { + _logger = logger ?? new NullLogger(); + } + + /// + public float Brightness + { + get => _lamp?.BrightnessLevel ?? 0f; + set + { + if (_lamp is null) + { + return; + } + + _lamp.BrightnessLevel = Math.Clamp(value, 0f, 1f); + } + } + + /// + public async Task Initialize() + { + _lamp ??= await Lamp.GetDefaultAsync(); + } + + /// + public void Toggle() + { + if (_lamp is null) + { + return; + } + + _lamp.IsEnabled = !_lamp.IsEnabled; + } + + /// + public void Dispose() + { + _lamp?.Dispose(); + _lamp = null; + } +} +#endif diff --git a/src/lib/DeviceCore.Uno.WinUI/ScreenWakeLockService.cs b/src/lib/DeviceCore.Uno.WinUI/ScreenWakeLockService.cs new file mode 100644 index 0000000..0b5de48 --- /dev/null +++ b/src/lib/DeviceCore.Uno.WinUI/ScreenWakeLockService.cs @@ -0,0 +1,89 @@ +#if ANDROID || IOS || WINDOWS +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.UI.Dispatching; +using Windows.System.Display; + +namespace DeviceCore; + +/// +/// The implementation using Uno. +/// +public sealed class ScreenWakeLockService : IScreenWakeLockService, IDisposable +{ + private readonly ILogger _logger; + private readonly DispatcherQueue _dispatcherQueue; + + private DisplayRequest? _displayRequest; + private bool _isEnabled; + + public ScreenWakeLockService(ILogger logger, DispatcherQueue dispatcherQueue) + { + _logger = logger ?? NullLogger.Instance; + _dispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue)); + } + + /// + public void Enable() + { + _logger.LogDebug("Trying to request display to stay active."); + + try + { + _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () => + { + _displayRequest ??= new DisplayRequest(); + _displayRequest.RequestActive(); + }); + + _isEnabled = true; + _logger.LogInformation("Requested display to stay active."); + } + catch (Exception e) + { + _logger.LogError(e, "Failed to request display to stay active."); + throw; + } + } + + /// + public void Disable() + { + _logger.LogDebug("Trying to release display request."); + + if (!_isEnabled) + { + _logger.LogWarning("Display request is already disabled."); + return; + } + + try + { + if (_displayRequest is null) + { + _logger.LogWarning("Display request is null, cannot release."); + return; + } + + _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, _displayRequest!.RequestRelease); + _isEnabled = false; + _logger.LogInformation("Released display request."); + } + catch (Exception e) + { + _logger.LogError(e, "Failed to release display request."); + throw; + } + } + + /// + public void Dispose() + { + if (_isEnabled) + { + Disable(); + } + } +} +#endif