Server-Side (SmartTV) Convergence Application
Client-Side (a Mobile Device) Convergence Application
Introducing the AllShare Framework
Popularity of smart phones and SmartTVs eventually called need of a convergence framework that supports interaction between different platforms. In addition to the smart phones and SmartTVs, recent cameras and cars are also equipped with smart functions. With the concept of the Samsung Smart Home that combines all smart electronics, the Convergence has become popular. The Samsung SmartTV supports the Convergence API for interacting with other devices and leads this trend.
In a Samsung SmartTV application, the Convergence means the Convergence App API that supports a Samsung SmartTV application to communicate with other devices and allows them to control the application. The Samsung SmartTV Convergence provides a REST-type interface for this connection. This implementation requires other devices to support HTTP (including UPnP) to connect the application. (The Convergence and the Convergence App API will be used in interchanging matter.)
While there are many devices that can access a Samsung SmartTV application, this book will use an Android Smart Phone to explain the Convergence. This chapter is primarily written for developers experienced in Android programming, but also can be read for a broad conceptual understanding.
The chapter will show two Convergence practices, and how they are implemented in both a SmartTV application and an Android smart phone. Finally, this chapter will introduce the AllShare framework, which provides an easier communication between Samsung devices only.
Please check the SDF (SDF -> Guide - > API -> Convergence App) for APIs that are not covered in this chapter.
See below for recommended system requirements for testing the following examples.
This chapter will use the Gingerbread Man, a children's storybook application developed by Handstudio, to demonstrate a Convergence application scenario.
There are two methods for connecting a SmartTV and a mobile device — UPnP and HTTP. The SDF recommends the UPnP method.
The UPnP (Universal Plug and Play) method presents a device on a local network so that other devices can sense and connect it without additional configuration. It allows a SmartTV and other local devices to automatically share IP addresses.
The HTTP method requires a SmartTV's IP address to be manually entered on a mobile device. This allows more direct and controlled access. Once devices are connected, the JSON format is used to exchange types and messages.
See below for concept diagrams of full duplex communication between a mobile client and a SmartTV server.
In the SmartTV programming, the Web API contains all Convergence functions.
<script type=“text/javascript” language=“javascript” src=“$MANAGER_WIDGET/ Common/webapi/1.0/webapis.js”></script>
Once the API is declared, all the Web API Convergence member functions and properties can be accessed with JavaScript.
this._API = window.webapis.customdevice || {};
Declare a variable to initialize a Web API instance to finish preparing to use the Convergence.
The SDF provides a guide for member functions of the SmartTV Convergence, for its easier usage. These functions help easy setup and connectivity to other devices without complex network configuration. The following chart lists major Web API functions for the Convergence function.
The preceding functions take care of all connections between devices. Let's see how they are used in practice.
The registerManagerCallback() function creates or breaks connection between a Samsung SmartTV and a mobile device. The member callback function receives connectivity information as arguments. The connectivity information includes a mobile status event that tells if the connection was successful. Other callback functions can be assigned for a different status. The connected mobile device's status is stored in the eventType variable.
The next table shows event values generated while connecting or disconnecting to a device. The argument callback function uses this value to see if a device is being connected or disconnected, to decide the next events.
The following example source code uses a registerManager assigned callback function to handle connect or disconnect events of a mobile device to a SmartTV.
var Convergence = { API: window.webapis.customdevice || {}, aDevice: [], init: function() { this.API.registerManagerCallback(Convergence.registerManager); this.API.getCustomDevices(Convergence.getCustomDevices); }, registerManager: function(oManagerEvent) { var _this = Convergence; alert(‘UID: ’ + oManagerEvent.UID); // UID: mobile device ID alert(‘name: ’ + oManagerEvent.name); // name: mobile device name alert(‘eventType: ’ + oManagerEvent.eventType); // eventType: mobile status event alert(‘deviceType: ’ + oManagerEvent.deviceType); // deviceType: mobile device type switch(oManagerEvent.eventType) { case _this.API.MGR_EVENT_DEV_CONNECT: alert(‘MGR_EVENT_DEV_CONNECT’); _this.API.getCustomDevices(Convergence.getCustomDevices); break; case _this.API.MGR_EVENT_DEV_DISCONNECT: alert(‘MGR_EVENT_DEV_DISCONNECT’); _this.API.getCustomDevices(Convergence.getCustomDevices); break; default: alert(‘EVENT_UNKNOWN’); break; } }, Convergence.init();
The next getCustomDevices() function receives an instance of the connected device as an array property. The device information can be used to assign different callback functions. It is used to update mobile connectivity information when there is a change in mobile connection.
The following example uses the getCustomDevice() function.
var Convergence = { API: window.webapis.customdevice || {}, aDevice: [], init: function() { this.API.registerManagerCallback(Convergence.registerManager); this.API.getCustomDevices(Convergence.getCustomDevices); }, registerManager: function(oManagerEvent) { var _this = Convergence; switch(oManagerEvent.eventType) { case _this.API.MGR_EVENT_DEV_CONNECT: alert(‘MGR_EVENT_DEV_CONNECT’); _this.API.getCustomDevices(Convergence.getCustomDevices); break; case _this.API.MGR_EVENT_DEV_DISCONNECT: alert(‘MGR_EVENT_DEV_DISCONNECT’); _this.API.getCustomDevices(Convergence.getCustomDevices); break; default: alert(‘EVENT_UNKNOWN’); break; } }, getCustomDevices: function(aDevice) { var _this = Convergence; _this.aDevice = aDevice; alert(‘aDevice.length: ’ + aDevice.length); for(var i = 0; i < aDevice.length; i++) { var sID = aDevice[i].getUniqueID(); alert(‘getUniqueID: ’ + aDevice[i].getUniqueID()); // getUniqueID: mobile device ID alert(‘getName: ’ + aDevice[i].getName()); // getName: mobile device name alert(‘getDeviceID: ’ + aDevice[i].getDeviceID()); // deviceID: mobile device ID alert(‘getType: ’ + aDevice[i].getType()); // getType: mobile device type aDevice[i].registerDeviceCallback(function(oDeviceInfo) { _this.registerDevice(sID, oDeviceInfo); }); } } }; Convergence.init();
The next registerDeviceCallback() function receives an instance of the connected mobile device and uses it to set events for the assigned callback function. The infoType property holds status information of the mobile instance. Table 11-7 lists possible event values of the infoType property. This function is used to assign behavior according to a message from a mobile device.
The next example uses the registerDeviceCallback() function.
var Convergence = { API: window.webapis.customdevice || {}, aDevice: [], init: function() { this.API.registerManagerCallback(Convergence.registerManager); this.API.getCustomDevices(Convergence.getCustomDevices); }, registerManager: function(oManagerEvent) { var _this = Convergence; switch(oManagerEvent.eventType) { case _this.API.MGR_EVENT_DEV_CONNECT: alert(‘MGR_EVENT_DEV_CONNECT’); _this.API.getCustomDevices(Convergence.getCustomDevices); break; case _this.API.MGR_EVENT_DEV_DISCONNECT: alert(‘MGR_EVENT_DEV_DISCONNECT’); _this.API.getCustomDevices(Convergence.getCustomDevices); break; default: alert(‘EVENT_UNKNOWN’); break; } }, getCustomDevices: function(aDevice) { var _this = Convergence; _this.aDevice = aDevice; alert(‘aDevice.length: ’ + aDevice.length); for(var i = 0; i < aDevice.length; i++) { var sID = aDevice[i].getUniqueID(); aDevice[i].registerDeviceCallback(function(oDeviceInfo) { _this.registerDevice(sID, oDeviceInfo); }); } }, registerDevice: function(sID, oDeviceInfo) { var _this = Convergence; alert(‘sID: ’ + sID); // sID: unique mobile device ID alert(‘infoType: ’ + oDeviceInfo.infoType); // infoType: mobile device information type for(var key in oDeviceInfo.data) { alert(key + ‘ : ’ + oDeviceInfo.data[key]); } // data: data received from the mobile device switch(oDeviceInfo.infoType) { case _this.API.DEV_EVENT_MESSAGE_RECEIVED: alert(‘DEV_EVENT_MESSAGE_RECEIVED’); break; case _this.API.DEV_EVENT_JOINED_GROUP: alert(‘DEV_EVENT_JOINED_GROUP’); break; case _this.API.DEV_EVENT_LEFT_GROUP: alert(‘EVENT_DEVICE_LEFT_GROUP’); break; } } }; Convergence.init();
The Convergence App API can also transfer files, in addition to sending text messages. This allows a mobile device to send an image file to a SmartTV and display it on the TV screen. In this case, the SmartTV application only needs to get the location of the mobile transferred image file and display it on the desired coordinate.
Maximum file size for the mobile to Samsung SmartTV transfer is 3MB. However, it is recommended to use the smallest possible files to avoid network delay. Note that the transfer cannot replace an existing file. The preceding URL address format is used to display an image file on the SmartTV screen.
A SmartTV can also transfer data to a mobile device. The next member functions enable the data transfer.
A Convergence app functions through a connection between mobile devices and a SmartTV. This calls for a protocol that will connect two different devices. Samsung Electronics' SDF provides the Convergence App API Guide to provide request header and application methods information.
Figure 11-4 maps how a Convergence App functions. The design is based on the REST interface and uses a URL format to define all the details for file transfer. This interface is standardized and simpler than the SOAP interface. It allows fast development cycle and fast execution.
The SDF suggested model is using an HTTP protocol-supporting mobile device to discover and connect through the UPnP scan.
This chapter will focus on the mobile device (client) part of the Samsung SmartTV Convergence App API, and explain how to implement connecting, sending a message, sending an image, receiving a message, and disconnecting between an Android-based mobile device and a SmartTV.
A mobile device uses the HTTP protocol to send a message to a SmartTV, using the following header information. Some of the header information is mandatory, while some is optional. It is vital to have a clear idea when using the table.
SLDeviceID is a header component that allows a SmartTV application to identify a client device. Usually, a unique device ID (UUID) is used. This component is mandatory for all requests except the Get Application Info method.
This header component indicates the content type in a JSON or XML value. It is mandatory for all POST requests.
This header identifies a smart device, and is mandatory for a connection request. Note that the ID must be an exactly eight-letter string that starts with “SMART.”
Example: SMARTDev, SMARTtvi, etc.
This header identifies the smart device's manufacturer, and is mandatory for a connection request. Note that the ID must also be an exactly eight-letter string.
Example: vendorMe, vendorTV
This header contains the name of the client device, and is mandatory for a connection request. This header can be up to 64 letters.
This header is used to identify type and specification of the client device, and is mandatory for all requests.
This user-defined header can be added if necessary. It is packaged in a JSON object to be sent.
In a Convergence app, a mobile device and a SmartTV communicate using the HTTP protocol. Therefore, its status response code is also the same as the standard HTTP protocol's response code. See the following table for a list of the response codes.
Since a Convergence app's main functions are handled through message exchanges between two devices, Connect, Send Message, Send Image, Retrieve Message, and Disconnect are the most frequently used methods. Descriptions and examples of the five major methods follow.
The Connect method connects a mobile device to a SmartTV. Keyword connect is used for this request. SLDeviceID, VendorID, and ProductID header components are mandatory.
This method adds a message to a SmartTV's message queue. The message body can be transmitted in JSON format. See the following table's (body) label for the JSON-formatted message with type and value pairs.
The Send Image request uses the same queue keyword as the Send Message request. Therefore, its header components are also very similar. Instead of the JSON object, this request includes the filename for the image to send.
This method checks if the SmartTV's message queue has any message to be sent to the mobile device. If there is a message, it will be JSON formatted and returned with a response code. Instead of actually sending a message, a SmartTV simply stores a message in its queue for a mobile device to periodically check and retrieve it.
The Disconnect method is sent by the mobile device (client) to be disconnected from the SmartTV (server.) The method only works when the two devices are already connected. Otherwise, the 404 response code will be returned.
Now that we have reviewed protocols for the mobile device—SmartTV Convergence app, let's see an example that actually connects the two devices. The SDF provides connect() for making a connecting; disconnect() for disconnecting; queue() for message exchange; info() for receiving TV application information; join() for device grouping; and leave() for device ungrouping.
This chapter will cover the more common connect(), disconnect(), and queue() methods to implement connecting, disconnecting, message exchanging, and image transferring. The following examples are for the Android platform.
Please note that some codes were repeated to demonstrate the preceding functions one at a time. For a production app, please increase the efficiency and readability of the code by modulating those repeated codes into functions. Also, it would be better to have separate implementations for AbstractHttpMessage objects, since the GET method is used for message or application information receiving.
All convergence app communication is processed in the URL format. As shown above, the URL format needs the TV's IP address, port number, TV Application ID, and the communication method. Note that the mobile device and SmartTV need to be in the same local network, and the port number change depends on the network environment. Also, a production app needs to use a Samsung-issued application ID.
An Internet router is commonly used to develop a convergence app. An Internet router usually assigns a 192.168.AAA.BBB type private IP address to connected local devices. The same AAA value means that the two devices are in the same local network. Different AAA values mean that the two devices are connected to two different Internet routers and cannot connect to each other.
Different devices usually use different port number. For example, a SmartTV uses port 80, while an emulator uses port 8008. An incorrect port number in the URL request will result in the 404 error code. Any random application ID can be used for a test, but a 12-digit Samsung-issued application ID must be used for a production app.
A mobile device uses a request URL with the Connect keyword to connect to a SmartTV. The platform supports security-enhanced HTTPS in addition to HTTP. The HTTPS method requires a security BKS file (key store) and an SSLSocket request. This book will only cover a normal HTTP-based connection.
private void connect() { // URL Configuration String URLStr = “http://” + IP Address + “:” + Port Number + “/ws/app/” + Application ID + “/connect”; URL URL = null; try { URL = new URL(URLStr); } catch (MalformedURLException e) { e.printStackTrace(); } // Create a HttpClient object and configure protocol HttpClient httpClient = new DefaultHttpClient(); ProtocolVersion protocol = new ProtocolVersion(“HTTP”, 1, 1); httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, protocol); // Create a Connect request header AbstractHttpMessage message = new HttpPost(URLStr); message.setParams(new BasicHttpParams().setParameter(URLStr, URL)); message.addHeader(“User-Agent”, “Android-Phone”); message.addHeader(“SLDeviceID”, “12345”); message.addHeader(“VendorID”, “VendorTV”); message.addHeader(“ProductID”, “SMARTdev”); message.addHeader(“DeviceName”, “SamsungGalaxyS3”); try { // Send the Connect request to the TV (returns HttpResponse object) HttpResponse response = httpClient.execute((HttpUriRequest) message); // Returns the response code int statusCode = response.getStatusLine().getStatusCode(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
With the Connect request successfully processed, let's move to sending a message. A similar step to the preceding Connect request will be used, except that a JSON-formatted message will be sent along. Receive a pair of strings formatted type and msg and create a JSON object, convert it to a string, in byte format, and include it in an HttpEntityEnclosingRequest object to be sent.
private void sendMessage(String type, String msg) { // URL Configuration String URLStr = “http://” + IP Address + “:” + Port Number + “/ws/app/” + Application ID + “/queue”; URL URL = null; try { URL = new URL(URLStr); } catch (MalformedURLException e) { e.printStackTrace(); } // Create a JSON object and store type and message values JSONObject jsonObj = new JSONObject(); try { jsonObj.put(JSON_TYPE, type); jsonObj.put(JSON_VALUE, msg); } catch (JSONException e1) { e1.printStackTrace(); } // Convert the JSON object to a String String body = jsonObj.toString(); // Create an HttpClient object and configure protocol HttpClient httpClient = new DefaultHttpClient(); ProtocolVersion protocol = new ProtocolVersion(“HTTP”, 1, 1); httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, protocol); // Create a Send Message request header AbstractHttpMessage message = new HttpPost(URLStr); message.setParams(new BasicHttpParams().setParameter(URLStr, URL)); message.addHeader(“User-Agent”, “Android-Phone”); message.addHeader(“SLDeviceID”, “12345”); // // Translate type and message data in the JSON object into bytes AbstractHttpEntity entity = new ByteArrayEntity(body.getBytes()); // Configure Content-Type header entity.setContentType(“application/json”); // Store the entity object with byte format type // and message data into the HttpEntityEnclosingRequest object HttpEntityEnclosingRequest entityMessage = (HttpEntityEnclosingRequest) message; entityMessage.setEntity(entity); try { Send the message to the TV (returns HttpResponse object) HttpResponse response = httpClient.execute((HttpUriRequest) entityMessage) ; // Returns the response code int statusCode = response.getStatusLine().getStatusCode(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
The Send Image uses the same queue method as the Send Message, and similar steps are used. Store type value and image filename into a JSON object, convert it to a string, and include it in a Multipart Entity object. The image itself is included in the FileBody object. Then package it into an HttpEntityEnclosingRequest and send it in an HttpClient object to the SmartTV.
private void sendImage(String FileName) { File path and URL Configuration String filePath = Environment.getExternalStorageDirectory(). getAbsolutePath() + “/Download/”; String URLStr = “http://” + IP Address + “:” + Port Number + “/ws/app/” + Application ID + “/queue”; URL URL = null; try { URL = new URL(URLStr); } catch (MalformedURLException e) { e.printStackTrace(); } // Create a JSON object and store type and message values JSONObject jsonObj = new JSONObject(); try{ jsonObj.put(“type”, “upload_image”); jsonObj.put(“msg”, FileName); } catch(Exception e) { e.printStackTrace(); } // Convert the JSON object to a String String body = jsonObj.toString(); int bodyLength = jsonObj.toString().length(); // Create an HttpClient object and configure protocol HttpClient httpClient = new DefaultHttpClient(); ProtocolVersion protocol = new ProtocolVersion(“HTTP”, 1, 1); httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, protocol); // Create a request header AbstractHttpMessage message = new HttpPost(URLStr); message.setParams(new BasicHttpParams().setParameter(URLStr, URL)); message.addHeader(“User-Agent”, “Android-Phone”); message.addHeader(“SLDeviceID”, “12345”); // Create a MultipartEntity object and set up FileBody, StringBody properties MultipartEntity multiEntity = new MultipartEntity(); try { // Use the image file's path to create a File object File file = new File(filePath + FileName); multiEntity.addPart(“upload_image”, new FileBody(file, FileName, “photo”, null)); multiEntity.addPart(“upload_image”, new StringBody(body)); } catch(Exception e) { e.printStackTrace(); } // Store the MultipartEntity object with Send Image information // to an HttpEntityEnclosingRequest object HttpEntityEnclosingRequest entityMessage = (HttpEntityEnclosingRequest) message; entityMessage.setEntity(multiEntity); try { // Send the message to the TV (returns HttpResponse object) HttpResponse response = httpClient.execute((HttpUriRequest) entityMessage); org.apache.http.Header[] header = response.getAllHeaders(); // Returns the response code int statusCode = response.getStatusLine().getStatusCode(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
The message retrieval process doesn't directly receive a message. Instead, it makes a request for a response object that contains a message, and retrieves a message from the object. First, the process creates an HttpClient object and requests for a message to the SmartTV. Then the SmartTV takes a message from its message queue, and sends it with the response object. Then the mobile device converts it to a string and finally parses it to a JSON-format type and msg pair.
The returned value can be used by activities that need the message, using the Android application's internal broadcast intent method. This entire process is implemented in the following sample code.
The Send Image uses the same queue method as the Send Message, and similar steps are used. Store type value and image filename in a JSON object, convert it to a string, and include it in a Multipart Entity object. The image itself is included in the FileBody object. Then package it in an HttpEntityEnclosingRequest and send it in an HttpClient object to the SmartTV.
// Retrieves a message from the TV private void receiveMessage() { HttpResponse response = null; String type = “”; String msg = “”; // URL Configuration String URLStr = “http://” + ipAddress + “:” + portNumber + “/ws/app/” + appId + “/queue/device/” + “12345”; URL URL = null; try { URL = new URL(URLStr); } catch (MalformedURLException e) { e.printStackTrace(); } // Create an HttpClient object and configure protocol HttpClient httpClient = new DefaultHttpClient(); ProtocolVersion protocol = new ProtocolVersion(“HTTP”, 1, 1); httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, protocol); Create a Retrieve Message request header AbstractHttpMessage message = new HttpGet(URLStr); // GET Type message.setParams(new BasicHttpParams().setParameter(URLStr, URL)); message.addHeader(“SLDeviceID”, “12345”); try { // Request for a message (returns Response object) aresponse = httpClient.execute((HttpUriRequest) message); int statusCode = response.getStatusLine().getStatusCode(); // If the return code is 200 (Success) if(statusCode == 200) { // Translate the message in the Response object HttpEntity entity = response.getEntity(); if(entity != null) { // Convert an InputStream format message in a String value InputStream is = entity.getContent(); StringBuffer out = new StringBuffer(); byte[] b = new byte[4096]; for(int n; (n=is.read(b)) != -1;) { out.append(new String(b, 0, n)); } String responseBody = out.toString(); // Store the String format message's type and msg value in a JSON object JSONObject jsonObj; try { jsonObj = new JSONObject(responseBody); if(jsonObj != null) { type = jsonObj.getString(“type”); msg = jsonObj.getString(“msg”); } }catch (JSONException e) { e.printStackTrace(); } } // If there is a converted type and msg pair, send // it using the internal broadcast mechanism. if(!type.equals(“”) && !msg.equals(“”)) { Intent intent = new Intent(); // RECEIVE constant value is used for message filtering by the receiver intent.setAction(RECEIVE); intent.putExtra(“type”, type); intent.putExtra(“msg”, msg); sendBroadcast(intent); } } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // Broadcast receiver private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Use received Intent's Action value for message filtering String action = intent.getAction(); if(action.equals(RECEIVE)) { String type = intent.getStringExtra(“type”); String msg = intent.getStringExtra(“msg”); Log.e(TAG, “[type] ” + type + “ [msg] ” + msg); // Use Log function to check the message value } } };
A mobile device uses this function to request a SmartTV to be disconnected. The request returns the 404 response code if the two devices are not connected.
private void disconnect() { // URL Configuration String URLStr = “http://” + IP Address + “:” + Port Number + “/ws/app/” + Application ID + “/disconnect”; URL URL = null; try { URL = new URL(URLStr); } catch (MalformedURLException e) { e.printStackTrace(); } // Create an HttpClient object and configuration protocol HttpClient httpClient = new DefaultHttpClient(); ProtocolVersion protocol = new ProtocolVersion(“HTTP”, 1, 1); httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, protocol); // Create a Disconnect request header AbstractHttpMessage message = new HttpPost(URLStr); message.setParams(new BasicHttpParams().setParameter(URLStr, URL)); message.addHeader(“SLDeviceID”, “12345”); try { // Send the Disconnect request to the TV (returns Response object) HttpResponse response = httpClient.execute((HttpUriRequest) message); // Returns the response code int statusCode = response.getStatusLine().getStatusCode(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
The AllShare Framework provides an easy way to enjoy and share media contents (VOD, pictures, music, etc.) between Samsung devices. It provides many convenient features, such as playing smart phone–stored content on a TV without copying it into the TV, using a smart phone to play web-stored media content on a SmartTV, mirroring a smart phone's screen with a SmartTV, and sending files.
This may sound similar to the Convergence App API. But it has its own purpose. While the Convergence App API connects a smart phone app with a SmartTV app, the AllShare Framework shares and plays media contents using DLNA and wireless Internet (Wi-Fi Direct).
This chapter will introduce, among other AllShare Framework functions, playing a VOD file stored in a smart phone on a SmartTV screen. This function allows using the AllShare button to play a Samsung Android smart phone VOD on a TV.
The following methods are used to play a VOD using the AllShare Framework.
See the recently published AllShare Framework document.
See below for recommended system requirements for testing the following examples.
The AllShare Framework requires Android SDK 4.1.2 (API 16) or higher. Verify the Android version before installing the AllShare Framework.
01. Installing the AllShare Framework Development Tool on the AllShare SDK Use the following URL address to download the tool, as you did for the ADT.
For more information on installing the framework, visit http://developer.samsung.com/allshare-framework/start and read 1.2. Installing AllShare Framework Development Tool and SDK.
02. Create an Android AllShare Project
After installing the AllShare Framework Development Tool and SDK, the “New Project” menu will have the new “AllShare Android Project” option.
For more information on creating an Android AllShare project, visit http://developer.samsung.com/allshare-framework/start and read 2.1, the Creating a New AllShare project.
A newly created AllShare project has the following differences from a standard Android project. A standard Android project can also have the AllShare feature by manually adding access rights and the AllShare library in the manifest option.
<?xml version=“1.0” encoding=“utf-8” standalone=“no”?> <manifest xmlns:android=“http://schemas.android.com/apk/res/android” package=“com.hz.allshareplayer” android:versionCode=“1” android:versionName=“1.0” > <uses-sdk android:minSdkVersion=“16” /> <!-- User permission for using the AllShare Framework --> <uses-permission android:name=“com.sec.android.permission.PERSONAL_MEDIA” /> <!-- Additional user permission for using the AllShare Framework for AllShare Cast and Remote control --> <uses-permission android:name=“com.android.setting.permission.ALLSHARE_ CAST_SERVICE” /> <uses-permission android:name=“android.permission.ACCESS_NETWORK_STATE”/> <uses-permission android:name=“android.permission.ACCESS_WIFI_STATE”/> <application android:icon=“@drawable/ic_launcher” android:label=“@string/app_name” > <activity android:name=“.AllSharePlayerActivity” android:label=“@string/app_name” > <intent-filter> <action android:name=“android.intent.action.MAIN” /> <category android:name=“android.intent.category.LAUNCHER” /> </intent-filter> </activity> </application> </manifest>
Use the DeviceFinder class to find an AllShare device. The DeviceFinder class provides the following four functions.
The following example will use the last function that finds a device with the set Device ID and DeviceType values.
The AllShare Framework provides several device types. The device type can be changed after acquiring the device instance. See Table 11-19 for the supported device types.
Let's use the AV Player with DEVICE_ALLPLAYER.
After retrieving the device list, use DeviceClass to retrieve information for each device. The AllShare Framework provides the following information.
Once the preceding functions and types are familiarized, review the next example to see how they are used to find a device. The example uses the AllShare Framework to find a VOD playback-capable device.
package com.hz.allshareplayer; import java.util.ArrayList; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; import com.sec.android.allshare.Device; import com.sec.android.allshare.Device.DeviceDomain; import com.sec.android.allshare.Device.DeviceType; import com.sec.android.allshare.DeviceFinder; import com.sec.android.allshare.DeviceFinder.IDeviceFinderEventListener; import com.sec.android.allshare.ERROR; import com.sec.android.allshare.ServiceConnector; import com.sec.android.allshare.ServiceConnector.IServiceConnectEventListener; import com.sec.android.allshare.ServiceConnector.ServiceState; import com.sec.android.allshare.ServiceProvider; public class AllSharePlayerActivity extends Activity { public static final String TAG = “AllSharePlayerActivity”; private ServiceProvider mServiceProvider = null; private TextView mText = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mText = (TextView) findViewById(R.id.txtLog); mText.append(“ ” + “Creating service provider!” + “ ”); ERROR err = 1ServiceConnector.createServiceProvider(this, new IServiceConnectEventListener() { @Override public void onCreated(ServiceProvider sprovider, ServiceState state) { mServiceProvider = sprovider; 2showDeviceList(); } @Override public void onDeleted(ServiceProvider sprovider) { mServiceProvider = null; } }); if (err == ERROR.FRAMEWORK_NOT_INSTALLED) { // AllShare Framework Service is not installed. } else if (err == ERROR.INVALID_ARGUMENT) { // Input argument is invalid. Check and try again } else { // Success on calling the function. } } private final DeviceFinder.IDeviceFinderEventListener mDeviceDiscoveryListener = new IDeviceFinderEventListener() { @Override public void onDeviceRemoved(DeviceType deviceType, Device device, ERROR err) { mText.append(“AVPlayer: ” + device.getName() + “ [” + device. getIPAddress() + “] is removed” + “ ”); } @Override public void 6onDeviceAdded(DeviceType deviceType, Device device, ERROR err) { mText.append(“Add - AVPlayer: ” + device.getName() + “ [” + device.getIPAddress() + “] is found” + “ ”); } }; private void showDeviceList() { if (mServiceProvider == null) return; 3DeviceFinder mDeviceFinder = mServiceProvider.getDeviceFinder(); 4mDeviceFinder.setDeviceFinderEventListener(DeviceType.DEVICE_ AVPLAYER, mDeviceDiscoveryListener); 5ArrayList<Device> mDeviceList = mDeviceFinder. getDevices(DeviceDomain.LOCAL_NETWORK, DeviceType.DEVICE_AVPLAYER); if (mDeviceList != null) { for (int i = 0; i < mDeviceList.size(); i++) { mText.append(“AVPlayer: ” + mDeviceList.get(i).getName() + “ [” + mDeviceList.get(i).getIPAddress() + “] is found” + “ ”); } } } @Override protected void onDestroy() { if (mServiceProvider != null) 7ServiceConnector.deleteServiceProvider(mServiceProvider); super.onDestroy(); } }
1 ServiceConnector.createServiceProvider – Initialize the service to use the AllShare Framework.
2 After step 1 is successfully completed, the onCreated callback function will be called by the listener. The function then calls the showDeviceList() function that performs the actual device browsing.
3 Initialize DeviceFinder using the getDeviceFinder() function.
4 Register IDeviceFinderEventListener to DeviceFinder. If the AllShare Framework's target device is added or removed, the onDeviceAdded, or onDeviceRemoved callback function is called.
5 Search for AllShare Framework VOD playback-capable local target devices using the getDevices() function.
Warning: The target device search function uses an Ajax request. A slower network may cause step 3 to be executed before step 5, resulting in devices not found.
6 Target devices not found in step 5 will be eventually found by the IDeviceFinder EventListener's onDeviceAdded callback function.
7 Disconnect the AllShare Framework using ServiceConnector.deleteServiceProvider.
The AllShare Framework uses an item instance to play media files. This item receives FilePath and MIME type as parameters.
The AV Player has one of the following statuses.
The UNKNOWN status indicates that there is a network connectivity error and the application or service cannot set the status.
The CONTENT_CHANGED status indicates that another device changed its media contents. An application needs to change the playing UI when receiving this event.
package com.hz.allshareplayer; import java.util.ArrayList; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import com.sec.android.allshare.Device; import com.sec.android.allshare.Device.DeviceDomain; import com.sec.android.allshare.Device.DeviceType; import com.sec.android.allshare.DeviceFinder; import com.sec.android.allshare.DeviceFinder.IDeviceFinderEventListener; import com.sec.android.allshare.ERROR; import com.sec.android.allshare.Item; import com.sec.android.allshare.ServiceConnector; import com.sec.android.allshare.ServiceConnector. IServiceConnectEventListener; import com.sec.android.allshare.ServiceConnector.ServiceState; import com.sec.android.allshare.ServiceProvider; import com.sec.android.allshare.media.AVPlayer; import com.sec.android.allshare.media.AVPlayer.AVPlayerState; import com.sec.android.allshare.media.ContentInfo; import com.sec.android.allshare.media.MediaInfo; public class AllSharePlayerActivity extends Activity { public static final String TAG = “AllSharePlayerActivity”; private ServiceProvider mServiceProvider = null; private TextView mText = null; private String mDeviceID = null; private AVPlayer mPlayer = null; private Item mItem = null; private String filePath = “/mnt/sdcard/Movies/test.mp4”; private String mimeType = “video/mp4”; boolean isPlay = false; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mText = (TextView) findViewById(R.id.txtLog); mText.append(“ ” + “Creating service provider!” + “ ”); ERROR err = ServiceConnector.createServiceProvider(this, new IServiceConnectEventListener() { @Override public void onCreated(ServiceProvider sprovider, ServiceState state) { mServiceProvider = sprovider; showDeviceList(); } @Override public void onDeleted(ServiceProvider sprovider) { mServiceProvider = null; } }); if (err == ERROR.FRAMEWORK_NOT_INSTALLED) { // AllShare Framework Service is not installed. } else if (err == ERROR.INVALID_ARGUMENT) { // Input argument is invalid. Check and try again } else { // Success on calling the function. } } private final DeviceFinder.IDeviceFinderEventListener mDeviceDiscoveryListener = new IDeviceFinderEventListener() { @Override public void onDeviceRemoved(DeviceType deviceType, Device device, ERROR err) { mText.append(“AVPlayer: ” + device.getName() + “ [” + device. getIPAddress() + “] is removed” + “ ”); } @Override public void onDeviceAdded(DeviceType deviceType, Device device, ERROR err) { mText.append(“Add - AVPlayer: ” + device.getName() + “ [” + device. getIPAddress() + “] is found” + “ ”); if(!isPlay){ mText.append(“ Play : ” + device.getName() + “ [” + device. getIPAddress() + “]” + “ ”); startAllShare(device.getID()); } } }; private void showDeviceList() { if (mServiceProvider == null) return; DeviceFinder mDeviceFinder = mServiceProvider.getDeviceFinder(); mDeviceFinder.setDeviceFinderEventListener(DeviceType.DEVICE_ AVPLAYER, mDeviceDiscoveryListener); ArrayList<Device> mDeviceList = mDeviceFinder.getDevices(DeviceDomain. LOCAL_NETWORK, DeviceType.DEVICE_AVPLAYER); if (mDeviceList != null) { for (int i = 0; i < mDeviceList.size(); i++) { mText.append(“AVPlayer: ” + mDeviceList.get(i).getName() + “ [” + mDeviceList.get(i).getIPAddress() + “] is found” + “ ”); if(!isPlay){ mText.append(“ Play : ” + mDeviceList.get(i).getName() + “ [” + mDeviceList.get(i).getIPAddress() + “]” + “ ”); startAllShare(mDeviceList.get(i).getID()); } } } } @Override protected void onDestroy() { if (mServiceProvider != null) ServiceConnector.deleteServiceProvider(mServiceProvider); super.onDestroy(); } private void initItem() { DeviceFinder deviceFinder = mServiceProvider.getDeviceFinder(); AVPlayer avPlayer = (AVPlayer) deviceFinder.getDevice(mDeviceID, DeviceType.DEVICE_AVPLAYER); // 1 if (avPlayer == null) { return; } Item.LocalContentBuilder lcb = new Item.LocalContentBuilder(filePath, mimeType); // 4 lcb.setTitle(“”); mItem = lcb.build(); mPlayer = avPlayer; mItem = new Item.LocalContentBuilder(filePath, mimeType).build(); } private void startAllShare(String deviceId) { mDeviceID = deviceID; // 1 initItem(); // 2 registerEventListener(); // 5 registerResponseHandler();// 6 play();// 7 isPlay = true; } private void play() { ContentInfo.Builder builder = new ContentInfo.Builder(); ContentInfo info = builder.build(); mPlayer.play(mItem, info); } /** * register AVPlayer state changed callback function */ private void registerEventListener() { if (mPlayer == null) return; mPlayer.setEventListener(new AVPlayer.IAVPlayerEventListener() { @Override public void onDeviceChanged(AVPlayerState state, ERROR err) { Log.d(TAG,“onDeviceChanged state = ” + state); if (AVPlayerState.PLAYING.equals(state)) { mPlayer.getMediaInfo(); } } }); } /** * register AVPlayer callback function */ private void registerResponseHandler() { if (mPlayer == null) return; mPlayer.setResponseListener(new AVPlayer.IAVPlayerPlaybackResponseListener() { @Override public void onStopResponseReceived(ERROR err){ } @Override public void onGetMediaInfoResponseReceived(MediaInfo arg0, ERROR arg1) { } @Override public void onGetPlayPositionResponseReceived(long arg0, ERROR arg1) { } @Override public void onGetStateResponseReceived(AVPlayerState arg0, ERROR arg1) { } @Override public void onPauseResponseReceived(ERROR arg0) { } @Override public void onPlayResponseReceived(Item arg0, ContentInfo arg1, ERROR arg2) { } @Override public void onResumeResponseReceived(ERROR arg0) { } @Override public void onSeekResponseReceived(long arg0, ERROR arg1) { } }); } }
1 mDeviceID = device.getID(): Copy and store a newly added device's ID from onDeviceAdded. The Device ID is necessary to call an AVPlayer instance that handles VOD-playing using the AllShare Framework.
2 Prepare for the playing and create a List of playback information.
3 Find an AllShare Framework media playing–capable device using mDeviceID, and obtain the device's AVPlayer instance.
4 Create a playback information list using a file path from Item.LocalContentBuilder and MIME TYPE.
5 Register AVPlayer.IAVPlayerEventListener so that it can call the onDeviceChanged callback function and notify it, if the AV Player's status changes.
6 Register AVPlayer.IAVPlayerPlaybackResponseListener to receive SmartTV messages using the callback function.
7 Send the prepared VOD to the SmartTV using the AllShare Framework; play it on the TV screen.
Callback functions listed in steps 5 and 6 are described in the AllShare Framework SDK.
A sample program was created to implement playback of a smart phone VOD on a SmartTV screen using the AllShare Framework. Additional coding will be needed to create commercial software. However, the information was enough to implement advanced media contents sharing and playing capability using the AllShare Framework.
Study more example codes on the SDF and try adding more capabilities.
The Samsung SmartTV provides data exchange with other devices using the HTTP and UPnP based Convergence. This capability is implemented using the Web API member functions on a SmartTV, and accessed using response headers and HTTP objects on a mobile device. The AllShare Framework is an additional sharing technology that supports easier connection between a Samsung SmartTV and other Samsung devices.
Merging different device types is not the future technology. The Samsung SmartTV already supports it using the Convergence and AllShare.