diff --git a/Mobile/Downloader/Downloader.sln b/Mobile/Downloader/Downloader.sln
new file mode 100644
index 000000000..ce50a4b91
--- /dev/null
+++ b/Mobile/Downloader/Downloader.sln
@@ -0,0 +1,22 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31005.135
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Downloader", "Downloader/Downloader.csproj", "{d42f4c90-c837-4704-8563-194d9392b9c7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {d42f4c90-c837-4704-8563-194d9392b9c7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {d42f4c90-c837-4704-8563-194d9392b9c7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {d42f4c90-c837-4704-8563-194d9392b9c7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {d42f4c90-c837-4704-8563-194d9392b9c7}.Release|Any CPU.Build.0 = Release|Any CPU
+
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Mobile/Downloader/Downloader/Directory.Build.targets b/Mobile/Downloader/Downloader/Directory.Build.targets
new file mode 100644
index 000000000..49b3ab641
--- /dev/null
+++ b/Mobile/Downloader/Downloader/Directory.Build.targets
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ $([System.IO.Path]::GetDirectoryName($(MSBuildProjectDirectory)))
+
+
+
+
+
+
diff --git a/Mobile/Downloader/Downloader/DownloadInfo.cs b/Mobile/Downloader/Downloader/DownloadInfo.cs
new file mode 100644
index 000000000..a7737c1a3
--- /dev/null
+++ b/Mobile/Downloader/Downloader/DownloadInfo.cs
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.ComponentModel;
+
+namespace Downloader
+{
+ ///
+ /// An internal class to show download information list
+ ///
+ internal class DownloadInfo : INotifyPropertyChanged
+ {
+ private string name;
+ private string value;
+
+ ///
+ /// Constructor
+ ///
+ /// field name
+ /// field value
+ public DownloadInfo(string name, string value)
+ {
+ this.Name = name;
+ this.Value = value;
+ }
+
+ ///
+ /// The field name of download information list
+ ///
+ public string Name
+ {
+ get => name;
+ set
+ {
+ if (name != value)
+ {
+ name = value;
+ OnPropertyChanged(nameof(Name));
+ }
+ }
+ }
+
+ ///
+ /// The field value of download information list
+ ///
+ public string Value
+ {
+ get => this.value;
+ set
+ {
+ if (this.value != value)
+ {
+ this.value = value;
+ OnPropertyChanged(nameof(Value));
+ }
+ }
+ }
+
+ // INotifyPropertyChanged implementation
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/Mobile/Downloader/Downloader/DownloadInfoPage.cs b/Mobile/Downloader/Downloader/DownloadInfoPage.cs
new file mode 100644
index 000000000..da8cde076
--- /dev/null
+++ b/Mobile/Downloader/Downloader/DownloadInfoPage.cs
@@ -0,0 +1,288 @@
+using System;
+using System.Collections.Generic;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+
+namespace Downloader
+{
+ ///
+ /// A public class to show download information page in NUI.
+ ///
+ public class DownloadInfoPage : ContentPage
+ {
+ private View downloadInfoLabel;
+ private CollectionView downloadInfoListView;
+ private List downloadInfoList = new List();
+ private bool isUpdatedInfoList;
+ private Button goToMainPageButton;
+ private IDownload downloadService;
+
+ ///
+ /// Default constructor (creates a new download service instance)
+ ///
+ public DownloadInfoPage() : this(new DownloadImplementation())
+ {
+ }
+
+ ///
+ /// Constructor that accepts a shared download service instance.
+ ///
+ /// The IDownload service instance to use.
+ public DownloadInfoPage(IDownload sharedDownloadService)
+ {
+ downloadService = sharedDownloadService;
+ InitializeComponent();
+ }
+
+ ///
+ /// Initialize page events.
+ ///
+ private void InitializeEvents()
+ {
+ // Update a download information list when this page is appearing
+ this.Appearing += (object sender, PageAppearingEventArgs e) =>
+ {
+ UpdateDownloadInfoList();
+ };
+ }
+
+ ///
+ /// Initialize components of page.
+ ///
+ private void InitializeComponent()
+ {
+ AppBar = new AppBar
+ {
+ Title = "Download Info",
+ BackgroundColor = Resources.PrimaryColor
+ };
+
+ // Create and add the navigation button to the AppBar using TextLabel instead of Button
+ var mainButtonContainer = new View()
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ LinearAlignment = LinearLayout.Alignment.Center,
+ CellPadding = Resources.ZeroSpacing
+ },
+ WidthSpecification = LayoutParamPolicies.WrapContent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ Padding = Resources.AppBarButtonPadding,
+ BackgroundColor = Resources.TransparentColor
+ };
+
+ var mainButtonText = new TextLabel
+ {
+ Text = "Main",
+ TextColor = Resources.WhiteColor,
+ PointSize = Resources.TextSizeMedium,
+ WidthSpecification = LayoutParamPolicies.WrapContent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+
+ mainButtonContainer.Add(mainButtonText);
+ mainButtonContainer.TouchEvent += (s, e) =>
+ {
+ if (e.Touch.GetState(0) == PointStateType.Up)
+ {
+ OnGoToMainPageButtonClicked(s, null);
+ }
+ return true;
+ };
+
+ goToMainPageButton = null;
+ AppBar.Actions = new View[] { mainButtonContainer };
+
+ downloadInfoLabel = CreateDownloadInfoLabel();
+ downloadInfoListView = CreateCollectionView();
+
+ var mainLayout = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = Resources.LayoutSpacingMedium
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ BackgroundColor = Resources.BackgroundColor,
+ Padding = Resources.PagePadding
+ };
+
+ mainLayout.Add(downloadInfoLabel);
+ mainLayout.Add(downloadInfoListView);
+
+ Content = mainLayout;
+
+ isUpdatedInfoList = false;
+
+ // Initialize events after content is set
+ InitializeEvents();
+ AddEvent(); // Add button events
+ }
+
+
+ ///
+ /// Register event handlers for buttons.
+ ///
+ private void AddEvent()
+ {
+ // goToMainPageButton.Clicked event is already subscribed in InitializeComponent
+ }
+
+ ///
+ /// Event handler for the "Go to Download Main Page" button click.
+ /// Navigates back to the DownloadMainPage.
+ ///
+ /// Event sender
+ /// Event arguments
+ private void OnGoToMainPageButtonClicked(object sender, ClickedEventArgs e)
+ {
+ var navigator = this.Navigator;
+ if (navigator != null && navigator.PageCount > 1)
+ {
+ navigator.Pop();
+ }
+ }
+
+ ///
+ /// Update a download information list.
+ ///
+ private void UpdateDownloadInfoList()
+ {
+ // Update a download information list when download is completed
+ if (!isUpdatedInfoList && downloadService.GetDownloadState() == (int)Downloader.State.Completed)
+ {
+ // Clear existing list
+ downloadInfoList.Clear();
+
+ // Add downloaded URL to list
+ downloadInfoList.Add(new DownloadInfo("URL", downloadService.GetUrl()));
+ // Add downloaded content name to list
+ downloadInfoList.Add(new DownloadInfo("Content Name", downloadService.GetContentName()));
+ // Add downloaded content size to list
+ downloadInfoList.Add(new DownloadInfo("Content Size", downloadService.GetContentSize().ToString()));
+ // Add downloaded MIME type to list
+ downloadInfoList.Add(new DownloadInfo("MIME Type", downloadService.GetMimeType()));
+ // Add downloaded path to list
+ downloadInfoList.Add(new DownloadInfo("Download Path", downloadService.GetDownloadedPath()));
+
+ // Update the CollectionView
+ downloadInfoListView.ItemsSource = downloadInfoList;
+
+ isUpdatedInfoList = true;
+ }
+ }
+
+ ///
+ /// Create a new Label container.
+ ///
+ /// View containing styled label
+ private View CreateDownloadInfoLabel()
+ {
+ var labelContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = Resources.LayoutSpacingSmall
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ BackgroundColor = Resources.SurfaceColor,
+ CornerRadius = Resources.CornerRadiusMedium,
+ Padding = Resources.CardPadding
+ };
+
+ var label = new TextLabel
+ {
+ Text = "Download Information",
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ PointSize = Resources.TextSizeLarge,
+ TextColor = Resources.TextColor,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+
+ labelContainer.Add(label);
+ return labelContainer;
+ }
+
+ ///
+ /// Create a new CollectionView
+ /// This CollectionView shows download information list.
+ ///
+ /// CollectionView
+ private CollectionView CreateCollectionView()
+ {
+ var collectionView = new CollectionView
+ {
+ ItemsSource = downloadInfoList,
+ ScrollingDirection = ScrollableBase.Direction.Vertical,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ ItemsLayouter = new LinearLayouter()
+ };
+
+ collectionView.ItemTemplate = new DataTemplate(() =>
+ {
+ var item = new DefaultLinearItem
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ BackgroundColor = Resources.SurfaceColor,
+ CornerRadius = Resources.CornerRadiusSmall,
+ Padding = Resources.CardPadding
+ };
+
+ // Create a horizontal layout for name and value
+ var itemLayout = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ CellPadding = Resources.ItemLayoutSpacing
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent
+ };
+
+ // Name label
+ var nameLabel = new TextLabel
+ {
+ WidthSpecification = Resources.NameLabelWidth,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ PointSize = Resources.TextSizeMedium,
+ TextColor = Resources.TextColor
+ };
+ nameLabel.SetBinding(TextLabel.TextProperty, "Name");
+
+ // Value label
+ var valueLabel = new TextLabel
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ PointSize = Resources.TextSizeMedium,
+ TextColor = Resources.TextColorSecondary,
+ HorizontalAlignment = HorizontalAlignment.Begin
+ };
+ valueLabel.SetBinding(TextLabel.TextProperty, "Value");
+
+ itemLayout.Add(nameLabel);
+ itemLayout.Add(valueLabel);
+
+ item.Add(itemLayout);
+
+ return item;
+ });
+
+ return collectionView;
+ }
+ }
+}
diff --git a/Mobile/Downloader/Downloader/DownloadMainPage.cs b/Mobile/Downloader/Downloader/DownloadMainPage.cs
new file mode 100644
index 000000000..fdf855878
--- /dev/null
+++ b/Mobile/Downloader/Downloader/DownloadMainPage.cs
@@ -0,0 +1,385 @@
+using System;
+using System.Collections.Generic; // Added for IEnumerable<>, List<>
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+
+namespace Downloader
+{
+ ///
+ /// A class for download main page in NUI.
+ ///
+ public class DownloadMainPage : ContentPage
+ {
+ private TextField urlTextField;
+ private Button downloadButton;
+ private Button goToInfoPageButton;
+ private Progress progressBar;
+ private TextLabel progressLabel;
+ private string downloadUrl;
+ private IDownload downloadService;
+
+ ///
+ /// Constructor.
+ ///
+ public DownloadMainPage()
+ {
+ // Initialize the download service
+ downloadService = new DownloadImplementation();
+ InitializeComponent();
+ }
+
+ ///
+ /// Initialize main page.
+ /// Add components and events.
+ ///
+ private void InitializeComponent()
+ {
+ AppBar = new AppBar
+ {
+ Title = "Download",
+ BackgroundColor = Resources.PrimaryColor
+ };
+
+ // Create and add the navigation button to the AppBar using TextLabel instead of Button
+ var infoButtonContainer = new View()
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Horizontal,
+ LinearAlignment = LinearLayout.Alignment.Center,
+ CellPadding = Resources.ZeroSpacing
+ },
+ WidthSpecification = LayoutParamPolicies.WrapContent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ Padding = Resources.AppBarButtonPadding,
+ BackgroundColor = Resources.TransparentColor
+ };
+
+ var infoButtonText = new TextLabel
+ {
+ Text = "Info",
+ TextColor = Resources.WhiteColor,
+ PointSize = Resources.TextSizeMedium,
+ WidthSpecification = LayoutParamPolicies.WrapContent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+
+ infoButtonContainer.Add(infoButtonText);
+ infoButtonContainer.TouchEvent += (s, e) =>
+ {
+ if (e.Touch.GetState(0) == PointStateType.Up)
+ {
+ OnGoToInfoPageButtonClicked(s, null);
+ }
+ return true;
+ };
+
+ goToInfoPageButton = null;
+ AppBar.Actions = new View[] { infoButtonContainer };
+
+ var mainLayout = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = Resources.LayoutSpacingMedium
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.MatchParent,
+ BackgroundColor = Resources.BackgroundColor,
+ Padding = Resources.FormPadding
+ };
+
+ // Create URL input section
+ var urlSection = CreateUrlSection();
+ mainLayout.Add(urlSection);
+
+ // Create download button
+ downloadButton = CreateDownloadButton();
+ mainLayout.Add(downloadButton);
+
+ // Create progress section
+ var progressSection = CreateProgressSection();
+ mainLayout.Add(progressSection);
+
+ Content = mainLayout;
+
+ AddEvent();
+ }
+
+ ///
+ /// Create URL input section.
+ ///
+ /// View containing URL input
+ private View CreateUrlSection()
+ {
+ var section = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = Resources.LayoutSpacingSmall
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ BackgroundColor = Resources.SurfaceColor,
+ CornerRadius = Resources.CornerRadiusMedium,
+ Padding = Resources.CardPadding
+ };
+
+ // URL label
+ var urlLabel = new TextLabel
+ {
+ Text = "Download URL",
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ PointSize = Resources.TextSizeMedium,
+ TextColor = Resources.TextColor
+ };
+ section.Add(urlLabel);
+
+ // Add spacing
+ var spacer1 = new View
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = Resources.SpacingSmall
+ };
+ section.Add(spacer1);
+
+ // URL text field
+ urlTextField = new TextField
+ {
+ PlaceholderText = "Enter URL to download...",
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = Resources.TextFieldHeight,
+ MaxLength = int.MaxValue,
+ BackgroundColor = Resources.SurfaceColor,
+ TextColor = Resources.TextColor,
+ PlaceholderTextColor = Resources.TextColorSecondary,
+ PointSize = Resources.TextSizeMedium,
+ CornerRadius = Resources.CornerRadiusSmall,
+ Padding = Resources.TextFieldPadding
+ };
+ section.Add(urlTextField);
+
+ return section;
+ }
+
+ ///
+ /// Create a new Button to start download.
+ ///
+ /// Button
+ private Button CreateDownloadButton()
+ {
+ var button = new Button(Resources.PrimaryButtonStyle)
+ {
+ Text = "Start Download",
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = Resources.ButtonHeight,
+ BackgroundColor = Resources.PrimaryColor,
+ TextColor = Resources.WhiteColor,
+ PointSize = Resources.TextSizeMedium,
+ CornerRadius = Resources.CornerRadiusMedium
+ };
+
+ return button;
+ }
+
+
+ ///
+ /// Create progress section with progress bar and label.
+ ///
+ /// View containing progress elements
+ private View CreateProgressSection()
+ {
+ var section = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = Resources.LayoutSpacingSmall
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ BackgroundColor = Resources.SurfaceColor,
+ CornerRadius = Resources.CornerRadiusMedium,
+ Padding = Resources.CardPadding
+ };
+
+ // Progress label
+ var progressTitleLabel = new TextLabel
+ {
+ Text = "Download Progress",
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ PointSize = Resources.TextSizeMedium,
+ TextColor = Resources.TextColor
+ };
+ section.Add(progressTitleLabel);
+
+ // Add spacing
+ var spacer2 = new View
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = Resources.SpacingSmall
+ };
+ section.Add(spacer2);
+
+ // Progress bar
+ progressBar = new Progress
+ {
+ CurrentValue = 0.0f,
+ MaxValue = 100.0f,
+ MinValue = 0.0f,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = Resources.ProgressBarHeight,
+ TrackColor = Resources.TrackColor,
+ ProgressColor = Resources.PrimaryColor,
+ CornerRadius = Resources.CornerRadiusSmall
+ };
+ section.Add(progressBar);
+
+ // Add spacing
+ var spacer3 = new View
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = Resources.SpacingSmall
+ };
+ section.Add(spacer3);
+
+ // Progress label
+ progressLabel = new TextLabel
+ {
+ Text = "0 bytes / 0 bytes",
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ PointSize = Resources.TextSizeSmall,
+ TextColor = Resources.TextColorSecondary
+ };
+ section.Add(progressLabel);
+
+ return section;
+ }
+
+ ///
+ /// Register event handlers for entry, button and state callback.
+ ///
+ private void AddEvent()
+ {
+ downloadButton.Clicked += OnButtonClicked;
+ // goToInfoPageButton.Clicked event is already subscribed in InitializeComponent
+ downloadService.DownloadStateChanged += OnStateChanged;
+ downloadService.DownloadProgress += OnProgressbarChanged;
+ }
+
+ ///
+ /// Event handler when download state is changed.
+ ///
+ /// Event sender
+ /// Event arguments including download state
+ private void OnStateChanged(object sender, DownloadStateChangedEventArgs e)
+ {
+ if (string.IsNullOrEmpty(e.stateMsg))
+ return;
+
+ downloadService.DownloadLog("State: " + e.stateMsg);
+
+ // Update UI on main thread
+ NUIApplication.Post(() =>
+ {
+ if (e.stateMsg == "Failed")
+ {
+ downloadButton.Text = e.stateMsg + "! Please start download again.";
+ // If download is failed, dispose a request
+ downloadService.Dispose();
+ // Enable a download button
+ downloadButton.Sensitive = true;
+ }
+ else if (e.stateMsg != downloadButton.Text)
+ {
+ downloadButton.Text = e.stateMsg;
+ }
+ });
+ }
+
+ ///
+ /// Event handler when data is received.
+ ///
+ /// Event sender
+ /// Event arguments including received data size
+ private void OnProgressbarChanged(object sender, DownloadProgressEventArgs e)
+ {
+ if (e.ReceivedSize <= 0)
+ return;
+
+ ulong contentSize = downloadService.GetContentSize();
+
+ // Update UI on main thread
+ NUIApplication.Post(() =>
+ {
+ if (contentSize > 0)
+ {
+ progressBar.CurrentValue = (float)((double)e.ReceivedSize / contentSize * 100.0);
+ }
+ progressLabel.Text = e.ReceivedSize + " bytes / " + contentSize + " bytes";
+ });
+ }
+
+ ///
+ /// Event handler when button is clicked.
+ /// Once a button is clicked and download is started, a button is disabled.
+ ///
+ /// Event sender
+ /// Event arguments
+ private void OnButtonClicked(object sender, ClickedEventArgs e)
+ {
+ downloadUrl = urlTextField.Text;
+
+ downloadService.DownloadLog("Start Download: " + downloadUrl);
+ try
+ {
+ // Start to download content
+ downloadService.StartDownload(downloadUrl);
+ // Disable a button to avoid duplicated request.
+ downloadButton.Sensitive = false;
+ }
+ catch (Exception ex)
+ {
+ downloadService.DownloadLog("Request.Start() is failed: " + ex.Message);
+ // In case download is failed, enable a button.
+ downloadButton.Sensitive = true;
+ }
+ }
+
+ ///
+ /// Event handler for the "Go to Download Info Page" button click.
+ /// Navigates to the DownloadInfoPage.
+ ///
+ /// Event sender
+ /// Event arguments
+ private void OnGoToInfoPageButtonClicked(object sender, ClickedEventArgs e)
+ {
+ this.Navigator?.Push(new DownloadInfoPage(this.downloadService));
+ }
+
+ ///
+ /// Clean up resources when page is disposed.
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Unsubscribe from events
+ if (downloadService != null)
+ {
+ downloadService.DownloadStateChanged -= OnStateChanged;
+ downloadService.DownloadProgress -= OnProgressbarChanged;
+ }
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/Mobile/Downloader/Downloader/Downloader.cs b/Mobile/Downloader/Downloader/Downloader.cs
new file mode 100644
index 000000000..ce0a0aa71
--- /dev/null
+++ b/Mobile/Downloader/Downloader/Downloader.cs
@@ -0,0 +1,37 @@
+using System;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace Downloader
+{
+ class Program : NUIApplication
+ {
+ private Window window;
+ private Navigator navigator;
+
+ protected override void OnCreate()
+ {
+ base.OnCreate();
+ Initialize();
+ }
+
+ void Initialize()
+ {
+ window = NUIApplication.GetDefaultWindow();
+ navigator = window.GetDefaultNavigator();
+
+ var downloadMainPage = new DownloadMainPage();
+ var downloadInfoPage = new DownloadInfoPage();
+
+ navigator.Push(downloadMainPage);
+ }
+
+ static void Main(string[] args)
+ {
+ NUIApplication.IsUsingXaml = false;
+ var app = new Program();
+ app.Run(args);
+ }
+ }
+}
diff --git a/Mobile/Downloader/Downloader/Downloader.csproj b/Mobile/Downloader/Downloader/Downloader.csproj
new file mode 100644
index 000000000..64b007b4e
--- /dev/null
+++ b/Mobile/Downloader/Downloader/Downloader.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ net6.0-tizen9.0
+
+
+
+ portable
+
+
+ None
+
+
+
+
+
+
+
+
diff --git a/Mobile/Downloader/Downloader/DownlodImplementation.cs b/Mobile/Downloader/Downloader/DownlodImplementation.cs
new file mode 100644
index 000000000..189ca3328
--- /dev/null
+++ b/Mobile/Downloader/Downloader/DownlodImplementation.cs
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using Tizen;
+using Tizen.Content.Download;
+
+namespace Downloader
+{
+ ///
+ /// Implementation class of IDownload interface
+ ///
+ public class DownloadImplementation : IDownload
+ {
+ public event EventHandler DownloadStateChanged;
+ public event EventHandler DownloadProgress;
+
+ private Request req;
+
+ // Flag to check download is started or not
+ private bool isStarted = false;
+
+ ///
+ /// Register event handler and call Request.Start() to start download
+ ///
+ /// The URL to download
+ public void StartDownload(string url)
+ {
+ // Create new Request with URL
+ req = new Request(url);
+ // Register state changed event handler
+ req.StateChanged += StateChanged;
+ // Register progress changed event handler
+ req.ProgressChanged += ProgressChanged;
+ isStarted = true;
+ // Start download content
+ req.Start();
+ }
+
+ ///
+ /// Get the URL to download
+ ///
+ /// The URL
+ public string GetUrl()
+ {
+ if (!isStarted)
+ {
+ return "";
+ }
+
+ return req.Url;
+ }
+
+ ///
+ /// Get the content name of the downloaded file
+ ///
+ /// The content name
+ public string GetContentName()
+ {
+ if (!isStarted)
+ {
+ return "";
+ }
+
+ return req.ContentName;
+ }
+
+ ///
+ /// Get the content size of the downloaded file
+ ///
+ /// The content size
+ public ulong GetContentSize()
+ {
+ if (!isStarted)
+ {
+ return 0;
+ }
+
+ return req.ContentSize;
+ }
+
+ ///
+ /// Get the downloaded path
+ ///
+ /// The downloaded path
+ public string GetDownloadedPath()
+ {
+ if (!isStarted)
+ {
+ return "";
+ }
+
+ return req.DownloadedPath;
+ }
+
+ ///
+ /// Get the MIME type of the downloaded content
+ ///
+ /// The MIME type
+ public string GetMimeType()
+ {
+ if (!isStarted)
+ {
+ return "";
+ }
+
+ return req.MimeType;
+ }
+
+ ///
+ /// Get the download state
+ ///
+ /// The download state
+ public int GetDownloadState()
+ {
+ if (!isStarted)
+ {
+ return 0;
+ }
+
+ return (int)req.State;
+ }
+
+ ///
+ /// Dispose the request
+ ///
+ public void Dispose()
+ {
+ if (isStarted)
+ {
+ req.Dispose();
+ isStarted = false;
+ }
+ }
+
+ ///
+ /// Show log message
+ ///
+ /// Log message
+ public void DownloadLog(String message)
+ {
+ // Show received message as DLOG
+ Log.Info("DOWNLOADER", message);
+ }
+
+ ///
+ /// Event handler for download state.
+ ///
+ /// Event sender
+ /// State changed event arguments
+ private void StateChanged(object sender, StateChangedEventArgs e)
+ {
+ String stateMsg = "";
+
+ switch (e.State)
+ {
+ /// Download is ready.
+ case DownloadState.Ready:
+ stateMsg = "Ready";
+ break;
+ /// Content is downloading.
+ case DownloadState.Downloading:
+ stateMsg = "Downloading";
+ break;
+ /// Download is completed.
+ case DownloadState.Completed:
+ stateMsg = "Completed";
+ break;
+ /// Download is failed.
+ case DownloadState.Failed:
+ stateMsg = "Failed";
+ break;
+ /// Download is Paused.
+ case DownloadState.Paused:
+ stateMsg = "Paused";
+ break;
+ /// Download is Queued.
+ case DownloadState.Queued:
+ stateMsg = "Queued";
+ break;
+ /// Download is Canceled.
+ case DownloadState.Canceled:
+ stateMsg = "Canceled";
+ break;
+ default:
+ stateMsg = "";
+ break;
+ }
+
+ // Send current state to event handler
+ DownloadStateChanged?.Invoke(sender, new DownloadStateChangedEventArgs(stateMsg));
+ }
+
+ ///
+ /// Event handler for download progress
+ ///
+ /// Event sender
+ /// Progress changed event arguments
+ private void ProgressChanged(object sender, ProgressChangedEventArgs e)
+ {
+ // If received data is exist, send data size to event handler
+ if (e.ReceivedDataSize > 0)
+ {
+ DownloadProgress?.Invoke(sender, new DownloadProgressEventArgs(e.ReceivedDataSize));
+ }
+ }
+ }
+}
diff --git a/Mobile/Downloader/Downloader/IDownload.cs b/Mobile/Downloader/Downloader/IDownload.cs
new file mode 100644
index 000000000..da69e4c2e
--- /dev/null
+++ b/Mobile/Downloader/Downloader/IDownload.cs
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.ComponentModel;
+
+namespace Downloader
+{
+ ///
+ /// Enumeration for the download states.
+ ///
+ public enum State
+ {
+ ///
+ /// None.
+ ///
+ None = 0,
+ ///
+ /// Ready to download.
+ ///
+ Ready,
+ ///
+ /// Queued to start downloading.
+ ///
+ Queued,
+ ///
+ /// Currently downloading.
+ ///
+ Downloading,
+ ///
+ /// The download is paused and can be resumed.
+ ///
+ Paused,
+ ///
+ /// The download is completed.
+ ///
+ Completed,
+ ///
+ /// The download failed.
+ ///
+ Failed,
+ ///
+ /// A user cancels the download request.
+ ///
+ Canceled
+ }
+
+ ///
+ /// Interface to call Tizen.Content.Download
+ ///
+ public interface IDownload
+ {
+ event EventHandler DownloadStateChanged;
+ event EventHandler DownloadProgress;
+
+ string GetContentName();
+ ulong GetContentSize();
+ string GetDownloadedPath();
+ string GetMimeType();
+ int GetDownloadState();
+ string GetUrl();
+
+ void StartDownload(String url);
+ void Dispose();
+ void DownloadLog(String msg);
+ }
+
+ ///
+ /// An extended EventArgs class for download state
+ ///
+ public class DownloadStateChangedEventArgs : EventArgs
+ {
+ public String stateMsg = "";
+
+ public DownloadStateChangedEventArgs(String msg)
+ {
+ stateMsg = msg;
+ }
+ }
+
+ ///
+ /// An extended EventArgs class for download progress
+ ///
+ public class DownloadProgressEventArgs : EventArgs
+ {
+ public ulong ReceivedSize = 0;
+ public DownloadProgressEventArgs(ulong size)
+ {
+ ReceivedSize = size;
+ }
+ }
+}
diff --git a/Mobile/Downloader/Downloader/Resources.cs b/Mobile/Downloader/Downloader/Resources.cs
new file mode 100644
index 000000000..e055ec664
--- /dev/null
+++ b/Mobile/Downloader/Downloader/Resources.cs
@@ -0,0 +1,98 @@
+using System;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+
+namespace Downloader
+{
+ ///
+ /// Static class to hold common resources, styles, and constants for the application
+ ///
+ internal static class Resources
+ {
+ // Common Colors
+ public static readonly Color PrimaryColor = new Color(0.1294f, 0.5882f, 0.9529f, 1.0f); // #2196F3
+ public static readonly Color PrimaryDarkColor = new Color(0.094f, 0.4627f, 0.8235f, 1.0f); // #1976D2
+ public static readonly Color PrimaryLightColor = new Color(0.2f, 0.7f, 1.0f, 1.0f); // #33B2FF
+ public static readonly Color AccentColor = new Color(0.0f, 0.7373f, 0.8314f, 1.0f); // #00BCE5
+ public static readonly Color BackgroundColor = new Color(0.98f, 0.98f, 0.98f, 1.0f); // #FAFAFA
+ public static readonly Color SurfaceColor = Color.White;
+ public static readonly Color CardColor = Color.White;
+ public static readonly Color TextColor = new Color(0.13f, 0.13f, 0.13f, 1.0f); // #212121
+ public static readonly Color TextColorSecondary = new Color(0.6f, 0.6f, 0.6f, 1.0f); // #999999
+ public static readonly Color TextColorLight = new Color(0.87f, 0.87f, 0.87f, 1.0f); // #DEDEDE
+ public static readonly Color DisabledColor = new Color(0.7f, 0.7f, 0.7f, 1.0f);
+ public static readonly Color SuccessColor = new Color(0.298f, 0.686f, 0.314f, 1.0f); // #4CAF50
+ public static readonly Color ErrorColor = new Color(0.957f, 0.263f, 0.212f, 1.0f); // #F44336
+ public static readonly Color WarningColor = new Color(1.0f, 0.6f, 0.0f, 1.0f); // #FF9800
+ public static readonly Color BorderColor = new Color(0.88f, 0.88f, 0.88f, 1.0f); // #E0E0E0
+ public static readonly Color ShadowColor = new Color(0.0f, 0.0f, 0.0f, 0.1f); // Subtle shadow
+ public static readonly Color WhiteColor = Color.White;
+ public static readonly Color TrackColor = new Color(0.9f, 0.9f, 0.9f, 1.0f);
+ public static readonly Color TransparentColor = Color.Transparent;
+
+ // Common Sizes
+ public const float TextSizeTitle = 28.0f;
+ public const float TextSizeLarge = 24.0f;
+ public const float TextSizeMedium = 20.0f;
+ public const float TextSizeSmall = 16.0f;
+ public const float TextSizeExtraSmall = 14.0f;
+
+ // Common Spacing
+ public const int SpacingExtraSmall = 4;
+ public const int SpacingSmall = 8;
+ public const int SpacingMedium = 16;
+ public const int SpacingLarge = 24;
+ public const int SpacingExtraLarge = 32;
+
+ // Component Heights
+ public const int ButtonHeight = 56;
+ public const int TextFieldHeight = 56;
+ public const int ProgressBarHeight = 8;
+ public const int TableHeight = 200;
+
+ // Component Widths
+ public const int NameLabelWidth = 200;
+
+ // Layout Spacing for specific components
+ public static readonly Size2D ItemLayoutSpacing = new Size2D(16, 0);
+ public static readonly Size2D ZeroSpacing = new Size2D(0, 0);
+
+ // Border Radius
+ public static readonly Vector4 CornerRadiusSmall = new Vector4(4, 4, 4, 4);
+ public static readonly Vector4 CornerRadiusMedium = new Vector4(8, 8, 8, 8);
+ public static readonly Vector4 CornerRadiusLarge = new Vector4(12, 12, 12, 12);
+
+ // Button Styles
+ public static readonly ButtonStyle PrimaryButtonStyle = new ButtonStyle
+ {
+ CornerRadius = CornerRadiusMedium,
+ Padding = new Extents(24, 24, 16, 16)
+ };
+
+ public static readonly ButtonStyle SecondaryButtonStyle = new ButtonStyle
+ {
+ CornerRadius = CornerRadiusMedium,
+ Padding = new Extents(24, 24, 16, 16)
+ };
+
+ public static readonly ButtonStyle AppBarButtonStyle = new ButtonStyle
+ {
+ CornerRadius = CornerRadiusSmall,
+ Padding = new Extents(16, 16, 8, 8)
+ };
+
+ // Layout Spacing
+ public static readonly Size2D LayoutSpacingExtraSmall = new Size2D(0, SpacingExtraSmall);
+ public static readonly Size2D LayoutSpacingSmall = new Size2D(0, SpacingSmall);
+ public static readonly Size2D LayoutSpacingMedium = new Size2D(0, SpacingMedium);
+ public static readonly Size2D LayoutSpacingLarge = new Size2D(0, SpacingLarge);
+
+ // Padding
+ public static readonly Extents PagePadding = new Extents(SpacingLarge, SpacingLarge, SpacingLarge, SpacingLarge);
+ public static readonly Extents FormPadding = new Extents(SpacingLarge, SpacingLarge, SpacingMedium, SpacingMedium);
+ public static readonly Extents CardPadding = new Extents(SpacingMedium, SpacingMedium, SpacingMedium, SpacingMedium);
+ public static readonly Extents SectionPadding = new Extents(SpacingSmall, SpacingSmall, SpacingSmall, SpacingSmall);
+ public static readonly Extents TextFieldPadding = new Extents(16, 16, 12, 12);
+ public static readonly Extents AppBarButtonPadding = new Extents(40, 40, 8, 8);
+ }
+}
diff --git a/Mobile/Downloader/Downloader/shared/res/Downloader.png b/Mobile/Downloader/Downloader/shared/res/Downloader.png
new file mode 100644
index 000000000..9f3cb9860
Binary files /dev/null and b/Mobile/Downloader/Downloader/shared/res/Downloader.png differ
diff --git a/Mobile/Downloader/Downloader/tizen-manifest.xml b/Mobile/Downloader/Downloader/tizen-manifest.xml
new file mode 100644
index 000000000..f7de68944
--- /dev/null
+++ b/Mobile/Downloader/Downloader/tizen-manifest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ http://tizen.org/privilege/download
+
+
+
+ Downloader.png
+
+
+
diff --git a/Mobile/Downloader/Downloader/tizen_dotnet_project.yaml b/Mobile/Downloader/Downloader/tizen_dotnet_project.yaml
new file mode 100644
index 000000000..a64777207
--- /dev/null
+++ b/Mobile/Downloader/Downloader/tizen_dotnet_project.yaml
@@ -0,0 +1,24 @@
+# csproj file path
+csproj_file: Downloader.csproj
+
+# Default profile, Tizen API version
+profile: tizen
+api_version: "9.0"
+
+# Build type [Debug/ Release/ Test]
+build_type: Debug
+
+# Signing profile to be used for Tizen package signing
+# If value is empty: "", active signing profile will be used
+# Else If value is ".", default signing profile will be used
+signing_profile: .
+
+# files monitored for dirty/modified status
+files:
+ - Downloader.csproj
+ - Downloader.cs
+ - tizen-manifest.xml
+ - shared/res/Downloader.png
+
+# project dependencies
+deps: []
diff --git a/Mobile/Downloader/README.md b/Mobile/Downloader/README.md
new file mode 100644
index 000000000..2d5830c18
--- /dev/null
+++ b/Mobile/Downloader/README.md
@@ -0,0 +1,17 @@
+# Downloader
+The Downloader application demonstrates how user can download contents and get the download information.
+
+
+
+
+
+### Verified Version
+* Tizen.NET : 6.0.428
+* Tizen.NET.SDK : 10.0.111
+
+
+### Supported Profile
+* Tizen 10.0 RPI4
+
+### Author
+* Raunak Bhalotia
diff --git a/Mobile/Downloader/Screenshots/Tizen/DownloadInfoPage.png b/Mobile/Downloader/Screenshots/Tizen/DownloadInfoPage.png
new file mode 100755
index 000000000..42b8721e1
Binary files /dev/null and b/Mobile/Downloader/Screenshots/Tizen/DownloadInfoPage.png differ
diff --git a/Mobile/Downloader/Screenshots/Tizen/DownloadMainPage.png b/Mobile/Downloader/Screenshots/Tizen/DownloadMainPage.png
new file mode 100755
index 000000000..ff152c86e
Binary files /dev/null and b/Mobile/Downloader/Screenshots/Tizen/DownloadMainPage.png differ
diff --git a/Mobile/LeScanner/Lescanner.sln b/Mobile/LeScanner/Lescanner.sln
new file mode 100644
index 000000000..b095480a7
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner.sln
@@ -0,0 +1,22 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31005.135
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lescanner", "Lescanner/Lescanner.csproj", "{9fa85abf-475d-4461-9f8a-9ced53b3809c}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9fa85abf-475d-4461-9f8a-9ced53b3809c}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9fa85abf-475d-4461-9f8a-9ced53b3809c}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9fa85abf-475d-4461-9f8a-9ced53b3809c}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9fa85abf-475d-4461-9f8a-9ced53b3809c}.Release|Any CPU.Build.0 = Release|Any CPU
+
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Mobile/LeScanner/Lescanner/AppStyles.cs b/Mobile/LeScanner/Lescanner/AppStyles.cs
new file mode 100644
index 000000000..483941c62
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/AppStyles.cs
@@ -0,0 +1,40 @@
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace Lescanner
+{
+ ///
+ /// Centralized class for defining UI raw style data for a modern, clean application.
+ ///
+ public static class AppStyles
+ {
+ // Color Palette - Refined for elegance
+ public static readonly Color PrimaryColor = new Color(0.2f, 0.5f, 0.9f, 1.0f); // A softer, more sophisticated blue
+ public static readonly Color PrimaryColorDark = new Color(0.15f, 0.4f, 0.75f, 1.0f); // Darker blue for hover/pressed states
+ public static readonly Color SecondaryColor = new Color(0.97f, 0.97f, 0.99f, 1.0f); // A very clean, soft off-white for cards/sections
+ public static readonly Color PageBackgroundColor = new Color(0.99f, 0.99f, 1.0f, 1.0f); // Barely off-white for page backgrounds
+ public static readonly Color TextColorPrimary = new Color(0.15f, 0.15f, 0.2f, 1.0f); // Softer black for primary text
+ public static readonly Color TextColorSecondary = new Color(0.55f, 0.55f, 0.6f, 1.0f); // Muted grey for secondary text
+ public static readonly Color AppBarTextColor = Color.White;
+ public static readonly Color ButtonTextColor = Color.White;
+ public static readonly Color ListBackgroundColor = Color.White; // White for list item backgrounds
+ public static readonly Color BorderColor = new Color(0.88f, 0.88f, 0.92f, 1.0f); // A very light, subtle border color
+
+ // Typography - Reduced for better readability
+ public static readonly float TitlePointSize = 28.0f; // For main page titles
+ public static readonly float HeaderPointSize = 24.0f; // For section headers or list item titles
+ public static readonly float BodyPointSize = 20.0f; // For standard body text, labels
+ public static readonly float DetailPointSize = 18.0f; // For smaller details, status text
+
+ // Spacing & Sizing
+ public static readonly Extents PagePadding = new Extents(24, 24, 24, 24);
+ public static readonly Extents ComponentPadding = new Extents(20, 20, 20, 20);
+ public static readonly Extents ListElementPadding = new Extents(16, 16, 16, 16);
+ public static readonly Extents ListElementMargin = new Extents(0, 0, 12, 0); // Vertical spacing between list items
+ public static readonly Size2D LayoutCellPadding = new Size2D(0, 20); // Vertical spacing in layouts
+ public static readonly Extents ButtonMargin = new Extents(0, 0, 16, 0); // Margin at the bottom of buttons
+ public static readonly float CornerRadius = 10.0f; // Slightly smaller for a more refined look
+ public static readonly float TextFieldBorderWidth = 1.5f; // For text fields (if using a View as a border)
+ }
+}
diff --git a/Mobile/LeScanner/Lescanner/Constants.cs b/Mobile/LeScanner/Lescanner/Constants.cs
new file mode 100644
index 000000000..76d64375a
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/Constants.cs
@@ -0,0 +1,13 @@
+namespace Lescanner
+{
+ ///
+ /// Common constants used throughout the Lescanner application.
+ ///
+ public static class Constants
+ {
+ ///
+ /// Log tag used for all logging in the Lescanner application.
+ ///
+ public const string LOG_TAG = "Lescanner";
+ }
+}
diff --git a/Mobile/LeScanner/Lescanner/DeviceListPage.cs b/Mobile/LeScanner/Lescanner/DeviceListPage.cs
new file mode 100644
index 000000000..5a1da2543
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/DeviceListPage.cs
@@ -0,0 +1,364 @@
+using System;
+using System.Collections.Generic;
+using System.Threading; // Added for SynchronizationContext
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using Tizen.Network.Bluetooth; // For AdapterLeScanResultChangedEventArgs
+
+namespace Lescanner
+{
+ ///
+ /// Page to display a list of discovered BLE devices.
+ ///
+ class DeviceListPage : ContentPage
+ {
+ private TizenBLEService _bleService;
+ private Navigator _navigator;
+ private TextLabel _statusLabel;
+ private ScrollableBase _deviceScrollableList;
+ private View _deviceListContentContainer;
+ // Changed to store TextLabel to update it later
+ private Dictionary _discoveredDeviceLabels = new Dictionary();
+ private bool _isScanActive = false;
+ private SynchronizationContext _uiContext; // Added for main thread dispatching
+ private HashSet _navigatedDevices = new HashSet(); // Track devices that have been navigated
+
+ ///
+ /// Constructor for DeviceListPage.
+ ///
+ /// The BLE service instance.
+ /// The navigator for page navigation.
+ public DeviceListPage(TizenBLEService bleService, Navigator navigator)
+ {
+ _bleService = bleService;
+ _navigator = navigator;
+ _uiContext = SynchronizationContext.Current; // Capture main UI thread context
+
+ AppBar = new AppBar { Title = "Device List" };
+
+ if (AppBar.Title != null)
+ {
+ AppBar.Title = AppBar.Title.PadLeft(20);
+ }
+
+ // Subscribe to BLE service events
+ _bleService.DeviceDiscovered += OnDeviceDiscovered;
+ _bleService.GattConnectionStateChanged += OnGattConnectionStateChanged; // For connection feedback
+ _bleService.DeviceNameRetrieved += OnDeviceNameRetrieved; // Subscribe to name retrieval event
+
+ InitializeComponent();
+
+ // Add back button to AppBar
+ AddBackButtonToAppBar();
+
+ // Start scanning process when the page is created.
+ StartScanningProcess();
+ }
+
+ ///
+ /// Adds a back button to the AppBar.
+ ///
+ private void AddBackButtonToAppBar()
+ {
+ if (AppBar != null)
+ {
+ // Create a back button
+ var backButton = new Button()
+ {
+ Text = "Back",
+ WidthSpecification = 80,
+ HeightSpecification = 40
+ };
+
+ // Set button background color
+ backButton.BackgroundColor = new Color(0.2f, 0.6f, 1.0f, 1.0f);
+ backButton.TextColor = Color.White;
+
+ // Add click handler
+ backButton.Clicked += OnBackButtonClicked;
+
+ // Add button to the AppBar
+ AppBar.NavigationContent = backButton;
+ }
+ }
+
+ ///
+ /// Initializes the UI components for the page.
+ ///
+ private void InitializeComponent()
+ {
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+ Content = mainLayoutContainer;
+
+ _statusLabel = Resources.CreateDetailLabel("Preparing to scan...");
+ _statusLabel.BackgroundColor = Color.Transparent;
+ mainLayoutContainer.Add(_statusLabel);
+
+ _deviceListContentContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(0, AppStyles.LayoutCellPadding.Height) // Use style for spacing
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+
+ _deviceScrollableList = Resources.CreateScrollableList();
+ _deviceScrollableList.Add(_deviceListContentContainer);
+ mainLayoutContainer.Add(_deviceScrollableList);
+ }
+
+
+ private async void StartScanningProcess()
+ {
+ ClearDeviceList();
+ _statusLabel.Text = "Scanning for devices...";
+ _isScanActive = true;
+ await _bleService.StartLeScanAsync();
+
+ // Simulate a 30-second scan duration like the original app
+ await System.Threading.Tasks.Task.Delay(30000);
+
+ if (_isScanActive) // Check if still active (e.g., user didn't navigate away)
+ {
+ await StopScanningProcessAsync();
+ }
+ }
+
+ private async System.Threading.Tasks.Task StopScanningProcessAsync()
+ {
+ if (_isScanActive)
+ {
+ _isScanActive = false;
+ await _bleService.StopLeScanAsync();
+ _statusLabel.Text = "Scan completed. Tap a device to connect.";
+ }
+ }
+
+ private void ClearDeviceList()
+ {
+ _discoveredDeviceLabels.Clear();
+ while (_deviceListContentContainer.ChildCount > 0)
+ {
+ _deviceListContentContainer.Remove(_deviceListContentContainer.GetChildAt(0));
+ }
+ }
+
+ ///
+ /// Event handler for when a new device is discovered by the BLE service.
+ ///
+ private void OnDeviceDiscovered(object sender, AdapterLeScanResultChangedEventArgs e)
+ {
+ if (e.DeviceData != null && !string.IsNullOrEmpty(e.DeviceData.RemoteAddress))
+ {
+ // Ensure UI updates happen on the main thread
+ _uiContext.Post(_ =>
+ {
+ if (!_discoveredDeviceLabels.ContainsKey(e.DeviceData.RemoteAddress))
+ {
+ string initialName = null;
+ try
+ {
+ // Attempt to get the name from scan data using a default packet type (0).
+ // This might resolve the obsolete warning and provide an early name.
+ initialName = e.DeviceData.GetDeviceName(0);
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, $"Error calling GetDeviceName(0) for {e.DeviceData.RemoteAddress}: {ex.Message}. Will use address.");
+ }
+ AddDeviceToList(e.DeviceData.RemoteAddress, initialName);
+ Tizen.Log.Info(Constants.LOG_TAG, $"Device discovered: {e.DeviceData.RemoteAddress}. Initial name from scan: {initialName ?? "N/A"}");
+ }
+ }, null);
+ }
+ }
+
+ private void AddDeviceToList(string deviceAddress, string initialName) // Accept address and initial name
+ {
+ var deviceItemContainer = Resources.CreateListItemContainer();
+ deviceItemContainer.Padding = AppStyles.ListElementPadding;
+
+ // Show initial name if available, otherwise show address.
+ string initialText = string.IsNullOrEmpty(initialName) ? deviceAddress : $"{initialName} ({deviceAddress})";
+ var deviceLabel = Resources.CreateBodyLabel(initialText);
+
+ // Make it look tappable
+ deviceLabel.TouchEvent += (s, args) =>
+ {
+ if (args.Touch.GetState(0) == PointStateType.Down)
+ {
+ OnDeviceTapped(deviceAddress);
+ }
+ return true;
+ };
+
+ _discoveredDeviceLabels.Add(deviceAddress, deviceLabel); // Store the label
+ deviceItemContainer.Add(deviceLabel);
+ _deviceListContentContainer.Add(deviceItemContainer);
+ }
+
+ ///
+ /// Event handler for when a device name is retrieved after connection.
+ /// This handles navigation to ensure we navigate only once with the proper device name.
+ ///
+ private void OnDeviceNameRetrieved(object sender, DeviceNameEventArgs e)
+ {
+ try
+ {
+ _uiContext.Post(_ =>
+ {
+ try
+ {
+ if (_discoveredDeviceLabels.TryGetValue(e.DeviceAddress, out TextLabel deviceLabel))
+ {
+ if (!string.IsNullOrEmpty(e.DeviceName))
+ {
+ deviceLabel.Text = $"{e.DeviceName} ({e.DeviceAddress})";
+ Tizen.Log.Info(Constants.LOG_TAG, $"Updated UI for {e.DeviceAddress} with name: {e.DeviceName}");
+
+ // Safely update status label to show device name was updated
+ try
+ {
+ _statusLabel.Text = $"Updated device name: {e.DeviceName}";
+ }
+ catch (Exception statusEx)
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, $"Could not update status label: {statusEx.Message}");
+ }
+ }
+ else
+ {
+ // Name might be null if not found or failed to read, keep address only.
+ Tizen.Log.Info(Constants.LOG_TAG, $"Failed to retrieve name for {e.DeviceAddress}. UI remains as address.");
+ }
+
+ // Navigate to UUID page only when we have a valid device name (not null)
+ if (!string.IsNullOrEmpty(e.DeviceName) && !_navigatedDevices.Contains(e.DeviceAddress))
+ {
+ _navigatedDevices.Add(e.DeviceAddress);
+ string displayName = deviceLabel.Text;
+ Tizen.Log.Info(Constants.LOG_TAG, $"Device name '{e.DeviceName}' retrieved for {e.DeviceAddress}. Navigating to UUID page with display name: {displayName}");
+ _navigator.Push(new UuidListPage(_bleService, _navigator, e.DeviceAddress, displayName));
+ }
+ else if (string.IsNullOrEmpty(e.DeviceName))
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, $"Device name is null or empty for {e.DeviceAddress}. Waiting for name retrieval before navigation.");
+ }
+ else
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, $"Already navigated for {e.DeviceAddress}. Skipping duplicate navigation.");
+ }
+ }
+ else
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, $"Received name for {e.DeviceAddress} but it's not in the discovered list (maybe navigated away?).");
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Exception in OnDeviceNameRetrieved UI thread for {e.DeviceAddress}: {ex.Message}");
+ }
+ }, null);
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Exception in OnDeviceNameRetrieved for {e.DeviceAddress}: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Handles the tap event on a device in the list.
+ ///
+ /// The address of the tapped device.
+ private async void OnDeviceTapped(string deviceAddress)
+ {
+ try
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, $"Device tapped: {deviceAddress}. Initiating GATT connection.");
+ _statusLabel.Text = $"Connecting to {deviceAddress}...";
+
+ // Stop scanning before attempting connection
+ if (_isScanActive)
+ {
+ await StopScanningProcessAsync();
+ }
+
+ // Clear any previous navigation state for this device
+ _navigatedDevices.Remove(deviceAddress);
+
+ // Initiate the connection. Navigation will be handled by OnDeviceNameRetrieved.
+ bool connectionResult = await _bleService.ConnectGattAsync(deviceAddress);
+
+ if (!connectionResult)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Failed to initiate connection to {deviceAddress}");
+ _statusLabel.Text = $"Failed to connect, retrying...";
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Exception in OnDeviceTapped for {deviceAddress}: {ex.Message}");
+ _statusLabel.Text = $"Connection error: {ex.Message}";
+ }
+ }
+
+ ///
+ /// Event handler for GATT connection state changes (for feedback on this page if needed).
+ ///
+ private void OnGattConnectionStateChanged(object sender, GattConnectionStateChangedEventArgs e)
+ {
+ try
+ {
+ _uiContext.Post(_ =>
+ {
+ try
+ {
+ if (e.IsConnected)
+ {
+ _statusLabel.Text = "GATT Connected. Fetching device name...";
+ // Navigation will be handled by OnDeviceNameRetrieved to ensure we have the device name
+ }
+ else
+ {
+ _statusLabel.Text = "GATT Disconnected or connection failed.";
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Exception in OnGattConnectionStateChanged UI thread: {ex.Message}");
+ }
+ }, null);
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Exception in OnGattConnectionStateChanged: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Event handler for the custom back button click.
+ /// Stops the scanning process and navigates back to the previous page.
+ ///
+ /// The event source.
+ /// The event arguments.
+ private async void OnBackButtonClicked(object sender, ClickedEventArgs e)
+ {
+ Tizen.Log.Info("LescannerDeviceListPage", "Back button clicked. Stopping scan and navigating back.");
+
+ // Stop the scanning process immediately
+ if (_isScanActive)
+ {
+ _statusLabel.Text = "Stopping scan...";
+ await StopScanningProcessAsync();
+ }
+
+ // Navigate back to the previous page
+ _navigator.Pop();
+ }
+
+ }
+}
diff --git a/Mobile/LeScanner/Lescanner/Directory.Build.targets b/Mobile/LeScanner/Lescanner/Directory.Build.targets
new file mode 100644
index 000000000..49b3ab641
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/Directory.Build.targets
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ $([System.IO.Path]::GetDirectoryName($(MSBuildProjectDirectory)))
+
+
+
+
+
+
diff --git a/Mobile/LeScanner/Lescanner/HomePage.cs b/Mobile/LeScanner/Lescanner/HomePage.cs
new file mode 100644
index 000000000..d93c02a4b
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/HomePage.cs
@@ -0,0 +1,74 @@
+using System;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+
+namespace Lescanner
+{
+ ///
+ /// The home page for the LE Scanner application.
+ /// Contains a button to initiate BLE scanning.
+ ///
+ class HomePage : ContentPage
+ {
+ private TizenBLEService _bleService;
+ private Navigator _navigator;
+ private Button _scanButton;
+ private TextLabel _statusLabel;
+
+ ///
+ /// Constructor for HomePage.
+ ///
+ /// The BLE service instance.
+ /// The navigator for page navigation.
+ public HomePage(TizenBLEService bleService, Navigator navigator)
+ {
+ _bleService = bleService;
+ _navigator = navigator;
+ AppBar = new AppBar { Title = "LE Scanner" };
+ InitializeComponent();
+ }
+
+ ///
+ /// Initializes the UI components for the page.
+ ///
+ private void InitializeComponent()
+ {
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+ Content = mainLayoutContainer;
+
+ var titleLabel = Resources.CreateTitleLabel("Bluetooth LE Scanner");
+ mainLayoutContainer.Add(titleLabel);
+
+ _scanButton = Resources.CreatePrimaryButton("BLE Scan");
+ _scanButton.Clicked += OnScanButtonClicked;
+ mainLayoutContainer.Add(_scanButton);
+
+ _statusLabel = Resources.CreateDetailLabel("Tap 'BLE Scan' to start.");
+ _statusLabel.BackgroundColor = Color.Transparent; // Ensure background is transparent
+ mainLayoutContainer.Add(_statusLabel);
+ }
+
+ ///
+ /// Handles the click event for the BLE Scan button.
+ ///
+ /// The event source.
+ /// The event arguments.
+ private void OnScanButtonClicked(object sender, ClickedEventArgs e)
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "BLE Scan button clicked.");
+ if (_bleService.IsBluetoothEnabled())
+ {
+ _statusLabel.Text = "Tap 'BLE Scan' to start.";
+ // Navigate to DeviceListPage. The page will handle starting the scan.
+ _navigator.Push(new DeviceListPage(_bleService, _navigator));
+ }
+ else
+ {
+ _statusLabel.Text = "Please turn on Bluetooth.";
+ // Optionally, show a more persistent message or a Toast.
+ Tizen.Log.Warn(Constants.LOG_TAG, "Bluetooth is not enabled.");
+ }
+ }
+ }
+}
diff --git a/Mobile/LeScanner/Lescanner/Lescanner.cs b/Mobile/LeScanner/Lescanner/Lescanner.cs
new file mode 100644
index 000000000..0122f18ee
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/Lescanner.cs
@@ -0,0 +1,42 @@
+using System;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+
+namespace Lescanner
+{
+ class Lescanner : NUIApplication
+ {
+ private TizenBLEService _bleService;
+
+ ///
+ /// Overrides the base class method to create the window and initialize the application.
+ ///
+ protected override void OnCreate()
+ {
+ base.OnCreate();
+ NUIApplication.IsUsingXaml = false;
+
+ Window window = Window.Default;
+ window.WindowSize = new Size2D(720, 1280); // Default size, can be adjusted
+ window.Title = "LE Scanner";
+ window.SetFullScreen(true); // Make the application fullscreen
+
+ _bleService = new TizenBLEService();
+
+ // Create a Navigator and push the HomePage as the initial page
+ var navigator = window.GetDefaultNavigator();
+ navigator.Push(new HomePage(_bleService, navigator));
+ }
+
+ ///
+ /// The main entry point for the application.
+ ///
+ /// Arguments.
+ static void Main(string[] args)
+ {
+ var app = new Lescanner();
+ app.Run(args);
+ }
+ }
+}
diff --git a/Mobile/LeScanner/Lescanner/Lescanner.csproj b/Mobile/LeScanner/Lescanner/Lescanner.csproj
new file mode 100644
index 000000000..64b007b4e
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/Lescanner.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ net6.0-tizen9.0
+
+
+
+ portable
+
+
+ None
+
+
+
+
+
+
+
+
diff --git a/Mobile/LeScanner/Lescanner/Resources.cs b/Mobile/LeScanner/Lescanner/Resources.cs
new file mode 100644
index 000000000..09aeb0ca8
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/Resources.cs
@@ -0,0 +1,222 @@
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace Lescanner
+{
+ ///
+ /// Static helper class for creating pre-styled UI components using raw data from AppStyles.
+ /// This promotes consistency and reduces boilerplate code in page definitions.
+ ///
+ public static class Resources
+ {
+ ///
+ /// Creates a TextLabel styled as a main title.
+ ///
+ /// The text for the label.
+ /// A styled TextLabel.
+ public static TextLabel CreateTitleLabel(string text)
+ {
+ return new TextLabel
+ {
+ Text = text,
+ TextColor = AppStyles.TextColorPrimary,
+ PointSize = AppStyles.TitlePointSize,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+ }
+
+ ///
+ /// Creates a TextLabel styled for headers or list item titles.
+ ///
+ /// The text for the label.
+ /// A styled TextLabel.
+ public static TextLabel CreateHeaderLabel(string text)
+ {
+ return new TextLabel
+ {
+ Text = text,
+ TextColor = AppStyles.TextColorPrimary,
+ PointSize = AppStyles.HeaderPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+ }
+
+ ///
+ /// Creates a TextLabel styled for body text.
+ ///
+ /// The text for the label.
+ /// A styled TextLabel.
+ public static TextLabel CreateBodyLabel(string text)
+ {
+ return new TextLabel
+ {
+ Text = text,
+ TextColor = AppStyles.TextColorPrimary,
+ PointSize = AppStyles.BodyPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+ }
+
+ ///
+ /// Creates a TextLabel styled for detail or secondary text.
+ ///
+ /// The text for the label.
+ /// A styled TextLabel.
+ public static TextLabel CreateDetailLabel(string text)
+ {
+ return new TextLabel
+ {
+ Text = text,
+ TextColor = AppStyles.TextColorSecondary,
+ PointSize = AppStyles.DetailPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+ }
+
+ ///
+ /// Creates a Button styled as a primary action button.
+ ///
+ /// The text for the button.
+ /// A styled Button.
+ public static Button CreatePrimaryButton(string text)
+ {
+ var button = new Button
+ {
+ Text = text,
+ TextColor = AppStyles.ButtonTextColor,
+ PointSize = AppStyles.BodyPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 72, // Fixed height for better touch experience
+ Margin = AppStyles.ButtonMargin,
+ BackgroundColor = AppStyles.PrimaryColor,
+ CornerRadius = AppStyles.CornerRadius,
+ };
+ return button;
+ }
+
+ ///
+ /// Creates a Button styled as a secondary action button.
+ ///
+ /// The text for the button.
+ /// A styled Button.
+ public static Button CreateSecondaryButton(string text)
+ {
+ var button = new Button
+ {
+ Text = text,
+ TextColor = AppStyles.PrimaryColor,
+ PointSize = AppStyles.BodyPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 72,
+ Margin = AppStyles.ButtonMargin,
+ BackgroundColor = Color.Transparent, // Transparent background
+ CornerRadius = AppStyles.CornerRadius,
+ };
+ // To create a border effect for a secondary button, we can wrap it in a View
+ // or use a background image. For simplicity, we'll keep it without a border for now.
+ return button;
+ }
+
+ ///
+ /// Creates a View styled as a container for list items or cards.
+ ///
+ /// A styled View.
+ public static View CreateListItemContainer()
+ {
+ return new View
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ BackgroundColor = AppStyles.ListBackgroundColor,
+ CornerRadius = AppStyles.CornerRadius,
+ Margin = AppStyles.ListElementMargin,
+ // Padding = AppStyles.ListElementPadding // Padding will be handled by child elements or specific cases
+ };
+ }
+
+ ///
+ /// Creates a ScrollableBase styled for displaying lists.
+ ///
+ /// A styled ScrollableBase.
+ public static ScrollableBase CreateScrollableList()
+ {
+ return new ScrollableBase
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ ScrollingDirection = ScrollableBase.Direction.Vertical,
+ HideScrollbar = false,
+ BackgroundColor = Color.Transparent,
+ };
+ }
+
+ ///
+ /// Creates a main content View with a vertical linear layout.
+ ///
+ /// Optional padding to override the default page padding.
+ /// A styled View.
+ public static View CreateMainLayoutContainer(Extents padding = null)
+ {
+ var layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = AppStyles.LayoutCellPadding
+ };
+ var view = new View
+ {
+ Layout = layout,
+ BackgroundColor = AppStyles.PageBackgroundColor,
+ Padding = padding ?? AppStyles.PagePadding
+ };
+ view.WidthSpecification = LayoutParamPolicies.MatchParent;
+ view.HeightSpecification = LayoutParamPolicies.MatchParent;
+ return view;
+ }
+
+ ///
+ /// Creates a TextField with a modern look, wrapped in a View to simulate a border.
+ ///
+ /// The placeholder text.
+ /// A View containing the styled TextField.
+ public static View CreateStyledTextField(string placeholderText)
+ {
+ var textField = new TextField
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 64, // Fixed height
+ PlaceholderText = placeholderText,
+ BackgroundColor = Color.Transparent, // Transparent, border is handled by parent
+ TextColor = AppStyles.TextColorPrimary,
+ PointSize = AppStyles.BodyPointSize,
+ Padding = new Extents(16, 16, 16, 16), // Internal text padding
+ // Margin is handled by the container
+ };
+
+ var borderContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ HorizontalAlignment = HorizontalAlignment.Begin,
+ VerticalAlignment = VerticalAlignment.Center
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 64, // Match TextField height
+ BackgroundColor = AppStyles.ListBackgroundColor, // White background for the field
+ CornerRadius = AppStyles.CornerRadius,
+ Margin = new Extents(0, 0, 16, 0), // Bottom margin for the container
+ // To simulate a border, we can use a slightly larger background View with a different color
+ // or use a BorderImage if available. For simplicity, we'll rely on CornerRadius for now.
+ // A more robust border would require a custom drawing or a 9-patch image.
+ };
+ borderContainer.Add(textField);
+ return borderContainer;
+ }
+ }
+}
diff --git a/Mobile/LeScanner/Lescanner/TizenBLEService.cs b/Mobile/LeScanner/Lescanner/TizenBLEService.cs
new file mode 100644
index 000000000..d491d2ddd
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/TizenBLEService.cs
@@ -0,0 +1,429 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text; // For Encoding
+using System.Threading.Tasks;
+using Tizen.Network.Bluetooth;
+
+namespace Lescanner
+{
+ ///
+ /// Event arguments for when a device name is retrieved.
+ ///
+ public class DeviceNameEventArgs : EventArgs
+ {
+ public string DeviceAddress { get; }
+ public string DeviceName { get; }
+
+ public DeviceNameEventArgs(string deviceAddress, string deviceName)
+ {
+ DeviceAddress = deviceAddress;
+ DeviceName = deviceName;
+ }
+ }
+
+ ///
+ /// Service class to handle Bluetooth Low Energy operations.
+ /// Encapsulates Tizen.Network.Bluetooth logic.
+ ///
+ public class TizenBLEService
+ {
+ // Event to notify when a new device is discovered
+ public event EventHandler DeviceDiscovered;
+ // Event to notify when GATT connection state changes
+ public event EventHandler GattConnectionStateChanged;
+ // Event to notify when service discovery is complete
+ public event EventHandler> ServicesDiscovered;
+ // Event to notify when device name is retrieved after connection
+ public event EventHandler DeviceNameRetrieved;
+
+ private BluetoothGattClient _gattClient;
+ private bool _isScanning = false;
+ private string _currentConnectedDeviceAddress; // To store address of the connected device for name fetching
+
+ ///
+ /// Gets the address of the currently connected device.
+ ///
+ public string CurrentConnectedDeviceAddress => _currentConnectedDeviceAddress;
+
+ ///
+ /// Gets the currently discovered services for the connected device.
+ ///
+ /// List of service UUIDs, or empty list if not connected or no services discovered.
+ public IEnumerable GetCurrentServices()
+ {
+ if (_gattClient == null)
+ {
+ return new List();
+ }
+
+ try
+ {
+ var services = _gattClient.GetServices();
+ if (services != null)
+ {
+ return services.Select(s => s.Uuid).ToList();
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error getting current services: {ex.Message}");
+ }
+
+ return new List();
+ }
+
+ // Standard UUIDs for Generic Access service and Device Name characteristic
+ private const string GENERIC_ACCESS_SERVICE_UUID = "00001800-0000-1000-8000-00805f9b34fb";
+ private const string DEVICE_NAME_CHARACTERISTIC_UUID = "00002a00-0000-1000-8000-00805f9b34fb";
+
+ ///
+ /// Checks if Bluetooth is enabled on the device.
+ ///
+ /// True if Bluetooth is enabled, false otherwise.
+ public bool IsBluetoothEnabled()
+ {
+ try
+ {
+ return BluetoothAdapter.IsBluetoothEnabled;
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error checking Bluetooth status: {ex.Message}");
+ return false;
+ }
+ }
+
+ ///
+ /// Starts the LE scan process.
+ ///
+ public async Task StartLeScanAsync()
+ {
+ if (_isScanning)
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "Already scanning.");
+ return;
+ }
+
+ if (!IsBluetoothEnabled())
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "Bluetooth is not enabled. Cannot start scan.");
+ return;
+ }
+
+ try
+ {
+ _isScanning = true;
+ BluetoothAdapter.ScanResultChanged += OnScanResultChanged;
+ BluetoothAdapter.StartLeScan();
+ Tizen.Log.Info(Constants.LOG_TAG, "LE Scan started.");
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error starting LE scan: {ex.Message}. The Bluetooth adapter may be in a bad state. Please try restarting Bluetooth or the device.");
+ _isScanning = false;
+ BluetoothAdapter.ScanResultChanged -= OnScanResultChanged;
+ }
+ }
+
+ ///
+ /// Stops the LE scan process.
+ ///
+ public async Task StopLeScanAsync()
+ {
+ if (!_isScanning)
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "Not currently scanning.");
+ return;
+ }
+
+ try
+ {
+ BluetoothAdapter.StopLeScan();
+ Tizen.Log.Info(Constants.LOG_TAG, "LE Scan stopped.");
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error stopping LE scan: {ex.Message}");
+ }
+ finally
+ {
+ _isScanning = false;
+ BluetoothAdapter.ScanResultChanged -= OnScanResultChanged;
+ }
+ }
+
+ ///
+ /// Event handler for BluetoothAdapter.ScanResultChanged.
+ ///
+ private void OnScanResultChanged(object sender, AdapterLeScanResultChangedEventArgs e)
+ {
+ DeviceDiscovered?.Invoke(this, e);
+ }
+
+ private bool _isConnecting = false; // Track connection state
+
+ ///
+ /// Initiates a GATT connection to the specified device address.
+ ///
+ /// The Bluetooth address of the device.
+ /// True if connection initiation was successful, false otherwise.
+ public async Task ConnectGattAsync(string deviceAddress)
+ {
+ if (_isConnecting)
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "Connection already in progress. Please wait.");
+ return false;
+ }
+
+ if (_gattClient != null)
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "A GATT client already exists. Disconnecting first.");
+ await DisconnectGattAsync();
+
+ // Add a small delay to ensure the disconnect is fully processed
+ await Task.Delay(500);
+ }
+
+ try
+ {
+ _isConnecting = true;
+ _currentConnectedDeviceAddress = deviceAddress; // Store for name fetching
+ _gattClient = BluetoothGattClient.CreateClient(deviceAddress);
+ if (_gattClient == null)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, "Failed to create GATT client.");
+ _currentConnectedDeviceAddress = null;
+ _isConnecting = false;
+ return false;
+ }
+ _gattClient.ConnectionStateChanged += OnGattConnectionStateChanged;
+ await _gattClient.ConnectAsync(false); // false for direct connection
+ Tizen.Log.Info(Constants.LOG_TAG, $"GATT connection initiated to {deviceAddress}.");
+ return true;
+ }
+ catch (InvalidOperationException ex) when (ex.Message.Contains("Operation now in progress"))
+ {
+ // This specific error often means a connection attempt is already underway.
+ // Do not dispose of the client yet, let the ConnectionStateChanged event determine the final state.
+ Tizen.Log.Warn(Constants.LOG_TAG, $"GATT connection to {deviceAddress} reported 'Operation now in progress'. Awaiting ConnectionStateChanged event. Message: {ex.Message}");
+ // The client might still be valid, so we don't dispose it here.
+ // Return false to indicate the ConnectAsync call itself had issues.
+ _isConnecting = false;
+ return false;
+ }
+ catch (InvalidOperationException ex) when (ex.Message.Contains("Operation already done"))
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, $"GATT connection to {deviceAddress} reported 'Operation already done'. The connection might already be established. Message: {ex.Message}");
+ _isConnecting = false;
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error connecting GATT to {deviceAddress}: {ex.Message}");
+ _gattClient?.Dispose();
+ _gattClient = null;
+ _currentConnectedDeviceAddress = null;
+ _isConnecting = false;
+ return false;
+ }
+ }
+
+ ///
+ /// Disconnects the current GATT client with a timeout.
+ /// Event is unsubscribed at the beginning of the process to prevent interference with new connections,
+ /// and to ensure the handler is not attached to the client when it's disposed.
+ ///
+ public async Task DisconnectGattAsync()
+ {
+ if (_gattClient == null)
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "No GATT client to disconnect.");
+ return;
+ }
+
+ var clientToDispose = _gattClient;
+
+ try
+ {
+ // Set to null first to prevent reconnection attempts during disconnect
+ _gattClient = null;
+ _currentConnectedDeviceAddress = null;
+
+ // Unsubscribe first to prevent this handler from being called with a disposed object later,
+ // and to avoid issues when creating a new client.
+ clientToDispose.ConnectionStateChanged -= OnGattConnectionStateChanged;
+ Tizen.Log.Info(Constants.LOG_TAG, "Unsubscribed from ConnectionStateChanged event. Proceeding with disconnect.");
+
+ var disconnectTask = clientToDispose.DisconnectAsync();
+ var timeoutTask = Task.Delay(3000); // Reduced to 3 seconds
+
+ if (await Task.WhenAny(disconnectTask, timeoutTask) == timeoutTask)
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "GATT disconnect timed out after 3 seconds. Forcing disposal.");
+ }
+ else
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "GATT disconnected.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error during GATT disconnect: {ex.Message}");
+ }
+ finally
+ {
+ try
+ {
+ clientToDispose?.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error disposing GATT client: {ex.Message}");
+ }
+ }
+ }
+
+ ///
+ /// Event handler for GattClient.ConnectionStateChanged.
+ ///
+ private void OnGattConnectionStateChanged(object sender, GattConnectionStateChangedEventArgs e)
+ {
+ // Robustness check: Ensure the sender is the currently active client.
+ // This prevents processing stale events from a client that was just disposed.
+ if (_gattClient == null || sender != _gattClient)
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "Received ConnectionStateChanged event for a stale or null client. Ignoring.");
+ return;
+ }
+
+ // Robustness check: Ensure event args are valid.
+ if (e == null)
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "ConnectionStateChanged event received with null args. Ignoring.");
+ return;
+ }
+
+ Tizen.Log.Info(Constants.LOG_TAG, $"ConnectionStateChanged: IsConnected = {e.IsConnected}, RemoteAddress = {_gattClient.RemoteAddress}");
+ GattConnectionStateChanged?.Invoke(this, e);
+
+ if (e.IsConnected)
+ {
+ _currentConnectedDeviceAddress = _gattClient.RemoteAddress; // Update address on successful connection
+ _isConnecting = false; // Clear connecting flag on successful connection
+ Tizen.Log.Info(Constants.LOG_TAG, "GATT connected. Discovering services and fetching name.");
+ DiscoverServices();
+ _ = FetchDeviceNameAsync(); // Fire and forget
+ }
+ else
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "GATT disconnected.");
+ _isConnecting = false; // Clear connecting flag on disconnect
+ // Do not set _gattClient to null here, DisconnectGattAsync handles it.
+ // Only clear the connected address.
+ _currentConnectedDeviceAddress = null;
+ }
+ }
+
+
+ ///
+ /// Fetches the device name by reading the Device Name characteristic from the Generic Access service.
+ /// Based on TizenFX source, ReadValueAsync returns a Task and the value is updated on the characteristic object.
+ ///
+ private async Task FetchDeviceNameAsync()
+ {
+ if (_gattClient == null || string.IsNullOrEmpty(_currentConnectedDeviceAddress))
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "GATT client or device address is null. Cannot fetch name.");
+ return;
+ }
+
+ try
+ {
+ BluetoothGattService genericAccessService = _gattClient.GetService(GENERIC_ACCESS_SERVICE_UUID);
+ if (genericAccessService != null)
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "Found Generic Access service.");
+ BluetoothGattCharacteristic deviceNameCharacteristic = genericAccessService.GetCharacteristic(DEVICE_NAME_CHARACTERISTIC_UUID);
+ if (deviceNameCharacteristic != null)
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "Found Device Name characteristic. Initiating read...");
+ // The ReadValueAsync method will complete when the read operation is done.
+ // The value of the characteristic will be updated internally.
+ bool readSuccess = await _gattClient.ReadValueAsync(deviceNameCharacteristic);
+
+ if (readSuccess)
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, "ReadValueAsync reported success. Checking characteristic value.");
+ if (deviceNameCharacteristic.Value != null)
+ {
+ string deviceName = Encoding.UTF8.GetString(deviceNameCharacteristic.Value).TrimEnd('\0');
+ Tizen.Log.Info(Constants.LOG_TAG, $"Successfully fetched device name: {deviceName}");
+ DeviceNameRetrieved?.Invoke(this, new DeviceNameEventArgs(_currentConnectedDeviceAddress, deviceName));
+ }
+ else
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "ReadValueAsync reported success, but characteristic value is null.");
+ DeviceNameRetrieved?.Invoke(this, new DeviceNameEventArgs(_currentConnectedDeviceAddress, null));
+ }
+ }
+ else
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, "ReadValueAsync failed to read the Device Name characteristic.");
+ DeviceNameRetrieved?.Invoke(this, new DeviceNameEventArgs(_currentConnectedDeviceAddress, null));
+ }
+ }
+ else
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "Device Name characteristic not found.");
+ DeviceNameRetrieved?.Invoke(this, new DeviceNameEventArgs(_currentConnectedDeviceAddress, null));
+ }
+ }
+ else
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "Generic Access service not found.");
+ DeviceNameRetrieved?.Invoke(this, new DeviceNameEventArgs(_currentConnectedDeviceAddress, null));
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error fetching device name: {ex.Message}");
+ DeviceNameRetrieved?.Invoke(this, new DeviceNameEventArgs(_currentConnectedDeviceAddress, null));
+ }
+ }
+
+
+ ///
+ /// Discovers services offered by the connected GATT server.
+ ///
+ private void DiscoverServices()
+ {
+ if (_gattClient == null)
+ {
+ Tizen.Log.Warn(Constants.LOG_TAG, "GATT client is null. Cannot discover services.");
+ return;
+ }
+
+ try
+ {
+ IEnumerable services = _gattClient.GetServices();
+ var uuids = new List();
+ if (services != null)
+ {
+ foreach (var service in services)
+ {
+ uuids.Add(service.Uuid);
+ Tizen.Log.Info(Constants.LOG_TAG, $"Discovered service UUID: {service.Uuid}");
+ }
+ }
+ ServicesDiscovered?.Invoke(this, uuids);
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error discovering services: {ex.Message}");
+ ServicesDiscovered?.Invoke(this, new List()); // Notify with empty list on error
+ }
+ }
+
+ }
+}
diff --git a/Mobile/LeScanner/Lescanner/UuidListPage.cs b/Mobile/LeScanner/Lescanner/UuidListPage.cs
new file mode 100644
index 000000000..e9a02a7df
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/UuidListPage.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading; // Added for SynchronizationContext
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using Tizen.Network.Bluetooth; // For GattConnectionStateChangedEventArgs
+
+namespace Lescanner
+{
+ ///
+ /// Page to display a list of discovered service UUIDs for a connected BLE device.
+ ///
+ class UuidListPage : ContentPage
+ {
+ private TizenBLEService _bleService;
+ private Navigator _navigator;
+ private string _deviceAddress;
+ private string _deviceDisplayName; // Added to store the fetched display name
+ private TextLabel _statusLabel;
+ private ScrollableBase _uuidScrollableList;
+ private View _uuidListContentContainer;
+ private SynchronizationContext _uiContext; // Added for main thread dispatching
+
+ ///
+ /// Constructor for UuidListPage.
+ ///
+ /// The BLE service instance.
+ /// The navigator for page navigation.
+ /// The address of the connected device.
+ /// The display name (address or name) of the connected device.
+ public UuidListPage(TizenBLEService bleService, Navigator navigator, string deviceAddress, string displayName)
+ {
+ _bleService = bleService;
+ _navigator = navigator;
+ _deviceAddress = deviceAddress;
+ _deviceDisplayName = displayName ?? deviceAddress; // Fallback to address if displayName is null
+ _uiContext = SynchronizationContext.Current; // Capture main UI thread context
+ AppBar = new AppBar { Title = $"UUIDs for {_deviceDisplayName}" }; // Use display name
+ InitializeComponent();
+
+ // Subscribe to BLE service events
+ _bleService.GattConnectionStateChanged += OnGattConnectionStateChanged;
+ _bleService.ServicesDiscovered += OnServicesDiscovered;
+
+ // Check if we're already connected and services might already be discovered
+ CheckExistingConnectionAndServices();
+ }
+
+ ///
+ /// Override Dispose to properly unsubscribe from events
+ ///
+ protected override void Dispose(DisposeTypes type)
+ {
+ try
+ {
+ if (_bleService != null)
+ {
+ _bleService.GattConnectionStateChanged -= OnGattConnectionStateChanged;
+ _bleService.ServicesDiscovered -= OnServicesDiscovered;
+ Tizen.Log.Info(Constants.LOG_TAG, "Unsubscribed from BLE service events.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error unsubscribing from BLE service events: {ex.Message}");
+ }
+
+ base.Dispose(type);
+ }
+
+ ///
+ /// Initializes the UI components for the page.
+ ///
+ private void InitializeComponent()
+ {
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+ Content = mainLayoutContainer;
+
+ _statusLabel = Resources.CreateDetailLabel($"Connecting to {_deviceDisplayName}..."); // Use display name
+ _statusLabel.BackgroundColor = Color.Transparent;
+ mainLayoutContainer.Add(_statusLabel);
+
+ _uuidListContentContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(0, AppStyles.LayoutCellPadding.Height) // Use style for spacing
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+
+ _uuidScrollableList = Resources.CreateScrollableList();
+ _uuidScrollableList.Add(_uuidListContentContainer);
+ mainLayoutContainer.Add(_uuidScrollableList);
+ }
+
+
+ ///
+ /// Event handler for GATT connection state changes.
+ ///
+ private void OnGattConnectionStateChanged(object sender, GattConnectionStateChangedEventArgs e)
+ {
+ // Ensure UI updates happen on the main thread
+ _uiContext.Post(_ => // Changed from NUIApplication.GetDefaultWindow().Post
+ {
+ if (e.IsConnected)
+ {
+ _statusLabel.Text = $"Connected to {_deviceDisplayName}. Fetching services..."; // Use display name
+ Tizen.Log.Info(Constants.LOG_TAG, $"GATT connected to {_deviceAddress} ({_deviceDisplayName}).");
+ }
+ else
+ {
+ _statusLabel.Text = $"Disconnected from {_deviceDisplayName}."; // Use display name
+ Tizen.Log.Warn(Constants.LOG_TAG, $"GATT disconnected from {_deviceAddress} ({_deviceDisplayName}).");
+ // Optionally, navigate back or show an error.
+ }
+ }, null); // Added null for state parameter
+ }
+
+ ///
+ /// Event handler for when services are discovered.
+ ///
+ private void OnServicesDiscovered(object sender, IEnumerable uuids)
+ {
+ // Ensure UI updates happen on the main thread
+ _uiContext.Post(_ => // Changed from NUIApplication.GetDefaultWindow().Post
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, $"Services discovered event received for {_deviceDisplayName}. Count: {uuids?.Count() ?? 0}");
+ ClearUuidList();
+ if (uuids != null && uuids.Any())
+ {
+ _statusLabel.Text = $"Found {uuids.Count()} services for {_deviceDisplayName}:"; // Use display name
+ foreach (var uuid in uuids)
+ {
+ AddUuidToList(uuid);
+ }
+ }
+ else
+ {
+ _statusLabel.Text = $"No services found for {_deviceDisplayName}."; // Use display name
+ }
+ }, null); // Added null for state parameter
+ }
+
+ private void ClearUuidList()
+ {
+ while (_uuidListContentContainer.ChildCount > 0)
+ {
+ _uuidListContentContainer.Remove(_uuidListContentContainer.GetChildAt(0));
+ }
+ }
+
+ private void AddUuidToList(string uuid)
+ {
+ var uuidItemContainer = Resources.CreateListItemContainer();
+ uuidItemContainer.Padding = AppStyles.ListElementPadding;
+
+ var uuidLabel = Resources.CreateBodyLabel(uuid);
+ uuidLabel.TextColor = AppStyles.TextColorSecondary; // Use secondary color for UUIDs
+
+ uuidItemContainer.Add(uuidLabel);
+ _uuidListContentContainer.Add(uuidItemContainer);
+ }
+
+ ///
+ /// Checks if we're already connected to the device and services have been discovered.
+ /// This handles the case where navigation happens after services are already discovered.
+ ///
+ private void CheckExistingConnectionAndServices()
+ {
+ try
+ {
+ string currentConnectedAddress = _bleService.CurrentConnectedDeviceAddress;
+
+ if (!string.IsNullOrEmpty(currentConnectedAddress) && currentConnectedAddress == _deviceAddress)
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, $"Already connected to {_deviceAddress}. Checking for existing services.");
+
+ // Get current services
+ var existingServices = _bleService.GetCurrentServices();
+
+ if (existingServices != null && existingServices.Any())
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, $"Found {existingServices.Count()} existing services for {_deviceAddress}.");
+ _uiContext.Post(_ =>
+ {
+ ClearUuidList();
+ _statusLabel.Text = $"Found {existingServices.Count()} services for {_deviceDisplayName}:";
+ foreach (var uuid in existingServices)
+ {
+ AddUuidToList(uuid);
+ }
+ }, null);
+ }
+ else
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, $"No existing services found for {_deviceAddress}. Waiting for service discovery.");
+ _uiContext.Post(_ =>
+ {
+ _statusLabel.Text = $"Connected to {_deviceDisplayName}. Discovering services...";
+ }, null);
+ }
+ }
+ else
+ {
+ Tizen.Log.Info(Constants.LOG_TAG, $"Not connected to {_deviceAddress} or connected to different device. Current: {currentConnectedAddress}");
+ _uiContext.Post(_ =>
+ {
+ _statusLabel.Text = $"Connecting to {_deviceDisplayName}...";
+ }, null);
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error(Constants.LOG_TAG, $"Error checking existing connection: {ex.Message}");
+ _uiContext.Post(_ =>
+ {
+ _statusLabel.Text = $"Error checking connection status.";
+ }, null);
+ }
+ }
+ }
+}
diff --git a/Mobile/LeScanner/Lescanner/images/20251024_123329.jpg b/Mobile/LeScanner/Lescanner/images/20251024_123329.jpg
new file mode 100644
index 000000000..89b797a22
Binary files /dev/null and b/Mobile/LeScanner/Lescanner/images/20251024_123329.jpg differ
diff --git a/Mobile/LeScanner/Lescanner/images/20251024_123410.jpg b/Mobile/LeScanner/Lescanner/images/20251024_123410.jpg
new file mode 100644
index 000000000..614664cf7
Binary files /dev/null and b/Mobile/LeScanner/Lescanner/images/20251024_123410.jpg differ
diff --git a/Mobile/LeScanner/Lescanner/images/20251024_123424.jpg b/Mobile/LeScanner/Lescanner/images/20251024_123424.jpg
new file mode 100644
index 000000000..5a3834ca1
Binary files /dev/null and b/Mobile/LeScanner/Lescanner/images/20251024_123424.jpg differ
diff --git a/Mobile/LeScanner/Lescanner/images/20251024_123432.jpg b/Mobile/LeScanner/Lescanner/images/20251024_123432.jpg
new file mode 100644
index 000000000..2724f342f
Binary files /dev/null and b/Mobile/LeScanner/Lescanner/images/20251024_123432.jpg differ
diff --git a/Mobile/LeScanner/Lescanner/images/20251024_123440.jpg b/Mobile/LeScanner/Lescanner/images/20251024_123440.jpg
new file mode 100644
index 000000000..4e1b84679
Binary files /dev/null and b/Mobile/LeScanner/Lescanner/images/20251024_123440.jpg differ
diff --git a/Mobile/LeScanner/Lescanner/shared/res/Lescanner.png b/Mobile/LeScanner/Lescanner/shared/res/Lescanner.png
new file mode 100644
index 000000000..9f3cb9860
Binary files /dev/null and b/Mobile/LeScanner/Lescanner/shared/res/Lescanner.png differ
diff --git a/Mobile/LeScanner/Lescanner/tizen-manifest.xml b/Mobile/LeScanner/Lescanner/tizen-manifest.xml
new file mode 100644
index 000000000..c91aa1057
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/tizen-manifest.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Lescanner.png
+
+
+
+
+
+ http://tizen.org/privilege/bluetooth
+ http://tizen.org/privilege/bluetooth.admin
+
+
+
+
diff --git a/Mobile/LeScanner/Lescanner/tizen_dotnet_project.yaml b/Mobile/LeScanner/Lescanner/tizen_dotnet_project.yaml
new file mode 100644
index 000000000..804608394
--- /dev/null
+++ b/Mobile/LeScanner/Lescanner/tizen_dotnet_project.yaml
@@ -0,0 +1,24 @@
+# csproj file path
+csproj_file: Lescanner.csproj
+
+# Default profile, Tizen API version
+profile: tizen
+api_version: "9.0"
+
+# Build type [Debug/ Release/ Test]
+build_type: Debug
+
+# Signing profile to be used for Tizen package signing
+# If value is empty: "", active signing profile will be used
+# Else If value is ".", default signing profile will be used
+signing_profile: .
+
+# files monitored for dirty/modified status
+files:
+ - Lescanner.csproj
+ - Lescanner.cs
+ - tizen-manifest.xml
+ - shared/res/Lescanner.png
+
+# project dependencies
+deps: []
diff --git a/Mobile/LeScanner/README.md b/Mobile/LeScanner/README.md
new file mode 100644
index 000000000..9dd011fc7
--- /dev/null
+++ b/Mobile/LeScanner/README.md
@@ -0,0 +1,17 @@
+# LeScanner
+The bluetooth allows you to connect BT devices, and exchange data with the remote BT devices.
+
+
+
+
+
+### Verified Version
+* Tizen.NET : 6.0.428
+* Tizen.NET.SDK : 10.0.111
+
+
+### Supported Profile
+* Tizen 10.0 RPI4
+
+### Author
+* Harish Nanu J
diff --git a/Mobile/LeScanner/Screenshots/Tizen/DeviceListPage.png b/Mobile/LeScanner/Screenshots/Tizen/DeviceListPage.png
new file mode 100755
index 000000000..08b6f7494
Binary files /dev/null and b/Mobile/LeScanner/Screenshots/Tizen/DeviceListPage.png differ
diff --git a/Mobile/LeScanner/Screenshots/Tizen/MainPage.png b/Mobile/LeScanner/Screenshots/Tizen/MainPage.png
new file mode 100755
index 000000000..35cb20c0b
Binary files /dev/null and b/Mobile/LeScanner/Screenshots/Tizen/MainPage.png differ
diff --git a/Mobile/NetworkApp/NetworkApp.sln b/Mobile/NetworkApp/NetworkApp.sln
new file mode 100644
index 000000000..0a66d299f
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp.sln
@@ -0,0 +1,22 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31005.135
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkApp", "NetworkApp/NetworkApp.csproj", "{6c9320fb-191e-42f6-a843-d03457242819}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6c9320fb-191e-42f6-a843-d03457242819}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6c9320fb-191e-42f6-a843-d03457242819}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6c9320fb-191e-42f6-a843-d03457242819}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6c9320fb-191e-42f6-a843-d03457242819}.Release|Any CPU.Build.0 = Release|Any CPU
+
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Mobile/NetworkApp/NetworkApp/APInfo.cs b/Mobile/NetworkApp/NetworkApp/APInfo.cs
new file mode 100644
index 000000000..cac317afd
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/APInfo.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace NetworkApp // Changed namespace to match the project
+{
+ ///
+ /// The Wi-Fi AP information
+ ///
+ public class APInfo
+ {
+ ///
+ /// Constructor
+ ///
+ /// ESSID of Wi-Fi AP
+ /// State of Wi-Fi AP
+ public APInfo(String Name, String State)
+ {
+ this.Name = Name;
+ this.State = State;
+ }
+
+ ///
+ /// ESSID of Wi-Fi AP
+ ///
+ public String Name;
+ ///
+ /// State of Wi-Fi AP
+ ///
+ public String State;
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/AppStyles.cs b/Mobile/NetworkApp/NetworkApp/AppStyles.cs
new file mode 100644
index 000000000..6a629ff27
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/AppStyles.cs
@@ -0,0 +1,40 @@
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace NetworkApp
+{
+ ///
+ /// Centralized class for defining UI raw style data for a modern, clean application.
+ ///
+ public static class AppStyles
+ {
+ // Color Palette - Refined for elegance
+ public static readonly Color PrimaryColor = new Color(0.2f, 0.5f, 0.9f, 1.0f); // A softer, more sophisticated blue
+ public static readonly Color PrimaryColorDark = new Color(0.15f, 0.4f, 0.75f, 1.0f); // Darker blue for hover/pressed states
+ public static readonly Color SecondaryColor = new Color(0.97f, 0.97f, 0.99f, 1.0f); // A very clean, soft off-white for cards/sections
+ public static readonly Color PageBackgroundColor = new Color(0.99f, 0.99f, 1.0f, 1.0f); // Barely off-white for page backgrounds
+ public static readonly Color TextColorPrimary = new Color(0.15f, 0.15f, 0.2f, 1.0f); // Softer black for primary text
+ public static readonly Color TextColorSecondary = new Color(0.55f, 0.55f, 0.6f, 1.0f); // Muted grey for secondary text
+ public static readonly Color AppBarTextColor = Color.White;
+ public static readonly Color ButtonTextColor = Color.White;
+ public static readonly Color ListBackgroundColor = Color.White; // White for list item backgrounds
+ public static readonly Color BorderColor = new Color(0.88f, 0.88f, 0.92f, 1.0f); // A very light, subtle border color
+
+ // Typography - Reduced for better readability
+ public static readonly float TitlePointSize = 28.0f; // For main page titles
+ public static readonly float HeaderPointSize = 24.0f; // For section headers or list item titles
+ public static readonly float BodyPointSize = 20.0f; // For standard body text, labels
+ public static readonly float DetailPointSize = 18.0f; // For smaller details, status text
+
+ // Spacing & Sizing
+ public static readonly Extents PagePadding = new Extents(24, 24, 24, 24);
+ public static readonly Extents ComponentPadding = new Extents(20, 20, 20, 20);
+ public static readonly Extents ListElementPadding = new Extents(16, 16, 16, 16);
+ public static readonly Extents ListElementMargin = new Extents(0, 0, 12, 0); // Vertical spacing between list items
+ public static readonly Size2D LayoutCellPadding = new Size2D(0, 20); // Vertical spacing in layouts
+ public static readonly Extents ButtonMargin = new Extents(0, 0, 16, 0); // Margin at the bottom of buttons
+ public static readonly float CornerRadius = 10.0f; // Slightly smaller for a more refined look
+ public static readonly float TextFieldBorderWidth = 1.5f; // For text fields (if using a View as a border)
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/ConnectionPage.cs b/Mobile/NetworkApp/NetworkApp/ConnectionPage.cs
new file mode 100644
index 000000000..b96dd25ed
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/ConnectionPage.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding; // Required for DataTemplate and more advanced binding with CollectionView
+
+namespace NetworkApp
+{
+ ///
+ /// Tizen NUI ContentPage for Network Connection functionalities,
+ /// migrated from the Xamarin.Forms ConnectionPage.
+ ///
+ public class ConnectionPage : ContentPage
+ {
+ private TextLabel titleLabel;
+
+ ///
+ /// Constructor for ConnectionPage.
+ ///
+ public ConnectionPage()
+ {
+ // AppBar (equivalent to Xamarin.Forms Title)
+ AppBar = new AppBar
+ {
+ Title = "Connection" // From ConnectionPage.Title = "Connection"
+ };
+
+ // Initialize UI components and layout
+ InitializeComponents();
+ }
+
+ ///
+ /// Initializes the UI components and layout for the page.
+ /// This method is equivalent to the Xamarin.Forms page's InitializeComponent.
+ ///
+ private void InitializeComponents()
+ {
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+
+ titleLabel = Resources.CreateTitleLabel("Connection Test");
+ mainLayoutContainer.Add(titleLabel);
+
+ var stringSourceList = new List
+ {
+ "Current Connection", "Wi-Fi State", "Cellular State", "IP Address",
+ "Wi-Fi MAC Address", "Proxy Address", "Profile List"
+ };
+
+ var listContentContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(0, 12) // Spacing between list items
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+
+ foreach (var itemText in stringSourceList)
+ {
+ var itemContainer = Resources.CreateListItemContainer();
+ itemContainer.Padding = AppStyles.ListElementPadding;
+
+ var itemLabel = Resources.CreateHeaderLabel(itemText);
+ itemLabel.Name = itemText;
+
+ itemContainer.Add(itemLabel);
+ itemContainer.TouchEvent += OnItemTapped; // Attach event to container
+ listContentContainer.Add(itemContainer);
+ }
+
+ var scrollableList = Resources.CreateScrollableList();
+ scrollableList.HeightSpecification = 700;
+ scrollableList.Add(listContentContainer);
+ mainLayoutContainer.Add(scrollableList);
+
+ Content = mainLayoutContainer;
+ }
+
+ ///
+ /// Handles the tap event on a list item container.
+ ///
+ /// The View container that was tapped.
+ /// Touch event arguments.
+ /// True if the event was consumed, false otherwise.
+ private bool OnItemTapped(object source, TouchEventArgs e)
+ {
+ // We only want to react to the touch start (or end) to avoid multiple actions.
+ // A simple check for TouchState.Down is often enough for a "tap" feel.
+ if (e.Touch.GetState(0) == PointStateType.Down)
+ {
+ var tappedContainer = source as View;
+ if (tappedContainer != null && tappedContainer.ChildCount > 0)
+ {
+ var tappedLabel = tappedContainer.GetChildAt(0) as TextLabel;
+ if (tappedLabel != null)
+ {
+ var selectedItemText = tappedLabel.Name; // Retrieve the text stored in the Name property
+ Tizen.Log.Info("ConnectionPage", $"Item tapped: {selectedItemText}");
+
+ ConnectionOperation operation = ConnectionOperation.CURRENT; // Default operation
+
+ switch (selectedItemText)
+ {
+ case "Current Connection":
+ operation = ConnectionOperation.CURRENT;
+ break;
+ case "Wi-Fi State":
+ operation = ConnectionOperation.WIFISTATE;
+ break;
+ case "Cellular State":
+ operation = ConnectionOperation.CELLULARSTATE;
+ break;
+ case "IP Address":
+ operation = ConnectionOperation.IPADDRESS;
+ break;
+ case "Wi-Fi MAC Address":
+ operation = ConnectionOperation.WIFIMACADDRESS;
+ break;
+ case "Proxy Address":
+ operation = ConnectionOperation.PROXYADDRESS;
+ break;
+ case "Profile List":
+ operation = ConnectionOperation.PROFILELIST;
+ break;
+ }
+
+ var navigator = NUIApplication.GetDefaultWindow().GetDefaultNavigator();
+ navigator.Push(new ConnectionResultPage(operation));
+ }
+ }
+ }
+ return true; // Event was consumed
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/ConnectionResultPage.cs b/Mobile/NetworkApp/NetworkApp/ConnectionResultPage.cs
new file mode 100644
index 000000000..80373fa41
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/ConnectionResultPage.cs
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+
+namespace NetworkApp
+{
+ ///
+ /// Enumeration for ConnectionOperation
+ ///
+ enum ConnectionOperation
+ {
+ CURRENT,
+ WIFISTATE,
+ CELLULARSTATE,
+ IPADDRESS,
+ WIFIMACADDRESS,
+ PROXYADDRESS,
+ PROFILELIST,
+ }
+
+ ///
+ /// The ContentPage to show the result of operation tabbed on CollectionView of ConnectionPage
+ ///
+ class ConnectionResultPage : ContentPage
+ {
+ private TextLabel resultLabel;
+ private ScrollableBase profileScrollableList; // For ProfileList operation
+ private View profileListContentContainer; // Container for profile items
+
+ private ConnectionOperation operation;
+ private TizenConnectionService connectionService; // Added TizenConnectionService field
+
+ ///
+ /// Constructor
+ ///
+ /// ConnectionOperation
+ public ConnectionResultPage(ConnectionOperation op)
+ {
+ AppBar = new AppBar { Title = $"Connection Result: {op}" }; // Dynamic title based on operation
+ operation = op;
+ connectionService = new TizenConnectionService(); // Initialize TizenConnectionService
+
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+ // Use a slightly different padding for this page if needed, or default
+ Content = mainLayoutContainer;
+
+ resultLabel = Resources.CreateBodyLabel(""); // Start with empty text
+ resultLabel.BackgroundColor = Color.Transparent; // Make background transparent
+ resultLabel.MultiLine = true; // Allow multiple lines
+ mainLayoutContainer.Add(resultLabel);
+
+ profileListContentContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(0, 8) // Spacing for profile items
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+
+ profileScrollableList = Resources.CreateScrollableList();
+ profileScrollableList.HeightSpecification = 0; // Initially hidden
+ profileScrollableList.Add(profileListContentContainer);
+ mainLayoutContainer.Add(profileScrollableList);
+
+ Operate(op);
+ }
+
+
+ ///
+ /// Event handler when a refresh button is clicked (if added later)
+ ///
+ /// Event sender
+ /// Event argument
+ public void OnClicked(object sender, EventArgs e)
+ {
+ Operate(operation);
+ }
+
+ ///
+ /// Operate for each requested ConnectionOperation
+ ///
+ /// ConnectionOperation
+ public void Operate(ConnectionOperation op)
+ {
+ try
+ {
+ // Clear previous results
+ resultLabel.Text = "";
+ profileScrollableList.HeightSpecification = 0; // Hide by setting height to 0
+ // Clear previous profile items from the container
+ while (profileListContentContainer.ChildCount > 0)
+ {
+ profileListContentContainer.Remove(profileListContentContainer.GetChildAt(0));
+ }
+
+ switch (op)
+ {
+ // Show the current connected network
+ case ConnectionOperation.CURRENT:
+ CurrentConnection();
+ break;
+ // Show the current Wi-Fi State
+ case ConnectionOperation.WIFISTATE:
+ WiFiState();
+ break;
+ // Show the current Cellular state
+ case ConnectionOperation.CELLULARSTATE:
+ CellularState();
+ break;
+ // Show the IPv4 address and the IPv6 address of the current connection
+ case ConnectionOperation.IPADDRESS:
+ IPAddress();
+ break;
+ // Show the MAC address of Wi-Fi
+ case ConnectionOperation.WIFIMACADDRESS:
+ WiFiMACAddress();
+ break;
+ // Show the proxy address
+ case ConnectionOperation.PROXYADDRESS:
+ ProxyAddress();
+ break;
+ // Show the connection profiles
+ case ConnectionOperation.PROFILELIST:
+ // ProfileList is async, so it needs to be awaited
+ _ = ProfileList(); // Fire and forget, or properly await if UI needs to wait
+ break;
+ }
+ }
+ // C# API throws NotSupportedException if the API is not supported
+ catch (NotSupportedException)
+ {
+ resultLabel.Text = "The operation is not supported on this device";
+ Tizen.Log.Error("Xaml2NUI", "ConnectionResultPage: NotSupportedException");
+ }
+ catch (Exception ex)
+ {
+ resultLabel.Text = ex.ToString();
+ Tizen.Log.Error("Xaml2NUI", $"ConnectionResultPage: Exception - {ex.Message}");
+ }
+ }
+
+ ///
+ /// Show the current connected network
+ ///
+ private void CurrentConnection()
+ {
+ try
+ {
+ string currentType = connectionService.CurrentType;
+ string currentState = connectionService.CurrentState;
+ // Check if the type or state indicates no connection
+ if (string.IsNullOrEmpty(currentType) || currentType.Equals("None", StringComparison.OrdinalIgnoreCase) ||
+ currentState.Equals("Disconnected", StringComparison.OrdinalIgnoreCase))
+ {
+ resultLabel.Text = "No active network connection.";
+ }
+ else
+ {
+ resultLabel.Text = $"Current Connection\nType: {currentType}\nState: {currentState}";
+ }
+ Tizen.Log.Info("Xaml2NUI", "ConnectionResultPage: CurrentConnection retrieved.");
+ }
+ catch (Exception ex)
+ {
+ // Check for a more specific exception if the service throws one when not connected
+ // For now, a general exception is caught and assumed to mean 'not connected' or an error.
+ resultLabel.Text = "No active network connection or an error occurred.";
+ Tizen.Log.Error("Xaml2NUI", $"ConnectionResultPage: CurrentConnection error - {ex.ToString()}");
+ }
+ }
+
+ ///
+ /// Show the current Wi-Fi State
+ ///
+ private void WiFiState()
+ {
+ try
+ {
+ string wifiState = connectionService.WiFiState;
+ if (wifiState.Equals("Deactivated", StringComparison.OrdinalIgnoreCase) ||
+ wifiState.Equals("Disconnected", StringComparison.OrdinalIgnoreCase) ||
+ wifiState.Equals("Not Available", StringComparison.OrdinalIgnoreCase))
+ {
+ resultLabel.Text = "Wi-Fi is not connected or active.";
+ }
+ else
+ {
+ resultLabel.Text = $"Wi-Fi State: {wifiState}";
+ }
+ Tizen.Log.Info("Xaml2NUI", "ConnectionResultPage: WiFiState retrieved.");
+ }
+ catch (Exception ex)
+ {
+ resultLabel.Text = "Wi-Fi state is not available or an error occurred.";
+ Tizen.Log.Error("Xaml2NUI", $"ConnectionResultPage: WiFiState error - {ex.ToString()}");
+ }
+ }
+
+ ///
+ /// Show the current Cellular state
+ ///
+ private void CellularState()
+ {
+ try
+ {
+ string cellularState = connectionService.CellularState;
+ Tizen.Log.Info("Xaml2NUI", $"ConnectionResultPage: Raw cellularState value: '{cellularState}'"); // Log raw value
+
+ // Trim whitespace for comparison
+ var trimmedCellularState = cellularState?.Trim();
+
+ if (string.IsNullOrEmpty(trimmedCellularState) ||
+ trimmedCellularState.Equals("Error", StringComparison.OrdinalIgnoreCase) || // Specific check for "Error" string
+ trimmedCellularState.Equals("Deactivated", StringComparison.OrdinalIgnoreCase) ||
+ trimmedCellularState.Equals("Disconnected", StringComparison.OrdinalIgnoreCase) ||
+ trimmedCellularState.Equals("Not Available", StringComparison.OrdinalIgnoreCase) ||
+ trimmedCellularState.Equals("OutOfService", StringComparison.OrdinalIgnoreCase)) // Common for cellular
+ {
+ resultLabel.Text = "Cellular is not connected or active.";
+ Tizen.Log.Info("Xaml2NUI", "ConnectionResultPage: Cellular state interpreted as 'not connected'.");
+ }
+ else
+ {
+ resultLabel.Text = $"Cellular State: {cellularState}"; // Display original, non-trimmed value if not an error state
+ Tizen.Log.Info("Xaml2NUI", "ConnectionResultPage: Cellular state displayed as is.");
+ }
+ }
+ catch (Exception ex)
+ {
+ // Log the detailed exception message to see if it contains "Error"
+ string exceptionMessage = ex.Message;
+ Tizen.Log.Error("Xaml2NUI", $"ConnectionResultPage: CellularState exception caught. Message: '{exceptionMessage}', StackTrace: {ex.ToString()}");
+
+ // Check if the generic exception message itself is what's being shown
+ if (exceptionMessage.Contains("Error", StringComparison.OrdinalIgnoreCase))
+ {
+ resultLabel.Text = "Cellular is not connected or active (service error).";
+ }
+ else
+ {
+ resultLabel.Text = "Cellular state is not available or an error occurred.";
+ }
+ }
+ }
+
+
+ ///
+ /// Show the IPv4 address and the IPv6 address of the current connection
+ ///
+ private void IPAddress()
+ {
+ try
+ {
+ string ipv4Address = connectionService.IPv4Address;
+ string ipv6Address = connectionService.IPv6Address;
+ resultLabel.Text = $"IPv4 Address: {ipv4Address}\nIPv6 Address: {ipv6Address}";
+ Tizen.Log.Info("Xaml2NUI", "ConnectionResultPage: IPAddress retrieved.");
+ }
+ catch (Exception ex)
+ {
+ resultLabel.Text = $"Error getting IP address: {ex.Message}";
+ Tizen.Log.Error("Xaml2NUI", $"ConnectionResultPage: IPAddress error - {ex.ToString()}");
+ }
+ }
+
+ ///
+ /// Show the MAC address of Wi-Fi
+ ///
+ private void WiFiMACAddress()
+ {
+ try
+ {
+ string wifiMACAddress = connectionService.WiFiMACAddress;
+ resultLabel.Text = $"Wi-Fi MAC Address: {wifiMACAddress}";
+ Tizen.Log.Info("Xaml2NUI", "ConnectionResultPage: WiFiMACAddress retrieved.");
+ }
+ catch (Exception ex)
+ {
+ resultLabel.Text = $"Error getting Wi-Fi MAC address: {ex.Message}";
+ Tizen.Log.Error("Xaml2NUI", $"ConnectionResultPage: WiFiMACAddress error - {ex.ToString()}");
+ }
+ }
+
+ ///
+ /// Show the proxy address
+ ///
+ private void ProxyAddress()
+ {
+ try
+ {
+ string proxyAddress = connectionService.ProxyAddress;
+ resultLabel.Text = $"Proxy Address: {proxyAddress}";
+ Tizen.Log.Info("Xaml2NUI", "ConnectionResultPage: ProxyAddress retrieved.");
+ }
+ catch (Exception ex)
+ {
+ resultLabel.Text = $"Error getting proxy address: {ex.Message}";
+ Tizen.Log.Error("Xaml2NUI", $"ConnectionResultPage: ProxyAddress error - {ex.ToString()}");
+ }
+ }
+
+ ///
+ /// Show the connection profiles
+ ///
+ private async Task ProfileList()
+ {
+ try
+ {
+ resultLabel.Text = "Getting profile list...";
+ Tizen.Log.Info("Xaml2NUI", "ConnectionResultPage: ProfileList fetching.");
+
+ List list = await connectionService.GetProfileListAsync();
+
+ // Update profileListView on the UI thread
+ var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
+ if (uiTaskScheduler != null)
+ {
+ await Task.Factory.StartNew(() =>
+ {
+ if (list != null && list.Count > 0 && !(list.Count == 1 && list[0] == "Error"))
+ {
+ foreach (var profileName in list)
+ {
+ var profileItemContainer = Resources.CreateListItemContainer();
+ profileItemContainer.Padding = AppStyles.ListElementPadding;
+
+ var profileLabel = Resources.CreateBodyLabel(profileName);
+
+ profileItemContainer.Add(profileLabel);
+ profileListContentContainer.Add(profileItemContainer);
+ }
+ profileScrollableList.HeightSpecification = LayoutParamPolicies.WrapContent; // Show the list
+ resultLabel.Text = $"Found {list.Count} profiles:"; // Update label text
+ }
+ else
+ {
+ resultLabel.Text = "No profiles found or error occurred.";
+ profileScrollableList.HeightSpecification = 0; // Ensure it's hidden
+ }
+ }, CancellationToken.None, TaskCreationOptions.None, uiTaskScheduler);
+ }
+ else
+ {
+ Tizen.Log.Warn("Xaml2NUI", "ProfileList: No SynchronizationContext found, UI update might be unsafe.");
+ if (list != null && list.Count > 0 && !(list.Count == 1 && list[0] == "Error"))
+ {
+ foreach (var profileName in list)
+ {
+ var profileItemContainer = Resources.CreateListItemContainer();
+ profileItemContainer.Padding = AppStyles.ListElementPadding;
+
+ var profileLabel = Resources.CreateBodyLabel(profileName);
+
+ profileItemContainer.Add(profileLabel);
+ profileListContentContainer.Add(profileItemContainer);
+ }
+ profileScrollableList.HeightSpecification = LayoutParamPolicies.WrapContent; // Show the list
+ resultLabel.Text = $"Found {list.Count} profiles:"; // Update label text
+ }
+ else
+ {
+ resultLabel.Text = "No profiles found or error occurred.";
+ profileScrollableList.HeightSpecification = 0; // Ensure it's hidden
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ resultLabel.Text = $"Error getting profile list: {ex.Message}";
+ Tizen.Log.Error("Xaml2NUI", $"ConnectionResultPage: ProfileList error - {ex.ToString()}");
+ }
+ }
+
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/Directory.Build.targets b/Mobile/NetworkApp/NetworkApp/Directory.Build.targets
new file mode 100644
index 000000000..49b3ab641
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/Directory.Build.targets
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+ $([System.IO.Path]::GetDirectoryName($(MSBuildProjectDirectory)))
+
+
+
+
+
+
diff --git a/Mobile/NetworkApp/NetworkApp/MainMenuPage.cs b/Mobile/NetworkApp/NetworkApp/MainMenuPage.cs
new file mode 100644
index 000000000..762f457b1
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/MainMenuPage.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+
+namespace NetworkApp
+{
+ ///
+ /// The main menu page for navigating to different network functionalities.
+ ///
+ class MainMenuPage : ContentPage
+ {
+ ///
+ /// Constructor
+ ///
+ public MainMenuPage()
+ {
+ AppBar = new AppBar { Title = "Network App Menu" };
+ InitializeComponent();
+ }
+
+ ///
+ /// Initialize MainMenuPage. Add components and events.
+ ///
+ private void InitializeComponent()
+ {
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+ // AppBar background is typically handled by the theme, but we can set it if needed.
+ // AppBar.BackgroundColor = AppStyles.PrimaryColor;
+
+ var titleLabel = Resources.CreateTitleLabel("Select a Network Option");
+ mainLayoutContainer.Add(titleLabel);
+
+ var menuOptions = new List
+ {
+ "Connection",
+ "Wi-Fi",
+ "Wi-Fi Direct"
+ };
+
+ var listContentContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(0, 12) // Spacing between menu items
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+
+ foreach (var optionText in menuOptions)
+ {
+ var itemContainer = Resources.CreateListItemContainer();
+ // Customize list item for menu: use primary color for background to make it pop
+ itemContainer.BackgroundColor = AppStyles.PrimaryColor;
+ itemContainer.Padding = AppStyles.ListElementPadding; // Add padding inside the item
+
+ var itemLabel = Resources.CreateHeaderLabel(optionText); // Use Header style for menu items
+ itemLabel.TextColor = AppStyles.AppBarTextColor; // Ensure text is white on blue background
+ itemLabel.HorizontalAlignment = HorizontalAlignment.Center; // Center text in menu items
+ itemLabel.Name = optionText;
+
+ itemContainer.Add(itemLabel);
+ itemContainer.TouchEvent += OnMenuItemTapped; // Attach event to container
+ listContentContainer.Add(itemContainer);
+ }
+
+ var scrollableList = Resources.CreateScrollableList();
+ scrollableList.Add(listContentContainer);
+ mainLayoutContainer.Add(scrollableList);
+
+ Content = mainLayoutContainer;
+ }
+
+ ///
+ /// Handles the tap event on a menu item container.
+ ///
+ /// The View container that was tapped.
+ /// Touch event arguments.
+ /// True if the event was consumed, false otherwise.
+ private bool OnMenuItemTapped(object source, TouchEventArgs e)
+ {
+ if (e.Touch.GetState(0) == PointStateType.Down)
+ {
+ var tappedContainer = source as View;
+ if (tappedContainer != null && tappedContainer.ChildCount > 0)
+ {
+ var tappedLabel = tappedContainer.GetChildAt(0) as TextLabel;
+ if (tappedLabel != null)
+ {
+ var selectedItemText = tappedLabel.Name;
+ Tizen.Log.Info("MainMenuPage", $"Item tapped: {selectedItemText}");
+
+ var navigator = NUIApplication.GetDefaultWindow().GetDefaultNavigator();
+ if (navigator != null)
+ {
+ switch (selectedItemText)
+ {
+ case "Connection":
+ navigator.Push(new ConnectionPage());
+ break;
+ case "Wi-Fi":
+ navigator.Push(new WiFiPage());
+ break;
+ case "Wi-Fi Direct":
+ navigator.Push(new WiFiDirectPage());
+ break;
+ }
+ }
+ }
+ }
+ }
+ return true; // Event was consumed
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/NetworkApp.cs b/Mobile/NetworkApp/NetworkApp/NetworkApp.cs
new file mode 100644
index 000000000..f0c25e767
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/NetworkApp.cs
@@ -0,0 +1,35 @@
+using System;
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace NetworkApp
+{
+ class Program : NUIApplication
+ {
+
+ private Window window;
+ protected override void OnCreate()
+ {
+ base.OnCreate();
+ Initialize();
+ }
+
+ void Initialize()
+ {
+ window = NUIApplication.GetDefaultWindow();
+ var navigator = window.GetDefaultNavigator(); ;
+
+ // Create the initial page for the application
+ var initialPage = new MainMenuPage();
+ navigator.Push(initialPage);
+ }
+
+ static void Main(string[] args)
+ {
+ NUIApplication.IsUsingXaml = false;
+ var app = new Program();
+ app.Run(args);
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/NetworkApp.csproj b/Mobile/NetworkApp/NetworkApp/NetworkApp.csproj
new file mode 100644
index 000000000..066195631
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/NetworkApp.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6.0-tizen9.0
+
+
+
+ portable
+
+
+ None
+
+
+
+
+
+
+
+
+
+
diff --git a/Mobile/NetworkApp/NetworkApp/Resources.cs b/Mobile/NetworkApp/NetworkApp/Resources.cs
new file mode 100644
index 000000000..b75470232
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/Resources.cs
@@ -0,0 +1,222 @@
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace NetworkApp
+{
+ ///
+ /// Static helper class for creating pre-styled UI components using raw data from AppStyles.
+ /// This promotes consistency and reduces boilerplate code in page definitions.
+ ///
+ public static class Resources
+ {
+ ///
+ /// Creates a TextLabel styled as a main title.
+ ///
+ /// The text for the label.
+ /// A styled TextLabel.
+ public static TextLabel CreateTitleLabel(string text)
+ {
+ return new TextLabel
+ {
+ Text = text,
+ TextColor = AppStyles.TextColorPrimary,
+ PointSize = AppStyles.TitlePointSize,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+ }
+
+ ///
+ /// Creates a TextLabel styled for headers or list item titles.
+ ///
+ /// The text for the label.
+ /// A styled TextLabel.
+ public static TextLabel CreateHeaderLabel(string text)
+ {
+ return new TextLabel
+ {
+ Text = text,
+ TextColor = AppStyles.TextColorPrimary,
+ PointSize = AppStyles.HeaderPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+ }
+
+ ///
+ /// Creates a TextLabel styled for body text.
+ ///
+ /// The text for the label.
+ /// A styled TextLabel.
+ public static TextLabel CreateBodyLabel(string text)
+ {
+ return new TextLabel
+ {
+ Text = text,
+ TextColor = AppStyles.TextColorPrimary,
+ PointSize = AppStyles.BodyPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+ }
+
+ ///
+ /// Creates a TextLabel styled for detail or secondary text.
+ ///
+ /// The text for the label.
+ /// A styled TextLabel.
+ public static TextLabel CreateDetailLabel(string text)
+ {
+ return new TextLabel
+ {
+ Text = text,
+ TextColor = AppStyles.TextColorSecondary,
+ PointSize = AppStyles.DetailPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+ }
+
+ ///
+ /// Creates a Button styled as a primary action button.
+ ///
+ /// The text for the button.
+ /// A styled Button.
+ public static Button CreatePrimaryButton(string text)
+ {
+ var button = new Button
+ {
+ Text = text,
+ TextColor = AppStyles.ButtonTextColor,
+ PointSize = AppStyles.BodyPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 72, // Fixed height for better touch experience
+ Margin = AppStyles.ButtonMargin,
+ BackgroundColor = AppStyles.PrimaryColor,
+ CornerRadius = AppStyles.CornerRadius,
+ };
+ return button;
+ }
+
+ ///
+ /// Creates a Button styled as a secondary action button.
+ ///
+ /// The text for the button.
+ /// A styled Button.
+ public static Button CreateSecondaryButton(string text)
+ {
+ var button = new Button
+ {
+ Text = text,
+ TextColor = AppStyles.PrimaryColor,
+ PointSize = AppStyles.BodyPointSize,
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 72,
+ Margin = AppStyles.ButtonMargin,
+ BackgroundColor = Color.Transparent, // Transparent background
+ CornerRadius = AppStyles.CornerRadius,
+ };
+ // To create a border effect for a secondary button, we can wrap it in a View
+ // or use a background image. For simplicity, we'll keep it without a border for now.
+ return button;
+ }
+
+ ///
+ /// Creates a View styled as a container for list items or cards.
+ ///
+ /// A styled View.
+ public static View CreateListItemContainer()
+ {
+ return new View
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ BackgroundColor = AppStyles.ListBackgroundColor,
+ CornerRadius = AppStyles.CornerRadius,
+ Margin = AppStyles.ListElementMargin,
+ // Padding = AppStyles.ListElementPadding // Padding will be handled by child elements or specific cases
+ };
+ }
+
+ ///
+ /// Creates a ScrollableBase styled for displaying lists.
+ ///
+ /// A styled ScrollableBase.
+ public static ScrollableBase CreateScrollableList()
+ {
+ return new ScrollableBase
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ ScrollingDirection = ScrollableBase.Direction.Vertical,
+ HideScrollbar = false,
+ BackgroundColor = Color.Transparent,
+ };
+ }
+
+ ///
+ /// Creates a main content View with a vertical linear layout.
+ ///
+ /// Optional padding to override the default page padding.
+ /// A styled View.
+ public static View CreateMainLayoutContainer(Extents padding = null)
+ {
+ var layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = AppStyles.LayoutCellPadding
+ };
+ var view = new View
+ {
+ Layout = layout,
+ BackgroundColor = AppStyles.PageBackgroundColor,
+ Padding = padding ?? AppStyles.PagePadding
+ };
+ view.WidthSpecification = LayoutParamPolicies.MatchParent;
+ view.HeightSpecification = LayoutParamPolicies.MatchParent;
+ return view;
+ }
+
+ ///
+ /// Creates a TextField with a modern look, wrapped in a View to simulate a border.
+ ///
+ /// The placeholder text.
+ /// A View containing the styled TextField.
+ public static View CreateStyledTextField(string placeholderText)
+ {
+ var textField = new TextField
+ {
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 64, // Fixed height
+ PlaceholderText = placeholderText,
+ BackgroundColor = Color.Transparent, // Transparent, border is handled by parent
+ TextColor = AppStyles.TextColorPrimary,
+ PointSize = AppStyles.BodyPointSize,
+ Padding = new Extents(16, 16, 16, 16), // Internal text padding
+ // Margin is handled by the container
+ };
+
+ var borderContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ HorizontalAlignment = HorizontalAlignment.Begin,
+ VerticalAlignment = VerticalAlignment.Center
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = 64, // Match TextField height
+ BackgroundColor = AppStyles.ListBackgroundColor, // White background for the field
+ CornerRadius = AppStyles.CornerRadius,
+ Margin = new Extents(0, 0, 16, 0), // Bottom margin for the container
+ // To simulate a border, we can use a slightly larger background View with a different color
+ // or use a BorderImage if available. For simplicity, we'll rely on CornerRadius for now.
+ // A more robust border would require a custom drawing or a 9-patch image.
+ };
+ borderContainer.Add(textField);
+ return borderContainer;
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/TizenConnectionService.cs b/Mobile/NetworkApp/NetworkApp/TizenConnectionService.cs
new file mode 100644
index 000000000..61e8b628d
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/TizenConnectionService.cs
@@ -0,0 +1,272 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Tizen.Network.Connection;
+
+namespace NetworkApp
+{
+ ///
+ /// Tizen-specific Connection service implementation for the Xaml2NUI project.
+ /// This class directly uses Tizen.Network.Connection APIs.
+ ///
+ public class TizenConnectionService
+ {
+ private IEnumerable profileList;
+
+ ///
+ /// Constructor
+ ///
+ public TizenConnectionService()
+ {
+ }
+
+ ///
+ /// The type of the current profile for data connection (Disconnected, Wi-Fi, Cellular, etc.)
+ ///
+ public String CurrentType
+ {
+ get
+ {
+ try
+ {
+ // Call Tizen C# API
+ // Check if CurrentConnection is null, which can happen if disconnected
+ var currentConn = ConnectionManager.CurrentConnection;
+ if (currentConn == null)
+ {
+ return "Not Connected";
+ }
+ return currentConn.Type.ToString();
+ }
+ catch (System.InvalidOperationException ex) when (ex.Message.Contains("not initialized") || ex.Message.Contains("not available"))
+ {
+ Console.WriteLine($"[TizenConnectionService] CurrentType not available: {ex.Message}");
+ return "Not Available";
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenConnectionService] CurrentType get failed: {ex.Message}");
+ return "Error"; // Fallback for unexpected errors
+ }
+ }
+ }
+
+ ///
+ /// The state of the current profile for data connection
+ ///
+ public String CurrentState
+ {
+ get
+ {
+ try
+ {
+ // Call Tizen C# API
+ var currentConn = ConnectionManager.CurrentConnection;
+ if (currentConn == null)
+ {
+ return "Not Connected";
+ }
+ return currentConn.State.ToString();
+ }
+ catch (System.InvalidOperationException ex) when (ex.Message.Contains("not initialized") || ex.Message.Contains("not available"))
+ {
+ Console.WriteLine($"[TizenConnectionService] CurrentState not available: {ex.Message}");
+ return "Not Available";
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenConnectionService] CurrentState get failed: {ex.Message}");
+ return "Error";
+ }
+ }
+ }
+
+ ///
+ /// The state of the Wi-Fi connection
+ ///
+ public String WiFiState
+ {
+ get
+ {
+ try
+ {
+ // Call Tizen C# API
+ return ConnectionManager.WiFiState.ToString();
+ }
+ catch (System.InvalidOperationException ex) when (ex.Message.Contains("not initialized") || ex.Message.Contains("not available"))
+ {
+ Console.WriteLine($"[TizenConnectionService] WiFiState not available: {ex.Message}");
+ return "Not Available";
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenConnectionService] WiFiState get failed: {ex.Message}");
+ return "Error";
+ }
+ }
+ }
+
+ ///
+ /// The state of the Cellular connection
+ ///
+ public String CellularState
+ {
+ get
+ {
+ try
+ {
+ // Call Tizen C# API
+ return ConnectionManager.CellularState.ToString();
+ }
+ catch (Exception ex) // Catch any exception to prevent raw error from showing
+ {
+ Console.WriteLine($"[TizenConnectionService] CellularState get failed: {ex.Message}");
+ // Check for common error messages to provide more specific feedback
+ if (ex.Message.Contains("not supported"))
+ {
+ return "Not Supported";
+ }
+ if (ex.Message.Contains("not available") || ex.Message.Contains("not initialized"))
+ {
+ return "Not Available";
+ }
+ return "Error"; // Generic fallback for other exceptions
+ }
+ }
+ }
+
+ ///
+ /// The IPv4 address of the current connection
+ ///
+ public String IPv4Address
+ {
+ get
+ {
+ try
+ {
+ // Call Tizen C# API
+ var ipAddress = ConnectionManager.GetIPAddress(AddressFamily.IPv4);
+ return ipAddress?.ToString() ?? "N/A";
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenConnectionService] IPv4Address get failed: {ex.Message}");
+ return "Error";
+ }
+ }
+ }
+
+ ///
+ /// The IPv6 address of the current connection
+ ///
+ public String IPv6Address
+ {
+ get
+ {
+ try
+ {
+ // Call Tizen C# API
+ var ipAddress = ConnectionManager.GetIPAddress(AddressFamily.IPv6);
+ return ipAddress?.ToString() ?? "N/A";
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenConnectionService] IPv6Address get failed: {ex.Message}");
+ return "Error";
+ }
+ }
+ }
+
+ ///
+ /// The MAC address of the Wi-Fi
+ ///
+ public String WiFiMACAddress
+ {
+ get
+ {
+ try
+ {
+ // Call Tizen C# API
+ var macAddress = ConnectionManager.GetMacAddress(ConnectionType.WiFi);
+ return macAddress?.ToString() ?? "N/A";
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenConnectionService] WiFiMACAddress get failed: {ex.Message}");
+ return "Error";
+ }
+ }
+ }
+
+ ///
+ /// The proxy address of the current connection
+ ///
+ public String ProxyAddress
+ {
+ get
+ {
+ try
+ {
+ // Call Tizen C# API
+ var proxyAddress = ConnectionManager.GetProxy(AddressFamily.IPv4);
+ if (proxyAddress == null || string.IsNullOrEmpty(proxyAddress.ToString()))
+ {
+ return "No proxy configured";
+ }
+ return proxyAddress.ToString();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenConnectionService] ProxyAddress get failed: {ex.Message}");
+ // Check for common error messages
+ if (ex.Message.Contains("not supported"))
+ {
+ return "Not Supported";
+ }
+ if (ex.Message.Contains("not available") || ex.Message.Contains("not initialized"))
+ {
+ return "Not Available";
+ }
+ return "Error"; // Generic fallback
+ }
+ }
+ }
+
+ ///
+ /// Get profile list as a list of profile name
+ ///
+ /// List of profile names
+ public async Task> GetProfileListAsync()
+ {
+ try
+ {
+ // Get profile list
+ await GetProfileListInternalAsync();
+ List result = new List();
+ if (profileList != null)
+ {
+ foreach (var item in profileList)
+ {
+ // Add name of connection profiles to a list
+ result.Add(item.Name);
+ }
+ }
+ return result;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenConnectionService] GetProfileListAsync failed: {ex.Message}");
+ return new List { "Error" };
+ }
+ }
+
+ ///
+ /// Gets the list of the profile internally
+ ///
+ private async Task GetProfileListInternalAsync()
+ {
+ // Call Tizen C# API
+ profileList = await ConnectionProfileManager.GetProfileListAsync(ProfileListType.Registered);
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/TizenWiFiDirectService.cs b/Mobile/NetworkApp/NetworkApp/TizenWiFiDirectService.cs
new file mode 100644
index 000000000..5b8e0b725
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/TizenWiFiDirectService.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Tizen.Network.WiFiDirect;
+
+namespace NetworkApp
+{
+ ///
+ /// Tizen-specific Wi-Fi Direct service implementation.
+ /// This class directly uses Tizen.Network.WiFiDirect APIs.
+ ///
+ public class TizenWiFiDirectService
+ {
+ private bool isScanning = false;
+ private List discoveredDevices = new List();
+ public bool IsInitialized { get; private set; } = false;
+
+ // Event to notify when a new device is discovered or the list is updated
+ public event EventHandler DevicesUpdated;
+
+ public TizenWiFiDirectService()
+ {
+ try
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", "Constructor called. Attempting to initialize.");
+ // Subscribe to Tizen's native events
+ WiFiDirectManager.DiscoveryStateChanged += OnDiscoveryStateChanged;
+ WiFiDirectManager.DeviceStateChanged += OnDeviceStateChanged;
+ IsInitialized = true;
+ Tizen.Log.Info("TizenWiFiDirectService", "Successfully initialized and subscribed to events.");
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error("TizenWiFiDirectService", $"Initialization failed: {ex.Message}. This might be due to Wi-Fi Direct not being supported or enabled on the device.");
+ IsInitialized = false;
+ // Ensure we are not subscribed if an exception occurred mid-subscription
+ try { WiFiDirectManager.DiscoveryStateChanged -= OnDiscoveryStateChanged; } catch { /* ignore */ }
+ try { WiFiDirectManager.DeviceStateChanged -= OnDeviceStateChanged; } catch { /* ignore */ }
+ }
+ }
+
+ ///
+ /// Starts scanning for Wi-Fi Direct devices.
+ ///
+ /// True if scan initiation was successful, false otherwise.
+ public bool StartScan()
+ {
+ try
+ {
+ if (isScanning)
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", "Scan already in progress.");
+ return true;
+ }
+
+ Tizen.Log.Info("TizenWiFiDirectService", "StartScan called.");
+ // Clear previous results
+ discoveredDevices.Clear();
+
+ // Check if the Wi-Fi Direct is deactivated
+ if (WiFiDirectManager.State == WiFiDirectState.Deactivated)
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", "Wi-Fi Direct is deactivated. Activating...");
+ // Activation is asynchronous. Discovery will start in OnDeviceStateChanged.
+ WiFiDirectManager.Activate();
+ // isScanning will be set to true once activation is complete and discovery starts.
+ }
+ else
+ {
+ // Already activated, start discovery directly
+ StartDiscoveryInternal();
+ }
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error("TizenWiFiDirectService", $"StartScan failed: {ex.Message}");
+ isScanning = false;
+ return false;
+ }
+ }
+
+ ///
+ /// Stops scanning for Wi-Fi Direct devices.
+ ///
+ /// True if scan stop was successful, false otherwise.
+ public bool StopScan()
+ {
+ try
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", "StopScan called.");
+ WiFiDirectManager.CancelDiscovery();
+ // isScanning will be set to false in OnDiscoveryStateChanged
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error("TizenWiFiDirectService", $"StopScan failed: {ex.Message}");
+ return false;
+ }
+ }
+
+ ///
+ /// Gets the list of currently discovered devices.
+ ///
+ /// A list of WiFiDirectDevice objects.
+ public List GetDiscoveredDevices()
+ {
+ return new List(discoveredDevices);
+ }
+
+ private void StartDiscoveryInternal()
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", "StartDiscoveryInternal called.");
+ try
+ {
+ WiFiDirectManager.StartDiscovery(false, 0); // synchronous = false, channel = 0 (for all channels)
+ isScanning = true; // Set scanning state after initiating discovery
+ Tizen.Log.Info("TizenWiFiDirectService", "StartDiscovery called.");
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error("TizenWiFiDirectService", $"StartDiscoveryInternal failed: {ex.Message}");
+ isScanning = false;
+ }
+ }
+
+ private void OnDiscoveryStateChanged(object sender, DiscoveryStateChangedEventArgs e)
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", $"OnDiscoveryStateChanged: {e.DiscoveryState}");
+ if (e.DiscoveryState == WiFiDirectDiscoveryState.Found)
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", "Discovery found peers.");
+ UpdateDiscoveredPeers();
+ }
+ else
+ {
+ if (isScanning)
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", $"Discovery state changed from 'Found' to '{e.DiscoveryState}'. Setting isScanning to false.");
+ isScanning = false;
+ }
+ }
+ }
+
+ private void OnDeviceStateChanged(object sender, DeviceStateChangedEventArgs e)
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", $"OnDeviceStateChanged: {e.DeviceState}");
+ if (e.DeviceState == WiFiDirectDeviceState.Activated)
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", "Wi-Fi Direct activated. Starting discovery.");
+ StartDiscoveryInternal();
+ }
+ else if (e.DeviceState == WiFiDirectDeviceState.Deactivated)
+ {
+ isScanning = false;
+ discoveredDevices.Clear();
+ DevicesUpdated?.Invoke(this, new WiFiDirectDeviceEventArgs(discoveredDevices));
+ Tizen.Log.Info("TizenWiFiDirectService", "Wi-Fi Direct deactivated. Scan stopped and list cleared.");
+ }
+ }
+
+ private void UpdateDiscoveredPeers()
+ {
+ try
+ {
+ IEnumerable peerList = WiFiDirectManager.GetDiscoveredPeers();
+ if (peerList != null)
+ {
+ var newDeviceList = peerList.Select(p => new WiFiDirectDevice { Name = p.Name }).ToList();
+
+ if (!discoveredDevices.SequenceEqual(newDeviceList, new WiFiDirectDeviceComparer()))
+ {
+ discoveredDevices = newDeviceList;
+ Tizen.Log.Info("TizenWiFiDirectService", $"Discovered {discoveredDevices.Count} peers. Notifying UI.");
+ DevicesUpdated?.Invoke(this, new WiFiDirectDeviceEventArgs(discoveredDevices));
+ }
+ else
+ {
+ Tizen.Log.Info("TizenWiFiDirectService", "Peer list has not changed.");
+ }
+ }
+ else
+ {
+ if (discoveredDevices.Any())
+ {
+ discoveredDevices.Clear();
+ DevicesUpdated?.Invoke(this, new WiFiDirectDeviceEventArgs(discoveredDevices));
+ }
+ Tizen.Log.Info("TizenWiFiDirectService", "GetDiscoveredPeers returned null.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error("TizenWiFiDirectService", $"UpdateDiscoveredPeers failed: {ex.Message}");
+ }
+ }
+ }
+
+ // Custom EventArgs for passing a list of discovered devices
+ public class WiFiDirectDeviceEventArgs : EventArgs
+ {
+ public List Devices { get; }
+
+ public WiFiDirectDeviceEventArgs(List devices)
+ {
+ Devices = devices;
+ }
+ }
+
+ // Class representing a Wi-Fi Direct device for UI purposes.
+ // Maps relevant info from Tizen.Network.WiFiDirect.WiFiDirectPeer
+ public class WiFiDirectDevice
+ {
+ public string Name { get; set; }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+
+ // Helper class to compare lists of WiFiDirectDevice
+ public class WiFiDirectDeviceComparer : IEqualityComparer
+ {
+ public bool Equals(WiFiDirectDevice x, WiFiDirectDevice y)
+ {
+ if (object.ReferenceEquals(x, y)) return true;
+ if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null)) return false;
+ return x.Name == y.Name;
+ }
+
+ public int GetHashCode(WiFiDirectDevice obj)
+ {
+ if (obj == null) return 0;
+ return obj.Name == null ? 0 : obj.Name.GetHashCode();
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/TizenWiFiService.cs b/Mobile/NetworkApp/NetworkApp/TizenWiFiService.cs
new file mode 100644
index 000000000..0784bbc8a
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/TizenWiFiService.cs
@@ -0,0 +1,285 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Tizen.Network.WiFi;
+
+// Note: APInfo class should be available or defined in your project.
+// It was part of the original Xamarin project (NetworkApp/APInfo.cs).
+// If it's not in the Xaml2NUI project, you'll need to add it.
+// For now, I'll assume it exists or will be added.
+
+namespace NetworkApp
+{
+ ///
+ /// Tizen-specific Wi-Fi service implementation for the Xaml2NUI project.
+ /// This class directly uses Tizen.Network.WiFi APIs.
+ ///
+ public class TizenWiFiService
+ {
+ private IEnumerable apList = null;
+
+ ///
+ /// Constructor
+ ///
+ public TizenWiFiService()
+ {
+ apList = new List();
+ // TODO: Consider if explicit Wi-Fi state change event handling is needed here,
+ // similar to what might have been implicitly managed by Xamarin's DependencyService.
+ // For example, WiFiManager.ConnectionStateChanged event, etc.
+ }
+
+ ///
+ /// Call WiFiManager.ActivateAsync() to turn on Wi-Fi interface
+ ///
+ /// Task to do ActivateAsync
+ public async Task Activate()
+ {
+ Console.WriteLine($"[TizenWiFiService] Activate()");
+ try
+ {
+ await WiFiManager.ActivateAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenWiFiService] Activate failed: {ex.Message}");
+ // Consider re-throwing or handling as appropriate for the UI
+ throw;
+ }
+ }
+
+ ///
+ /// Call WiFiManager.DeactivateAsync() to turn off Wi-Fi interface
+ ///
+ /// Task to do DeactivateAsync
+ public async Task Deactivate()
+ {
+ Console.WriteLine($"[TizenWiFiService] Deactivate()");
+ try
+ {
+ await WiFiManager.DeactivateAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenWiFiService] Deactivate failed: {ex.Message}");
+ throw;
+ }
+ }
+
+ ///
+ /// Call WiFiManager.ScanAsync() to scan
+ ///
+ /// Task to do ScanAsync
+ public async Task Scan()
+ {
+ Console.WriteLine($"[TizenWiFiService] Scan()");
+ try
+ {
+ await WiFiManager.ScanAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenWiFiService] Scan failed: {ex.Message}");
+ throw;
+ }
+ }
+
+ ///
+ /// Call WiFiManager.GetFoundAPs() to get scan result and return a list that contains Wi-Fi AP information
+ ///
+ /// scan result by a list of Wi-Fi AP information
+ public List ScanResult()
+ {
+ Console.WriteLine($"[TizenWiFiService] ScanResult()");
+ try
+ {
+ apList = WiFiManager.GetFoundAPs();
+ return GetAPList();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenWiFiService] ScanResult failed: {ex.Message}");
+ return null; // Or return an empty list
+ }
+ }
+
+ ///
+ /// Call WiFiAP.ConnectAsync() to connect the Wi-Fi AP
+ ///
+ /// ESSID of Wi-Fi AP to connect
+ /// password of Wi-Fi AP to connect
+ /// Task to do ConnectAsync
+ public async Task Connect(String essid, String password)
+ {
+ Console.WriteLine($"[TizenWiFiService] Connect() ESSID: {essid}, Password: {(string.IsNullOrEmpty(password) ? "N/A" : "****")}");
+ WiFiAP ap = FindAP(essid);
+ if (ap == null)
+ {
+ var ex = new ArgumentException($"Cannot find AP with ESSID: {essid}");
+ Console.WriteLine($"[TizenWiFiService] Connect failed: {ex.Message}");
+ throw ex;
+ }
+
+ if (password.Length > 0)
+ {
+ // The Tizen API may have changed; KeyManagement property is no longer available.
+ // SetPassphrase should handle security type internally or ignore password for open networks.
+ ap.SecurityInformation.SetPassphrase(password);
+ }
+ try
+ {
+ await ap.ConnectAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenWiFiService] Connect to {essid} failed: {ex.Message}");
+ throw;
+ }
+ }
+
+ ///
+ /// Call WiFiAP.DisconnectAsync() to disconnect the Wi-Fi AP
+ ///
+ /// ESSID of Wi-Fi AP to disconnect
+ /// Task to do DisconnectAsync
+ public async Task Disconnect(String essid)
+ {
+ Console.WriteLine($"[TizenWiFiService] Disconnect() ESSID: {essid}");
+ WiFiAP ap = FindAP(essid);
+ if (ap == null)
+ {
+ var ex = new ArgumentException($"Cannot find AP with ESSID: {essid} to disconnect.");
+ Console.WriteLine($"[TizenWiFiService] Disconnect failed: {ex.Message}");
+ throw ex;
+ }
+ try
+ {
+ await ap.DisconnectAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenWiFiService] Disconnect from {essid} failed: {ex.Message}");
+ throw;
+ }
+ }
+
+ ///
+ /// Call WiFiAP.ForgetAP() to forget the Wi-Fi AP
+ ///
+ /// ESSID of Wi-Fi AP to forget
+ public void Forget(String essid)
+ {
+ Console.WriteLine($"[TizenWiFiService] Forget() ESSID: {essid}");
+ WiFiAP ap = FindAP(essid);
+ if (ap == null)
+ {
+ Console.WriteLine($"[TizenWiFiService] Forget failed: Can't find AP {essid}");
+ return;
+ }
+ try
+ {
+ ap.ForgetAP();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenWiFiService] Forget {essid} failed: {ex.Message}");
+ throw;
+ }
+ }
+
+ ///
+ /// Find a WiFiAP instance
+ ///
+ /// ESSID of Wi-Fi AP to find from apList
+ /// WiFiAP instance with the ESSID
+ private WiFiAP FindAP(String essid)
+ {
+ Console.WriteLine($"[TizenWiFiService] FindAP() ESSID: {essid}");
+ // Ensure apList is populated if it's empty or stale.
+ // The original implementation called ScanResult() here, which might be too slow
+ // if called frequently (e.g., for connect/disconnect).
+ // Consider if apList should be refreshed by a Scan call initiated by the UI.
+ // For now, mirroring original behavior:
+ if (apList == null || !apList.GetEnumerator().MoveNext()) // Basic check if list is empty
+ {
+ ScanResult(); // This will update apList
+ }
+
+ foreach (var item in apList)
+ {
+ // Ensure NetworkInformation and Essid are not null
+ if (item?.NetworkInformation?.Essid != null)
+ {
+ Console.WriteLine($"[TizenWiFiService] FindAP() Checking AP\t{item.NetworkInformation.Essid}");
+ if (item.NetworkInformation.Essid.Equals(essid))
+ {
+ return item;
+ }
+ }
+ }
+ Console.WriteLine($"[TizenWiFiService] FindAP() AP with ESSID {essid} not found in current list.");
+ return null;
+ }
+
+ ///
+ /// Check if Wi-Fi is powered on
+ ///
+ /// True if Wi-Fi is on. False, otherwise
+ public bool IsActive()
+ {
+ Console.WriteLine($"[TizenWiFiService] IsActive()");
+ try
+ {
+ return WiFiManager.IsActive;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[TizenWiFiService] IsActive check failed: {ex.Message}");
+ return false; // Default to false on error
+ }
+ }
+
+ ///
+ /// Get the ESSID of the AP that this device is connected to
+ ///
+ /// ESSID of the connected Wi-Fi AP
+ public String ConnectedAP()
+ {
+ Console.WriteLine($"[TizenWiFiService] ConnectedAP()");
+ try
+ {
+ WiFiAP connectedAp = WiFiManager.GetConnectedAP();
+ return connectedAp?.NetworkInformation?.Essid; // Use null conditional for safety
+ }
+ catch (Exception ex)
+ {
+ // This can throw if no AP is connected, depending on Tizen API version/behavior
+ Console.WriteLine($"[TizenWiFiService] ConnectedAP failed: {ex.Message}");
+ return null; // Or string.Empty
+ }
+ }
+
+ ///
+ /// Get a list of scanned Wi-Fi APs
+ ///
+ /// List of Wi-Fi AP information
+ public List GetAPList()
+ {
+ List apInfoList = new List();
+ if (apList == null)
+ {
+ Console.WriteLine("[TizenWiFiService] GetAPList() apList is null.");
+ return apInfoList; // Return empty list
+ }
+
+ foreach (var item in apList)
+ {
+ if (item?.NetworkInformation != null)
+ {
+ apInfoList.Add(new APInfo(item.NetworkInformation.Essid, item.NetworkInformation.ConnectionState.ToString()));
+ }
+ }
+ return apInfoList;
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/WiFiDirectPage.cs b/Mobile/NetworkApp/NetworkApp/WiFiDirectPage.cs
new file mode 100644
index 000000000..af2cc2b92
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/WiFiDirectPage.cs
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+
+namespace NetworkApp
+{
+ ///
+ /// The ContentPage for testing Tizen.Network.WiFiDirect C# API
+ ///
+ class WiFiDirectPage : ContentPage
+ {
+ private Button scanButton;
+ private ScrollableBase scanScrollableList;
+ private View scanListContentContainer;
+ private TextLabel resultLabel;
+
+ private TizenWiFiDirectService wifiDirectService;
+
+ ///
+ /// Constructor
+ ///
+ public WiFiDirectPage()
+ {
+ AppBar = new AppBar { Title = "WiFiDirect" };
+ InitializeComponent();
+ }
+
+ ///
+ /// Initialize WiFiDirectPage. Add components and events.
+ ///
+ private void InitializeComponent()
+ {
+ wifiDirectService = new TizenWiFiDirectService();
+
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+ // WiFiDirectPage can use the default light theme from AppStyles
+
+ var titleLabel = Resources.CreateTitleLabel("Wi-Fi Direct Test");
+ mainLayoutContainer.Add(titleLabel);
+
+ scanButton = Resources.CreatePrimaryButton("Start Scan");
+ scanButton.Clicked += OnClicked;
+ mainLayoutContainer.Add(scanButton);
+
+ if (!wifiDirectService.IsInitialized)
+ {
+ Tizen.Log.Error("Xaml2NUI", "WiFiDirectPage: TizenWiFiDirectService failed to initialize.");
+ // resultLabel will be created below, set text after creation
+ scanButton.IsEnabled = false;
+ }
+ else
+ {
+ wifiDirectService.DevicesUpdated += OnDevicesUpdated;
+ Tizen.Log.Info("Xaml2NUI", "WiFiDirectPage: TizenWiFiDirectService initialized successfully.");
+ }
+
+ scanListContentContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(0, 8) // Less vertical padding for device list items
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+
+ scanScrollableList = Resources.CreateScrollableList();
+ scanScrollableList.Add(scanListContentContainer);
+ mainLayoutContainer.Add(scanScrollableList);
+
+ resultLabel = Resources.CreateDetailLabel(""); // Start with empty text
+ resultLabel.BackgroundColor = Color.Transparent; // Make background transparent
+ resultLabel.HorizontalAlignment = HorizontalAlignment.Center; // Center status text
+ if (!wifiDirectService.IsInitialized)
+ {
+ resultLabel.Text = "Wi-Fi Direct service could not be initialized. It may not be supported or enabled on this device.";
+ }
+ mainLayoutContainer.Add(resultLabel);
+
+ Content = mainLayoutContainer;
+ }
+
+ ///
+ /// Event handler when button is clicked
+ ///
+ /// Event sender
+ /// Event argument
+ private void OnClicked(object sender, EventArgs e)
+ {
+ try
+ {
+ if (scanButton.Text.Equals("Start Scan"))
+ {
+ if (wifiDirectService.StartScan())
+ {
+ scanButton.Text = "Stop Scanning";
+ resultLabel.Text = "Scanning for devices...";
+ // ClearDeviceList is no longer needed as UpdateDeviceList handles clearing.
+ // Explicitly clear by sending an empty list if desired, or rely on first DevicesUpdated event.
+ // For now, we rely on the first DevicesUpdated event to clear/update the list.
+ UpdateDeviceList(new List()); // Clear list immediately
+ }
+ else
+ {
+ resultLabel.Text = "Failed to start scan.";
+ }
+ }
+ else // "Stop Scanning"
+ {
+ if (wifiDirectService.StopScan())
+ {
+ scanButton.Text = "Start Scan";
+ resultLabel.Text = "Scan stopped.";
+ }
+ else
+ {
+ resultLabel.Text = "Failed to stop scan.";
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ resultLabel.Text = $"Error: {ex.Message}";
+ Tizen.Log.Error("Xaml2NUI", $"WiFiDirectPage: OnClicked Exception - {ex.ToString()}");
+ }
+ }
+
+ ///
+ /// Event handler when the list of discovered devices is updated via TizenWiFiDirectService
+ ///
+ /// Event sender (TizenWiFiDirectService)
+ /// Event argument containing the list of discovered devices
+ private void OnDevicesUpdated(object sender, WiFiDirectDeviceEventArgs e)
+ {
+ Tizen.Log.Info("Xaml2NUI", $"WiFiDirectPage: Devices updated. Count: {e.Devices.Count}");
+ UpdateDeviceList(e.Devices);
+ }
+
+ private void UpdateDeviceList(List devices)
+ {
+ // Clear existing items from the UI list
+ while (scanListContentContainer.ChildCount > 0)
+ {
+ scanListContentContainer.Remove(scanListContentContainer.GetChildAt(0));
+ }
+
+ if (devices != null && devices.Count > 0)
+ {
+ foreach (var device in devices)
+ {
+ var deviceContainer = Resources.CreateListItemContainer();
+ deviceContainer.Padding = AppStyles.ListElementPadding;
+
+ var deviceLabel = Resources.CreateBodyLabel(device.ToString());
+
+ deviceContainer.Add(deviceLabel);
+ scanListContentContainer.Add(deviceContainer);
+ }
+ scanScrollableList.HeightSpecification = LayoutParamPolicies.WrapContent; // Show the list
+ resultLabel.Text = $"Found {devices.Count} device(s)."; // Update status
+ }
+ else
+ {
+ scanScrollableList.HeightSpecification = 0; // Hide the list if no devices
+ if (!resultLabel.Text.Equals("Scanning for devices..."))
+ {
+ resultLabel.Text = "No devices found.";
+ }
+ }
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/WiFiPage.cs b/Mobile/NetworkApp/NetworkApp/WiFiPage.cs
new file mode 100644
index 000000000..d103c7ede
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/WiFiPage.cs
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Collections.Generic;
+using Tizen.NUI;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Binding;
+
+namespace NetworkApp
+{
+ ///
+ /// The ContentPage for testing Tizen.Network.WiFi C# API
+ ///
+ class WiFiPage : ContentPage
+ {
+ // ILog log = DependencyService.Get(); // To be handled if a similar service is available in Tizen NUI
+
+ private TextLabel titleLabel;
+
+ ///
+ /// Constructor
+ ///
+ public WiFiPage()
+ {
+ AppBar = new AppBar { Title = "Wi-Fi" };
+ InitializeComponent();
+ }
+
+ ///
+ /// Initialize WiFiPage. Add components and events.
+ ///
+ private void InitializeComponent()
+ {
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+
+ titleLabel = Resources.CreateTitleLabel("Wi-Fi Test");
+ mainLayoutContainer.Add(titleLabel);
+
+ var wifiOperationsList = new List
+ {
+ "Activate",
+ "Deactivate",
+ "Scan",
+ "Connect",
+ "Disconnect",
+ "Forget",
+ };
+
+ var listContentContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(0, 12) // Spacing between list items
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+
+ foreach (var opText in wifiOperationsList)
+ {
+ var itemContainer = Resources.CreateListItemContainer();
+ itemContainer.Padding = AppStyles.ListElementPadding;
+
+ var itemLabel = Resources.CreateHeaderLabel(opText);
+ itemLabel.Name = opText; // Store operation text for identification
+
+ itemContainer.Add(itemLabel);
+ // Attach the TouchEvent to the container to ensure the whole item is tappable
+ itemContainer.TouchEvent += OnWiFiItemTapped;
+ listContentContainer.Add(itemContainer);
+ }
+
+ var scrollableList = Resources.CreateScrollableList();
+ // Give scrollable list a more defined height to make it scrollable if content is long
+ scrollableList.HeightSpecification = 700;
+ scrollableList.Add(listContentContainer);
+ mainLayoutContainer.Add(scrollableList);
+
+ Content = mainLayoutContainer;
+ }
+
+ ///
+ /// Handles the tap event on a Wi-Fi operation item container.
+ ///
+ /// The View container that was tapped.
+ /// Touch event arguments.
+ /// True if the event was consumed, false otherwise.
+ private bool OnWiFiItemTapped(object source, TouchEventArgs e)
+ {
+ if (e.Touch.GetState(0) == PointStateType.Down)
+ {
+ var tappedContainer = source as View;
+ if (tappedContainer != null && tappedContainer.ChildCount > 0)
+ {
+ // We assume the first child is the TextLabel holding the operation name
+ var tappedLabel = tappedContainer.GetChildAt(0) as TextLabel;
+ if (tappedLabel != null)
+ {
+ var selectedItemText = tappedLabel.Name;
+ Tizen.Log.Info("WiFiPage", $"Item tapped: {selectedItemText}");
+
+ WiFiResultPage.WiFiOperation operation = WiFiResultPage.WiFiOperation.Activate; // Default
+
+ switch (selectedItemText)
+ {
+ case "Activate":
+ operation = WiFiResultPage.WiFiOperation.Activate;
+ break;
+ case "Deactivate":
+ operation = WiFiResultPage.WiFiOperation.Deactivate;
+ break;
+ case "Scan":
+ operation = WiFiResultPage.WiFiOperation.Scan;
+ break;
+ case "Connect":
+ operation = WiFiResultPage.WiFiOperation.Connect;
+ break;
+ case "Disconnect":
+ operation = WiFiResultPage.WiFiOperation.Disconnect;
+ break;
+ case "Forget":
+ operation = WiFiResultPage.WiFiOperation.Forget;
+ break;
+ }
+
+ var navigator = NUIApplication.GetDefaultWindow().GetDefaultNavigator();
+ navigator.Push(new WiFiResultPage(operation));
+ }
+ }
+ }
+ return true; // Event was consumed
+ }
+ }
+}
diff --git a/Mobile/NetworkApp/NetworkApp/WiFiResultPage.cs b/Mobile/NetworkApp/NetworkApp/WiFiResultPage.cs
new file mode 100644
index 000000000..21562753d
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/WiFiResultPage.cs
@@ -0,0 +1,347 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks; // Added for Task operations
+using Tizen.NUI;
+using Tizen.NUI.BaseComponents;
+using Tizen.NUI.Components;
+
+namespace NetworkApp // Added namespace declaration
+{
+ public class WiFiResultPage : ContentPage
+ {
+ public enum WiFiOperation
+ {
+ Activate,
+ Deactivate,
+ Scan,
+ Connect,
+ Disconnect,
+ Forget
+ }
+
+ private WiFiOperation currentOperation;
+ private TizenWiFiService wifiService; // Added TizenWiFiService field
+
+ // UI Elements
+ private TextLabel operationLabel;
+ private ScrollableBase resultScrollableList; // For scan results
+ private View resultListContentContainer; // Container for scan result items
+ private Button actionButton;
+ private TextLabel statusLabel;
+ private View ssidEntryContainer; // For Connect operation
+ private View passwordEntryContainer; // For Connect operation
+
+ public WiFiResultPage(WiFiOperation operation)
+ {
+ currentOperation = operation;
+ wifiService = new TizenWiFiService(); // Initialize TizenWiFiService
+ AppBar = new AppBar { Title = $"WiFi {operation} Result" }; // Set AppBar title
+
+ InitializeComponents();
+ SetupUIBasedOnOperation();
+ }
+
+ private void InitializeComponents()
+ {
+ var mainLayoutContainer = Resources.CreateMainLayoutContainer();
+ Content = mainLayoutContainer;
+
+ operationLabel = Resources.CreateHeaderLabel(""); // Text will be set in SetupUIBasedOnOperation
+ operationLabel.Margin = new Extents(0, 0, 16, 0); // Add some bottom margin
+
+ resultListContentContainer = new View
+ {
+ Layout = new LinearLayout
+ {
+ LinearOrientation = LinearLayout.Orientation.Vertical,
+ CellPadding = new Size2D(0, 8) // Spacing for scan result items
+ },
+ WidthSpecification = LayoutParamPolicies.MatchParent,
+ HeightSpecification = LayoutParamPolicies.WrapContent,
+ };
+
+ resultScrollableList = Resources.CreateScrollableList();
+ resultScrollableList.Margin = new Extents(0, 0, 16, 0); // Add some bottom margin
+ resultScrollableList.Add(resultListContentContainer);
+
+ actionButton = Resources.CreatePrimaryButton("Action"); // Text will be set in SetupUIBasedOnOperation
+ actionButton.Clicked += OnActionButtonClicked;
+ // Margin is already handled by AppStyles.PrimaryButtonStyle
+
+ statusLabel = Resources.CreateDetailLabel("Status: Idle");
+ statusLabel.BackgroundColor = Color.Transparent; // Make background transparent
+ statusLabel.Margin = new Extents(0,0,0,0); // Override default margin from style if needed
+
+ ssidEntryContainer = Resources.CreateStyledTextField("Enter SSID"); // Store the container
+ passwordEntryContainer = Resources.CreateStyledTextField("Enter Password"); // Store the container
+ }
+
+ private void SetupUIBasedOnOperation()
+ {
+ // Clear previous content from the main container before adding new elements
+ if (Content is View mainLayoutContainer)
+ {
+ while (mainLayoutContainer.ChildCount > 0)
+ {
+ mainLayoutContainer.Remove(mainLayoutContainer.GetChildAt(0));
+ }
+ mainLayoutContainer.Add(operationLabel);
+ }
+ else
+ {
+ // Fallback or error handling if Content is not a View as expected
+ Tizen.Log.Error("WiFiResultPage", "Content is not a View, cannot setup UI.");
+ return;
+ }
+
+ switch (currentOperation)
+ {
+ case WiFiOperation.Activate:
+ operationLabel.Text = "WiFi Activation";
+ actionButton.Text = "Activate WiFi";
+ mainLayoutContainer.Add(actionButton);
+ mainLayoutContainer.Add(statusLabel);
+ break;
+
+ case WiFiOperation.Deactivate:
+ operationLabel.Text = "WiFi Deactivation";
+ actionButton.Text = "Deactivate WiFi";
+ mainLayoutContainer.Add(actionButton);
+ mainLayoutContainer.Add(statusLabel);
+ break;
+
+ case WiFiOperation.Scan:
+ operationLabel.Text = "WiFi Scan";
+ actionButton.Text = "Scan Networks";
+ mainLayoutContainer.Add(actionButton);
+ mainLayoutContainer.Add(resultScrollableList); // ScrollableBase for scan results
+ mainLayoutContainer.Add(statusLabel);
+ break;
+
+ case WiFiOperation.Connect:
+ operationLabel.Text = "WiFi Connection";
+ mainLayoutContainer.Add(ssidEntryContainer);
+ mainLayoutContainer.Add(passwordEntryContainer);
+ actionButton.Text = "Connect";
+ mainLayoutContainer.Add(actionButton);
+ mainLayoutContainer.Add(statusLabel);
+ break;
+
+ case WiFiOperation.Disconnect:
+ operationLabel.Text = "WiFi Disconnection";
+ actionButton.Text = "Disconnect Current";
+ mainLayoutContainer.Add(actionButton);
+ mainLayoutContainer.Add(statusLabel);
+ break;
+
+ case WiFiOperation.Forget:
+ operationLabel.Text = "Forget Network";
+ mainLayoutContainer.Add(ssidEntryContainer); // SSID to forget
+ actionButton.Text = "Forget Network";
+ mainLayoutContainer.Add(actionButton);
+ mainLayoutContainer.Add(statusLabel);
+ break;
+ }
+ }
+
+ ///
+ /// Helper method to check if Wi-Fi is currently active.
+ ///
+ /// True if Wi-Fi is active, false otherwise.
+ private async Task IsWiFiActiveAsync()
+ {
+ // This is a placeholder. You'll need to replace this with the actual call
+ // to your TizenWiFiService to get the current Wi-Fi state.
+ // For example, if TizenWiFiService has a boolean property 'IsActive':
+ // return wifiService.IsActive;
+
+ // Or if it has a method that returns state:
+ // var state = await wifiService.GetStateAsync();
+ // return state == WiFiState.Enabled; // Assuming WiFiState is an enum
+
+ // For demonstration, let's assume a property 'IsActive' exists on TizenWiFiService.
+ // If the property is synchronous, you can access it directly.
+ // If it's async, use await.
+ // This example assumes a synchronous property for simplicity.
+ try
+ {
+ // Replace 'wifiService.IsActive' with the actual property/method from your service.
+ // If your service uses an enum for state, it might look like:
+ // return wifiService.CurrentState == Tizen.Network.WiFi.WiFiState.Enabled;
+ if (wifiService != null)
+ {
+ // This is a common pattern. Adjust if your service is different.
+ // For instance, if there's a 'GetState()' method:
+ // var currentState = await wifiService.GetStateAsync(); // If async
+ // var currentState = wifiService.GetState(); // If sync
+ // return currentState == YourWiFiStateEnum.Enabled;
+
+ // Assuming a boolean method 'IsActive()' for now:
+ return wifiService.IsActive();
+ }
+ }
+ catch (Exception ex)
+ {
+ Tizen.Log.Error("WiFiResultPage", $"Error checking Wi-Fi state: {ex.Message}");
+ }
+ return false; // Default to false if service is null or error occurs
+ }
+
+ // OnResultListItemSelected was for CollectionView.
+ // If manual selection/tap on scan results is needed, a TouchEvent handler
+ // would need to be added to each TextLabel in the scan results list.
+
+ private void OnActionButtonClicked(object sender, ClickedEventArgs args)
+ {
+ Tizen.Log.Info("WiFiResultPage", $"Action button clicked for: {currentOperation}");
+ statusLabel.Text = $"Performing {currentOperation}...";
+
+ // Placeholder for actual async operations
+ // In a real app, these would call IWiFi service methods
+ // and update UI on completion.
+ PerformOperationAsync(currentOperation);
+ }
+
+ private async void PerformOperationAsync(WiFiOperation operation)
+ {
+ try
+ {
+ switch (operation)
+ {
+ case WiFiOperation.Activate:
+ // Check if WiFi is already active
+ if (await IsWiFiActiveAsync())
+ {
+ statusLabel.Text = "WiFi is already active.";
+ }
+ else
+ {
+ statusLabel.Text = "Activating WiFi...";
+ await wifiService.Activate();
+ // Optionally, check again if activation was successful
+ if (await IsWiFiActiveAsync())
+ {
+ statusLabel.Text = "WiFi Activation Successful.";
+ }
+ else
+ {
+ statusLabel.Text = "WiFi Activation failed. Please try again.";
+ }
+ }
+ break;
+
+ case WiFiOperation.Deactivate:
+ // Check if WiFi is already inactive
+ if (!await IsWiFiActiveAsync())
+ {
+ statusLabel.Text = "WiFi is already inactive.";
+ }
+ else
+ {
+ statusLabel.Text = "Deactivating WiFi...";
+ await wifiService.Deactivate();
+ // Optionally, check again if deactivation was successful
+ if (!await IsWiFiActiveAsync())
+ {
+ statusLabel.Text = "WiFi Deactivation Successful.";
+ }
+ else
+ {
+ statusLabel.Text = "WiFi Deactivation failed. Please try again.";
+ }
+ }
+ break;
+
+ case WiFiOperation.Scan:
+ statusLabel.Text = "Scanning for networks...";
+ await wifiService.Scan();
+ // Allow a brief moment for scan results to be populated internally by Tizen
+ await Task.Delay(500); // Short delay before fetching results
+ var apInfoList = wifiService.ScanResult(); // ScanResult() is synchronous
+
+ // Clear previous scan results from the container
+ while (resultListContentContainer.ChildCount > 0)
+ {
+ resultListContentContainer.Remove(resultListContentContainer.GetChildAt(0));
+ }
+
+ if (apInfoList != null)
+ {
+ int count = 0;
+ foreach (var apInfo in apInfoList)
+ {
+ var networkItemContainer = Resources.CreateListItemContainer();
+ networkItemContainer.Padding = AppStyles.ListElementPadding;
+
+ var networkLabel = Resources.CreateBodyLabel($"{apInfo.Name} ({apInfo.State})");
+
+ networkItemContainer.Add(networkLabel);
+ resultListContentContainer.Add(networkItemContainer);
+ count++;
+ }
+ resultScrollableList.HeightSpecification = LayoutParamPolicies.WrapContent; // Show the list
+ statusLabel.Text = $"Scan complete. Found {count} networks:";
+ }
+ else
+ {
+ resultScrollableList.HeightSpecification = 0; // Hide if no results
+ statusLabel.Text = "Scan failed or no networks found.";
+ }
+ break;
+
+ case WiFiOperation.Connect:
+ var ssidField = ssidEntryContainer.GetChildAt(0) as TextField;
+ var passwordField = passwordEntryContainer.GetChildAt(0) as TextField;
+ if (ssidField != null && !string.IsNullOrEmpty(ssidField.Text))
+ {
+ statusLabel.Text = $"Connecting to {ssidField.Text}...";
+ await wifiService.Connect(ssidField.Text, passwordField?.Text);
+ statusLabel.Text = $"Successfully connected to {ssidField.Text}.";
+ }
+ else
+ {
+ statusLabel.Text = "Connection Failed: SSID cannot be empty.";
+ }
+ break;
+
+ case WiFiOperation.Disconnect:
+ string connectedAp = wifiService.ConnectedAP();
+ if (!string.IsNullOrEmpty(connectedAp))
+ {
+ statusLabel.Text = $"Disconnecting from {connectedAp}...";
+ await wifiService.Disconnect(connectedAp);
+ statusLabel.Text = $"Successfully disconnected from {connectedAp}.";
+ }
+ else
+ {
+ statusLabel.Text = "Disconnection Failed: Not connected to any AP.";
+ }
+ break;
+
+ case WiFiOperation.Forget:
+ var forgetSsidField = ssidEntryContainer.GetChildAt(0) as TextField;
+ if (forgetSsidField != null && !string.IsNullOrEmpty(forgetSsidField.Text))
+ {
+ statusLabel.Text = $"Forgetting {forgetSsidField.Text}...";
+ wifiService.Forget(forgetSsidField.Text); // Forget is synchronous
+ statusLabel.Text = $"Successfully forgot {forgetSsidField.Text}.";
+ }
+ else
+ {
+ statusLabel.Text = "Forget Failed: SSID cannot be empty.";
+ }
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ // Update UI on the main thread in case of an error
+ // NUI operations are generally thread-safe for property updates like Text,
+ // but for more complex UI changes, ensure it's on the main thread.
+ // For simplicity, direct update here.
+ statusLabel.Text = $"{operation} Failed: {ex.Message}";
+ Tizen.Log.Error("WiFiResultPage", $"Error in PerformOperationAsync for {operation}: {ex.ToString()}");
+ }
+}
+}
+}
diff --git a/Mobile/NetworkApp/NetworkApp/shared/res/NetworkApp.png b/Mobile/NetworkApp/NetworkApp/shared/res/NetworkApp.png
new file mode 100644
index 000000000..9f3cb9860
Binary files /dev/null and b/Mobile/NetworkApp/NetworkApp/shared/res/NetworkApp.png differ
diff --git a/Mobile/NetworkApp/NetworkApp/tizen-manifest.xml b/Mobile/NetworkApp/NetworkApp/tizen-manifest.xml
new file mode 100644
index 000000000..3d8530da8
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/tizen-manifest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ NetworkApp.png
+
+
+
+
+
+ http://tizen.org/privilege/network.get
+ http://tizen.org/privilege/network.set
+ http://tizen.org/privilege/network.profile
+ http://tizen.org/privilege/wifidirect
+
+
+
+
diff --git a/Mobile/NetworkApp/NetworkApp/tizen_dotnet_project.yaml b/Mobile/NetworkApp/NetworkApp/tizen_dotnet_project.yaml
new file mode 100644
index 000000000..0f5fbae14
--- /dev/null
+++ b/Mobile/NetworkApp/NetworkApp/tizen_dotnet_project.yaml
@@ -0,0 +1,24 @@
+# csproj file path
+csproj_file: NetworkApp.csproj
+
+# Default profile, Tizen API version
+profile: tizen
+api_version: "9.0"
+
+# Build type [Debug/ Release/ Test]
+build_type: Debug
+
+# Signing profile to be used for Tizen package signing
+# If value is empty: "", active signing profile will be used
+# Else If value is ".", default signing profile will be used
+signing_profile: .
+
+# files monitored for dirty/modified status
+files:
+ - NetworkApp.csproj
+ - NetworkApp.cs
+ - tizen-manifest.xml
+ - shared/res/NetworkApp.png
+
+# project dependencies
+deps: []
diff --git a/Mobile/NetworkApp/README.md b/Mobile/NetworkApp/README.md
new file mode 100644
index 000000000..84abb562b
--- /dev/null
+++ b/Mobile/NetworkApp/README.md
@@ -0,0 +1,18 @@
+# NetworkApp
+The NetworkApp application demonstrates how user can get network information, connect Wi-Fi APs and scan Wi-Fi Direct peer devices.
+
+
+
+
+
+
+### Verified Version
+* Tizen.NET : 6.0.428
+* Tizen.NET.SDK : 10.0.111
+
+
+### Supported Profile
+* Tizen 10.0 RPI4
+
+### Author
+* Harish Nanu J
diff --git a/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-Connection.png b/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-Connection.png
new file mode 100755
index 000000000..80810820a
Binary files /dev/null and b/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-Connection.png differ
diff --git a/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-WiFi.png b/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-WiFi.png
new file mode 100755
index 000000000..50bd261d5
Binary files /dev/null and b/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-WiFi.png differ
diff --git a/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-WiFiDirect.png b/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-WiFiDirect.png
new file mode 100755
index 000000000..0eaf31132
Binary files /dev/null and b/Mobile/NetworkApp/Screenshots/Tizen/NetworkApp-WiFiDirect.png differ