The Intel® Common Connectivity Framework (Intel® CCF) is connectivity software for applications running on mobile devices. Applications using Intel CCF can connect users together whether they are across the world behind different firewalls or in the same room with no connection to the Internet. Intel CCF is available for iOS*, Android*, Windows* Desktop, and Windows Store apps, making applications with Intel CCF form factor- and platform-agnostic. Using Intel CCF, developers can produce apps that talk to phones, tablets, PCs, and other smart devices.
Intel CCF’s communication model is peer to peer. It enables people to connect directly with each other and share information between all their mobile computing devices.
In this article I will review how to develop applications with Intel CCF 3.0 for Windows 8 devices. I was the owner of a project to develop an app for transferring files between Windows 8 and Android devices, where I personally developed a Windows Store app. Here, I will share my experience of using Intel CCF.
First of all, you need to attach libs to the project in Microsoft Visual Studio*. The Intel CCF SDK contains two dll files: libMetroSTC and WinRTSTC that every Intel CCF application needs. To use the Intel CCF APIs, add the Intel.STC.winmd to the project references. The winmd file contains metadata for the Intel CCF SDK for Windows Store apps.
Identity Setup
Before a session can be made discoverable, the Intel CCF user must set the identity, which consists of user name, device name, and avatar. This is the identity that remote users will see. In the SDK for Windows Store apps, InviteAssist class allows users to set the Intel CCF identity.
string displayName = await UserInformation.GetDisplayNameAsync(); _inviteAssist = InviteAssist.GetInstance(); _inviteAssist.SetUserName(displayName); _inviteAssist.SetStatusText("Status"); _inviteAssist.SetSessionName("Win 8"); if (_AvatarStorageFile == null) { Windows.Storage.StorageFile imgfile = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Device.png")); _AvatarStorageFile = imgfile; } await _inviteAssist.SetAvatarAsync(_AvatarStorageFile);
Implement the SetUserProfile() function to get an instance of InviteAssist class and set the profile by calling InviteAssist.SetUserName(), InviteAssist.SetSessionName(), and InviteAssist.SetAvatarAsync() APIs as shown above. I set the user name as the name of the Windows account and this designation will be visible on all devices that my Windows 8 device will connect to. Status text and session name may be defined by the user in UI. In my case these parameters can’t be changed by the user and will always use my notation. Now the profile is set and ready to be discovered by remote users.
Discovery
Intel CCF remote user discovery is done by calling Intel.STC.Api.STCSession.GetNeighborhood() API, which returns an IObservableVector<object> of all remote STCSessions that are discoverable. It’s the developer’s responsibility to either Data Bind the observable collection to the UI or to update the UI from the code behind to show the list of the users. I used Grid APP (XAML), which is the standard template in Visual Studio for rendering the GUI. All users are displayed in the results of GridList.
Create a ObservableCollection of NeighborhoodUsers class objects. _userList is a list of STCSession class objects and holds a list of neighborhood users.
private static List<STCSession> _userList; IObservableVector<object> _hood; ObservableCollection<NeighborhoodUsers> _neighborhoodList = new ObservableCollection<NeighborhoodUsers>(); ObservableCollection<NeighborhoodUsers> neighborhoodList { get { return _neighborhoodList; } }
Get IObservableVector by calling STCSession.GetNeighborhood()API and set VectorChanged event handler for the IObservableVector.
async void GetNeighborhoodList() { await Task.Run(() => { _hood = STCSession.GetNeighborhood(); _hood.VectorChanged += _hood_VectorChanged; STCSession.LockNeighborhood(); IEnumerator<object> en = _hood.GetEnumerator(); while (en.MoveNext()) { STCSession session = en.Current as STCSession; if (session != null) hood_DiscoveryEvent(session, CollectionChange.ItemInserted); } STCSession.UnlockNeighborhood(); }); } void _hood_VectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs evt) { STCSession session = null; lock (sender) { if (sender.Count > evt.Index) session = sender[(int)evt.Index] as STCSession; } if (session != null) hood_DiscoveryEvent(session, evt.CollectionChange); }
We add the hood_DiscoveryEvent() callback function to capture vector change events. This callback function notifies when remote sessions are available for connection or when they are no longer available in the neighborhood. When a new session is available, a CollectionChange.ItemInserted event is received and CollectionChange.ItemRemoved and CollectionChange.ItemChanged events are received when remote STCSession leaves the neighborhood or any STCSession parameter is changed, respectively. Add STCSession.ContainsGadget() to check to see if the same app is installed on the remote machine or not.
private async void hood_DiscoveryEvent(STCSession session, CollectionChange type) { switch (type) { case CollectionChange.ItemInserted: await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { _userList.Add(session); AddPeopleToList(session, "Not Connected"); }); break; case CollectionChange.ItemRemoved: // Handle this case to check if remote users have left the neighborhood. if (_neighborhoodList.Count > 0) { NeighborhoodUsers obj; try { obj = _neighborhoodList.First((x => x.Name == session.User.Name)); await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { _neighborhoodList.Remove(obj); _userList.RemoveAll(x => x.Id.ToString() == session.Id.ToString()); }); } catch { obj = null; } } break; case CollectionChange.ItemChanged: // Handle this case to check if any STCSession data is updated. { STCSession item; try { item = _userList.First(x => x.Id == session.Id); } catch { item = null; } if (item != null) item.Update(session); break; } default: break; } }
Invitation
Now that we know how to discover remote users (devices), it is high time to explain the process of sending a connection. In Intel CCF, this process is called invitation. In Intel CCF 3.0, sending and receiving invitations are handled by STCInitiator and STCResponder classes. STCInitiator is used to send the invitation to the remote user, and STCResponder is used to respond to the incoming request. When a request is accepted by the remote user, a successful Intel CCF connection is established. There are no restrictions on using the STCInitiator object to send invitations. An object can send multiple invitations.
Sending an invitation to a remote user is described in the following function.
private void InitializeInitiator(STCApplicationId appId) { initiator = new STCInitiator(appId, true); initiator.InviteeResponded += initiator_InviteeResponded; initiator.CommunicationStarted += initiator_CommunicationStarted; initiator.Start(); }
After all callback handlers are set, call STCInitiator.Start() API. Now to send an invitation to the discovered remote user, it is necessary to call STCInitiator.Invite() API.
initiator.Invite(_userList[itemListView.Items.IndexOf(e.ClickedItem)].Id);
To check the status of a sent invitation, implement STCInitiator.InviteeResponded() callback.
async void initiator_InviteeResponded(STCSession session, InviteResponse response) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { switch(response) { case InviteResponse.ACCEPTED: // You are connected to the user. break; case InviteResponse.REJECTED: //Invite was rejected by the remote user. break; case InviteResponse.TIMEDOUT: //No response. Invite time-out. break; } }); }
Now that the invitation was sent, the remote user needs to receive it. Implement a function called InitializeResponder() and initialize a STCResponder object by passing a STCApplicationId object. Register STCResponder.InviteeResponded() and STCResponder.CommunicationStarted() handlers. These handlers are called when remote users respond to invitations and when communication channels are successfully established between two Intel CCF users, respectively.
private void InitializeResponder(STCApplicationId appId) { responder = new STCResponder(appId); responder.InviteReceived += responder_InviteReceived; responder.CommunicationStarted += responder_CommunicationStarted; responder.Start(); }
Invitations sent by remote users can be received in STCResponder.InviteReceived() callback. When an invitation is received, it can be accepted or rejected. To respond to the invitation, STCResponder.RespondToInvite() API is called.
STCResponder.RespondToInvite() API is called. async void responder_InviteReceived(STCSession session, int inviteHandle) { if ((_initiatorDataStream == null) && (_responderDataStream == null)) { try { if (!checkPopUp) { _inviteHandle = inviteHandle; _session = session; Debug.WriteLine("Several windows " + _inviteHandle); checkPopUp = true; await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { InviteeName.Text = session.User.Name + " wants to connect"; InviteePopup.IsOpen = true; checkPopUp = true; }); } } catch (Exception ex) { Debug.WriteLine(ex.Message); } } else { responder.RespondToInvite(session, inviteHandle, false); } }
Communication and Data Transfer
After sending and receiving invitations, initiator_CommunicationStarted() and responder_CommunicationStarted() callbacks give the Stream handle. We will use this NetStream handle to transfer data between two connected users. To get the data stream handle, implement initiator_CommunicationStarted() and responder_CommunicationStarted() callbacks and create a NetStream object. Callbacks for NetStream.StreamClosed and NetStream.StreamSuspended events can be registered. These events are received when a communication channel is closed or suspended, respectively.
void initiator_CommunicationStarted(CommunicationStartedEventArgs args) { _initiatorDataStream = args.Stream; objWrapper.SetStream(_initiatorDataStream); _initiatorDataStream.StreamClosed += DataStream_StreamClosed; _initiatorDataStream.StreamSuspended += DataStream_StreamSuspended; _initiatorDataStream.DataReady += objWrapper.StreamListen; } void responder_CommunicationStarted(CommunicationStartedEventArgs args) { _responderDataStream = args.Stream; objWrapper.SetStream(_responderDataStream); _responderDataStream.StreamClosed += DataStream_StreamClosed; _responderDataStream.StreamSuspended += DataStream_StreamSuspended; _responderDataStream.DataReady += objWrapper.StreamListen; } private async void DataStream_StreamClosed(int streamId, Guid sessionGuid) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { UpdateConnectionStatus(_session.User.Name, "Not Connected"); if (_inviter) { _initiatorDataStream.Dispose(); _initiatorDataStream = null; _inviter = false; } else if (_responder) { _responderDataStream.Dispose(); _responderDataStream = null; _responder = false; } if (isTransferFrame) { if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack(); } ResetUIScreen(); }); }
Now, let’s look at the process of transferring data. First, we choose the file that we want to send. For this, I developed my own file explorer, but a simpler way to choose files is to use FileOpenPicker. When a file is chosen, write data to the NetStream handle received. To write data on the communication channel NetStrem.Write() is used.
async void SendFileData()
async void SendFileData() { uint size = 1024 * 4; byte[] buffer = new byte[size]; int totalbytesread = 0; using (Stream sourceStream = await storedfile.OpenStreamForReadAsync()) { do { int bytesread = await sourceStream.ReadAsync(buffer, 0, buffer.Length); _objNetStream.Write(buffer, (uint)bytesread); totalbytesread += bytesread; TransferedBytes = totalbytesread; if (args.nState != TransferState.FT_SEND_PROGRESS) { args.nState = TransferState.FT_SEND_PROGRESS; args.FileName = storedfile.Name; args.FileSize = (int)storedfileProperties.Size; args.DataBuffer = null; args.readbytes = 0; OnTransferUpdates(args); } } while (totalbytesread < sourceStream.Length); } }
To receive a file, implement NetStream.Read() event callback, which was explained in the Communication section.
readBytes = _objNetStream.Read(receivebuffer, (uint)bytesToRead);
In conclusion, I hope this information helps you to understand the Intel CCF SDK and that you will use this SDK for developing cool applications for Windows 8 and Android.
Intel and the Intel logo are trademarks of Intel Corporation in the U.S. and/or other countries.
Copyright © 2014 Intel Corporation. All rights reserved.
*Other names and brands may be claimed as the property of others.