Thursday 14 November 2013

Sharing a file from WP8 using Bluetooth OBEX Object Push Profile - CODE

I keep getting asked for the code for this article:

http://geoffwebbercross.blogspot.co.uk/2012/12/pushing-file-from-wp8-using-bluetooth.html

So here's the View Model example:

using BluetoothApp.Commanding;
using BluetoothApp.Helpers;
using BluetoothApp.Services.Interfaces;
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using WebberCross.Obelisk;
using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace BluetoothApp.ViewModel
{
    public class BluetoothShareVM : ViewModelBase
    {
        const string FILE_NAME = "demo.jpg";

        private readonly INavigationService _navService = null;
        private readonly ITaskService _taskService = null;
        private readonly IDialogService _diaService = null;
        private IReadOnlyList<PeerInformation> _pairedDevices = null;
        private StreamSocket _stream = null;
        private DataWriter _dataWriter = null;
        private DataReader _dataReader = null;

        private byte[] _shareData = null;
        private Brush _thumbnail = null;
        private DelegateCommand _shareCommand = null;
        private bool _isLoading = false;
        private bool _isShareExecutable = true;
        private string _message = Resources.StringTable.BTSearching;
        private bool _isShareVisible = false;
        private PeerInformation _selectedDevice = null;
        private int _progress = 0;
        private string _shareStatus = Resources.StringTable.BTSending;

        public string BTShareTitle { get { return Resources.StringTable.BTShareTitle; } }
        public string BTShareHeading { get { return Resources.StringTable.BTShareHeading; } }
        public string ShareText { get { return Resources.StringTable.ShareText; } }
        public string LogoutText { get { return Resources.StringTable.LogoutText; } }
        public string CheckPeersWarning { get { return Resources.StringTable.CheckPeersWarning; } }
        public string DevicesText { get { return Resources.StringTable.DevicesText; } }
        public string StatusText { get { return Resources.StringTable.StatusText; } }

        private List<byte[]> _packets = null;

        public byte[] ShareData
        {
            get { return this._shareData; }
            set
            {
                if (this._shareData != value)
                {
                    this._shareData = value;

                    base.RaisePropertyChanged("ShareData");
                }
            }
        }

        public string Message
        {
            get { return this._message; }
            set
            {
                if (this._message != value)
                {
                    this._message = value;

                    base.RaisePropertyChanged("Message");
                }
            }
        }

        public string ShareStatus
        {
            get { return this._shareStatus; }
            set
            {
                if (this._shareStatus != value)
                {
                    this._shareStatus = value;

                    base.RaisePropertyChanged("ShareStatus");
                }
            }
        }

        public int Progress
        {
            get { return this._progress; }
            set
            {
                if (this._progress != value)
                {
                    this._progress = value;

                    base.RaisePropertyChanged("Progress");
                }
            }
        }

        public Brush Thumbnail
        {
            get
            {
                if (this._thumbnail == null && this._shareData != null)
                {
                    this._thumbnail = Imaging.GetImageBrush(this._shareData, 0);
                }

                return _thumbnail;
            }
        }

        public bool IsLoading
        {
            get { return this._isLoading; }
            set
            {
                if (this._isLoading != value)
                {
                    this._isLoading = value;

                    base.RaisePropertyChanged("IsLoading");
                }
            }
        }

        public bool IsShareVisible
        {
            get { return this._isShareVisible; }
            set
            {
                if (this._isShareVisible != value)
                {
                    this._isShareVisible = value;

                    base.RaisePropertyChanged("IsShareVisible");
                }
            }
        }

        public DelegateCommand ShareCommand
        {
            get { return this._shareCommand; }
        }

        public bool IsShareExecutable
        {
            get { return this._isShareExecutable; }
            set
            {
                if (this._isShareExecutable != value)
                {
                    this._isShareExecutable = value;

                    base.RaisePropertyChanged("IsShareExecutable");
                    this._shareCommand.RaiseCanExecuteChanged();
                }
            }
        }

        public IReadOnlyList<PeerInformation> PairedDevices
        {
            get { return this._pairedDevices; }
            set
            {
                if (this._pairedDevices != value)
                {
                    this._pairedDevices = value;

                    base.RaisePropertyChanged("PairedDevices");
                }
            }
        }

        public PeerInformation SelectedDevice
        {
            get { return this._selectedDevice; }
            set
            {
                this.IsShareExecutable = value != null;

                if (this._selectedDevice != value)
                {
                    this._selectedDevice = value;

                    base.RaisePropertyChanged("SelectedDevice");
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the FacebookShareViewModel class.
        /// </summary>
        public BluetoothShareVM(INavigationService navService, ITaskService taskService, IDialogService diaService)
        {
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
                // Code runs "for real": Connect to service, etc...
                this._navService = navService;
                this._taskService = taskService;
                this._diaService = diaService;

                this.InitialiseCommands();

                this.Search();
            }
        }

        private void InitialiseCommands()
        {
            // Background
            this._shareCommand = new DelegateCommand((param) =>
            {
                this.Share();
            },
                (p) =>
                {
                    return this.IsShareExecutable;
                });
        }

        private async void Search()
        {
            this.IsLoading = true;
            this.IsShareExecutable = false;

            this.Message = Resources.StringTable.BTSearching;

            // Note: You can only browse and connect to paired devices!
            // Configure PeerFinder to search for all paired devices.
            PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";

            try
            {
                this.PairedDevices = await PeerFinder.FindAllPeersAsync();
            }
            catch (Exception)
            {
               
            }

            this.IsLoading = false;
        }
       
        private async Task Share()
        {
            this.IsShareVisible = true;
            this.IsShareExecutable = false;
            this.ShareStatus = Resources.StringTable.BTSending;

            this.CreatePackets();

            // Select a paired device. In this example, just pick the first one.
            // Attempt a connection
            _stream = new StreamSocket();

            try
            {
                // Make sure ID_CAP_NETWORKING is enabled in your WMAppManifest.xml, or the next
                // line will throw an Access Denied exception.
                string oopUUID = "{00001105-0000-1000-8000-00805f9b34fb}";

                await _stream.ConnectAsync(_selectedDevice.HostName, oopUUID);
                _dataWriter = new DataWriter(_stream.OutputStream);
                _dataReader = new DataReader(_stream.InputStream);

                // Send data
                int maxServerPacket = await this.ObexConnect();

                if (maxServerPacket > 0)
                {
                    if (await ObexPushRequest())
                    {
                        // Success
                        this.ShareStatus = Resources.StringTable.BTSuccess;
                    }
                    else
                    {
                        // Failed
                        this.ShareStatus = Resources.StringTable.BTFailed;
                    }

                    this.Progress = 100;
                }

                this.ObexDisconnect();
            }
            catch (Exception ex)
            {
                this.ShareStatus = Resources.StringTable.BTFailed;
            }
        }



        private async Task<int> ObexConnect()
        {
            //send client request
            byte[] ConnectPacket = new byte[7];

            ConnectPacket[0] = 0x80;                            // Connect
            ConnectPacket[1] = (7 & 0xFF00) >> 8;       // Packetlength Hi Byte
            ConnectPacket[2] = (7 & 0xFF);                          // Packetlength Lo Byte
            ConnectPacket[3] = 0x10;                            // Obex v1
            ConnectPacket[4] = 0x00;                            // No flags
            ConnectPacket[5] = (2048 & 0xFF00) >> 8;    // 2048 byte client max packet size Hi Byte
            ConnectPacket[6] = (2048 & 0xFF);                   // 2048 byte max packet size Lo Byte

            _dataWriter.WriteBytes(ConnectPacket);
            await _dataWriter.StoreAsync();

            // Get response code
            await _dataReader.LoadAsync(1);
            byte[] buffer = new byte[1];
            _dataReader.ReadBytes(buffer);

            if (buffer[0] == 0xA0) // Sucess
            {
                // Get length
                await _dataReader.LoadAsync(2);
                buffer = new byte[2];
                _dataReader.ReadBytes(buffer);

                int length = buffer[0] << 8;
                length += buffer[1];

                // Get rest of packet
                await _dataReader.LoadAsync((uint)length - 3);
                buffer = new byte[length - 3];
                _dataReader.ReadBytes(buffer);

                int obexVersion = buffer[0];
                int flags = buffer[1];
                int maxServerPacket = buffer[2] << 8 + buffer[3];

                return maxServerPacket;
            }
            else
            {
                return -1;
            }
        }

        private async void ObexDisconnect()
        {
            byte[] bytes = new byte[3];
            bytes[0] = 0x81;
            bytes[1] = 0;
            bytes[2] = 3;

            _dataWriter.WriteBytes(bytes);
            await _dataWriter.StoreAsync();

            await _dataReader.LoadAsync(3);
            byte[] response = new byte[3];
            _dataReader.ReadBytes(response);

            _stream.Dispose();
            _stream = null;

            _dataReader.Dispose();
            _dataWriter.Dispose();
        }

        private async Task<bool> ObexPushRequest()
        {
            int step = 100 / this._packets.Count;

            foreach (var packet in this._packets)
            {
                this.Progress += step;

                _dataWriter.WriteBytes(packet);
                await _dataWriter.StoreAsync();

                // Get response code
                await _dataReader.LoadAsync(3);
                byte[] buffer = new byte[3];
                _dataReader.ReadBytes(buffer);

                // If not success and not continue it's an error
                if (buffer[0] != 0xA0 && buffer[0] != 0x90)
                    return false;
                else if (buffer[0] == 0xA0) // Success
                    return true;
            }

            return false;
        }


        private void CreatePackets()
        {
            int bodyLength = 1024;
            this._packets = new List<byte[]>();           

            // Chop data into packets           
            int blocks = (int)Math.Ceiling((decimal)this._shareData.Length / bodyLength);

            System.Text.UnicodeEncoding encoding = new System.Text.UnicodeEncoding(true, false);
            byte[] encodedName = encoding.GetBytes(FILE_NAME + new char());

            for (int i = 0; i < blocks; i++)
            {
                int headerLength = i == 0 ? 14 + encodedName.Length : 6;

                // Chop data into body
                byte[] body = null;
                if (i < blocks - 1)
                    body = new byte[bodyLength];
                else
                    body = new byte[this._shareData.Length - (i * bodyLength)];

                System.Buffer.BlockCopy(this._shareData, i * bodyLength, body, 0, body.Length);

                // Create packet
                byte[] packet = new byte[headerLength + body.Length];
                this._packets.Add(packet);

                // Build packet
                int offset = 0;
                packet[offset++] = i != blocks - 1 ? (byte)0x02 : (byte)0x82; // 0x02 for first blocks, 0x82 for last
                packet[offset++] = (byte)((packet.Length & 0xFF00) >> 8);
                packet[offset++] = (byte)(packet.Length & 0xFF);

                // Payload details on first packet
                if (i == 0)
                {
                    packet[offset++] = 0x01; // Name header
                    packet[offset++] = (byte)(((encodedName.Length + 3) & 0xFF00) >> 8);
                    packet[offset++] = (byte)((encodedName.Length + 3) & 0xFF);
                    System.Buffer.BlockCopy(encodedName, 0, packet, offset, encodedName.Length);
                    offset += encodedName.Length;
                    packet[offset++] = 0xC3; // Length header
                    packet[offset++] = (byte)((this._shareData.Length & 0xFF000000) >> 24);
                    packet[offset++] = (byte)((this._shareData.Length & 0xFF0000) >> 16);
                    packet[offset++] = (byte)((this._shareData.Length & 0xFF00) >> 8);
                    packet[offset++] = (byte)(this._shareData.Length & 0xFF);
                }

                packet[offset++] = 0x48; // Object body chunk header
                packet[offset++] = (byte)(((body.Length + 3) & 0xFF00) >> 8);
                packet[offset++] = (byte)((body.Length + 3) & 0xFF);
                System.Buffer.BlockCopy(body, 0, packet, offset, body.Length);
            }
        }

    }

}

And here's the View XAML:

<phone:PhoneApplicationPage x:Class="BluetoothApp.Pages.BluetoothShareView"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
                            xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
                            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                            xmlns:preview="clr-namespace:Phone7.Fx.Preview;assembly=Phone7.Fx.Preview"
                            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                            xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
                            xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
                            xmlns:cmd="clr-namespace:BluetoothApp.Commanding"
                            mc:Ignorable="d"
                            d:DesignWidth="480"
                            d:DesignHeight="728"
                            FontFamily="{StaticResource PhoneFontFamilyNormal}"
                            FontSize="{StaticResource PhoneFontSizeNormal}"
                            Foreground="{StaticResource PhoneForegroundBrush}"
                            SupportedOrientations="Portrait"
                            Orientation="Portrait"
                            shell:SystemTray.IsVisible="False"
                            DataContext="{Binding Bluetooth, Source={StaticResource Locator}}">

    <toolkit:TransitionService.NavigationInTransition>
        <toolkit:NavigationInTransition>
            <toolkit:NavigationInTransition.Backward>
                <toolkit:TurnstileTransition Mode="BackwardIn"/>
            </toolkit:NavigationInTransition.Backward>
            <toolkit:NavigationInTransition.Forward>
                <toolkit:TurnstileTransition Mode="ForwardIn"/>
            </toolkit:NavigationInTransition.Forward>
        </toolkit:NavigationInTransition>
    </toolkit:TransitionService.NavigationInTransition>
    <toolkit:TransitionService.NavigationOutTransition>
        <toolkit:NavigationOutTransition>
            <toolkit:NavigationOutTransition.Backward>
                <toolkit:TurnstileTransition Mode="BackwardOut"/>
            </toolkit:NavigationOutTransition.Backward>
            <toolkit:NavigationOutTransition.Forward>
                <toolkit:TurnstileTransition Mode="ForwardOut"/>
            </toolkit:NavigationOutTransition.Forward>
        </toolkit:NavigationOutTransition>
    </toolkit:TransitionService.NavigationOutTransition>

    <Grid x:Name="LayoutRoot" Background="{StaticResource BackgroundBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <toolkit:PerformanceProgressBar Margin="0,-110,0,0" Padding="0" IsIndeterminate="True" IsEnabled="{Binding Path=IsLoading}" Visibility="{Binding IsLoading, Converter={StaticResource visConverter}}" />
        <ProgressBar Margin="24,-110,24,0" Padding="0" Minimum="0" Maximum="100" Value="{Binding Progress}" Visibility="{Binding IsShareVisible, Converter={StaticResource visConverter}}" />

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,0">
            <TextBlock Text="{Binding Path=BTShareTitle, Mode=OneTime, FallbackValue=TITLE}" Style="{StaticResource PhoneTextNormalStyle}" />
            <TextBlock Text="{Binding Path=BTShareHeading, Mode=OneTime, FallbackValue=heading}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" />
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="24,0,24,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="200" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <Rectangle Fill="{Binding Path=Thumbnail, FallbackValue=Red}" Height="200" Width="120" Stretch="Fill" HorizontalAlignment="Center" />

            <Grid Grid.Row="1" Visibility="{Binding IsShareVisible, Converter={StaticResource visConverter}, ConverterParameter='invert'}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <TextBlock Margin="0,20,0,0" Text="{Binding CheckPeersWarning}" Style="{StaticResource PhoneTextNormalStyle}"
                           TextWrapping="Wrap"/>
                <TextBlock Margin="0,20,0,0" Grid.Row="1" Text="{Binding DevicesText}" Style="{StaticResource PhoneTextTitle2Style}" />
                <ListBox Grid.Row="2" ItemsSource="{Binding PairedDevices}" SelectedItem="{Binding SelectedDevice, Mode=TwoWay}"
                         DisplayMemberPath="DisplayName"  Margin="0,10,0,0" FontSize="{StaticResource PhoneFontSizeMediumLarge}">
                </ListBox>
            </Grid>

            <Grid Grid.Row="1" Visibility="{Binding IsShareVisible, Converter={StaticResource visConverter}}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <TextBlock Margin="0,20,0,0" Text="{Binding StatusText}" Style="{StaticResource PhoneTextTitle2Style}" />               
                <TextBlock Grid.Row="2" Margin="0,20,0,0" Text="{Binding ShareStatus}" Style="{StaticResource PhoneTextNormalStyle}"
                           TextWrapping="Wrap"/>
            </Grid>
        </Grid>

        <preview:BindableApplicationBar BackgroundColor="#FFD82735">
            <preview:BindableApplicationBarIconButton IsEnabled="{Binding Path=IsShareExecutable}" Command="{Binding Path=ShareCommand}" IconUri="/Images/send.png" Text="{Binding Path=ShareText, Mode=OneTime}" />
        </preview:BindableApplicationBar>
    </Grid>
</phone:PhoneApplicationPage>

No comments:

Post a Comment