A number of people have been interested in accessing internet content from MHP. This makes sense – after all, there’s a lot of content out there that could be used. The problem with doing this right now is that broadcasters need to implement their own client for accessing that content, be it a web browser, email client or other client application.
The Internet Access profile tries to make this a little bit easier. This profile is only included in MHP 1.1, and basically consists of a single additional API that allows an MHP application to control a web browser, email client or newsreader that may be present on the MHP receiver. This of course means that there’s a requirement that a receiver implementing the Internet Access profile actually supports at least one of these client applications.
The philosophy of the internet client API
The internet client API is the first of the MHP APIs to consider the concept of resident applications – that is, MHP-compatible applications which are built in to the MHP receiver instead of being downloaded from the broadcast channel. This gave some additional challenges to the API designers, namely how to make resident applications interoperate as smoothly as possible with downloaded MHP applications.
Doing this involved starting from the very basics and following them to their logical conclusion. Applications are associated with a service, even if they are the only thing in that service. Services are always presented in a service context: even applications running as part of that service are linked to that service context (see the service selection tutorial for details of this). Given these facts, the most logical way to control resident applications was via the JavaTV service selection API.
While it could have been made to work using the application management API (which seems like the most logical choice, at first), this has a number of problems. Normally, services are put together by the network operator with a certain amount of care and attention. This includes making sure that any applications broadcast on that serve interoperate with each other in a sensible way, and with the AV content that’s also contained in the service. If the application management API was allowed to start and stop resident applications as part of the curernt service, then the set of applications in a service effectively increases in a way that the network operator can’t control and which makes it much more difficult to test interoperability.
What happens to an application when the web browser starts? What about the email client? What happens if both the web browser and the email client start? This leads to a great deal of potential ugliness and unpredictability for the network operator, which usually translates into less reliable applications. Using the service selection API gives control back to the receiver manufacturer and allow them to decide what is the most sensible thing to do. The MHP specification already states that what happens in a service context is independent of what happens in other service contexts, within the limits of resource availability. This makes it easier for the middleware developer to decide how and when an application can start a resident application, and guarantees that other running applications won’t be affected.
The internet client follows the philosophy of the JavaTV service selection API pretty closely. Every client application is treated as a service, and an MHP application can start or stop a client application by selecting the appropriate service or by stopping that service. It can create a new service context and select the ‘service’ representing that application in the new service context. This will start the selected client application. Stopping the service in that service context (or destroying the service context) will cause the client application to be stopped.
Why do it this way? Well, this allows the application using the internet client API much more flexibility in the way client applications are run. Since resources are so scarce on an MHP receiver, it may not be possible to run an MHP application and a web browser (for instance) at the same time – there may not be enough memory in the system, or other resources may be too scarce. Normally, the client application wouldn’t be able to run in this case. The internet client API, by using the service selection API, offers another possibility for starting the application, however. If resources are too scarce, the receiver could refuse to create another service context. In that case, the MHP application may choose to start the client application in its own service context. This will kill the application (and stop any broadcast services currently being presented in that service context), but the client application can then start. In some cases, this is an acceptable choice for a network operator – for
instance, when the user is being sent to a web page for a TV show instead of downloading an application.
Using the internet client API
The internet client API is contained in the org.dvb.internet
package. From the point of view of the using application, there are two sets of classes which are important. These are the InternetClientService
interface and its subclasses, and the InternetClient
interface and its subclasses. If you look at the javadoc for these classes, you’ll probably wonder why there are two sets of interfaces. Sub-interfaces of the InternetClientService
interface provide operations that are common to all instances of the client in question. Operations that fall in to this category include getting the user’s email address, adding an entry to an email address book or to a web browser’s bookmarks.
Sub-interfaces of the InternetClient
interface, on the other hand, provide operations that apply to a single running copy of a client application. These operations include creating a new email message, or going to a specific URL in a browser.
Why do we have two class hierarchies for handling this? The answer is really quite simple – since all of these are interfaces, there’s no way of defining static methods. If we could do this in Java, then all of the methods defined by InternetClientService
and its sub-interfaces would be static.
One thing to note is that operations on InternetClient
and its subclasses only succeed if an instance of the internet client in question is running. For instance, attempting to go to a URL in a web browser will fail if no web browser is running.
Now that we’ve seen the two main class hierarchies, we’ll look at the sub-interfaces. Since the two class hierarchies deal with the same clients, we will examine the clients in turn rather than examining the hierarchies. This gives a more logical view into how these objects relate to one another.
General operations
The InternetClientService
interface represents the service that corresponds to a given internet client. This is a subclass of javax.tv.service.Service
, and so it has all of the methods that one would expect. Calling the getLocator()
method returns a locator that can be used to select the service that starts that client. Applications can use the canRunApplication()
method to find out whether the client in question is able to run at the same time as the downloaded application. This lets the downloaded application decide the best course of action when it wants to display internet content.
For a specific instance of an internet client, the InternetClient
interface provides a couple of useful methods. An application can register as a listener for events from the internet client using the addInternetClientListener()
method. These events, which are subclasses of the InternetClientEvent
class, allow the application to find out whether as particular operation succeeded or failed. Since the client may not be able to respond immediately (after all, the term world-wide wait does have a certain amount of truth to it), this allows the applications to get some status without blocking.
The InternetClient
interface inherits from the javax.tv.service.ServiceContentHandler
interface, just like any other class that presents content from a service. This provides a couple of methods that are not terribly useful in this case. The getService()
method returns a reference to the InternetClientService
object that corresponds to the type of client represented by this object (e.g. a WWWBrowser
object would return a WWWBrowserService
object), while a call to getServiceContentLocators()
returns an array containing the locator for the service. This has the same effect as calling InternetClientService.getLocator()
for the appropriate type of service, except that the locaotr is returned in an array with one element.
One thing must be born in mind when using this API. Many of the methods in this API will cause the client application to write something to persistent storage -a new bookmark, a new address book entry or an additional newsgroup. Given that there is a limited amount of persistent storage available, new entries may not be added. In this case, an attempt to add a new entry will throw a java.io.IOException
.
Email clients
Email clients are represented by the EmailClient
and EmailClientService
interfaces.
The EmailClientService
interface allows the application to add an entry to the user’s address book by calling the addToAddressBook()
method. This takes two strings – the first contains the email address to be added, while the second contains the name of the entry. If an application wants to get the email address of the user for any reason (e.g. to automatically fill in a field in the a form within the application), it can call the getUserEmailAddress()
method. This returns a string containing the default email address if it is set. If the application doesn’t have permission to access the email address, or if no email address has been set, then this method will return null.
To actually send an email address, the application can use the EmailClient
interface. This provides a single method, createMessage()
, which creates (and possibly sends) a new email message. The full interface of this method is:
public void createMessage( java.lang.String to, java.lang.String subject, java.lang.String messageBody, java.lang.String sender);
The parameters to this method are as follows:
- to
- The address to which the email will be sent.
- subject
- The subject of the message.
- messageBody
- The text to be contained in the body of the message.
- sender
- The email address that the application will use as the sender of the message. If the sender is null, then the default email address will be used.
By allowing the application to specify the sender, the API allows more secure use of the email functionality. Any message that is created using the user’s email address for the sender is not sent automatically – the user must choose to send it. This gives them a chance to review any emails sent by an application in their name. Other sender names may be used by applications to send email automatically, but since these are not the address of the user, there is less room for abuse.
Web browsers
The WWWBrowser
and WWWBrowserService
give an application a means for controlling a browser. Just as for email clients, the functionality is split between these two classes.
The WWWBrowser
interface gives the application a way of directing the browser to a specific URL, using the goToURL()
method. This takes a java.net.URL
object as an argument, and simply causes the browser to open that URL. This will not cause the browser to open a new window, and so any page that’s currently displayed by that browser instance will be lost.
To add a bookmark to the current user’s bookmarks list, the WWWBrowserService
interface provides the addBookmark()
. There are two versions of this method, one accepting a java.net.URL
and the other accepting a javax.tv.locator.Locator
object as the first argument. Both versions of the method also take a textual description of the bookmark as their second argument. One thing to note is that it may not always be possible to add a bookmark. The receiver may only have a limited amount of space to store the bookmarks, and so if that space is filled, no more bookmarks can be written. In this case, a java.io.IOException
will be thrown.
In cases where the application has permission to do so, it can even change the user’s home page using the setHomepage()
method. This takes a java.net.URL
object that represents the new home page. Since it would probably be a bad thing to allow every application to change the user’s home page without consulting them, applications must have the org.dvb.internet.HomePagePermission
before they can do this.
The WWWBrowserService
interface also provides a mechanism that allows the application to query the capabilities of the browser. This is even more important on an MHP receiver than it is on a desktop, since the capabilities of the device are much more limited. By querying the capabilities of the web browser, the application can make sure that it directs the user to the page that displays best on his or her receiver.
The methods for querying the capabilities of the browser are shown below, in the full interface of the WWWBrowserService
class:
public interface WWWBrowserService extends InternetClientService { public void addBookmark( javax.tv.locator.Locator locator, java.lang.String name); public void addBookmark( java.net.URL bookmarkUrl, java.lang.String name); public void setHomepage(java.net.URL defaultUrl); public boolean areFramesSupported(); public java.lang.String[] getAcceptedMediaTypes(); public java.lang.String[] getSupportedPlugins(); public java.lang.String getUserAgent(); }
The getAcceptedMediaTypes()
method returns an array of strings containing the MIME content types supported by the browser. Similarly, the getSupportedPlugins()
method returns an array of strings containing the names of the plugins that are currently installed for the browser. These names are defined by the plugin developer or the browser developer, and are not standardised in MHP.
Finally the getUserAgent()
method returns the user agent string, as specified in the HTTP headers that the browser transmits.
News readers
A Usenet newsreader application is represented by the UsenetClient
and UsenetClientService
interfaces.
To subscribe the user to a newsgroup, the UsenetClientService
provides the subscribe()
method. This takes a string containing the name of the newsgroup to be subscribed. The MHP specification states that as a side-effect, this method may cause the newsreader client to unsubscribe a group that’s already been subscribed to using this method. The spec isn’t terribly clear on what this means, however, and there has been no other clarification that I’m aware of. Caveat developer.
The UsenetClient
interface allows the application to direct the newsreader application to a specific group or message using the selectGroup()
or selectMessage()
methods. Both of these methods take a java.net.URL
that refers to the group or message in question.
A practical example
Now that we’ve seen the various elements of the API, let’s take a look at how it’s used in practice. The example below relies fairly heavily on the JavaTV service selection and service information APIs, and so if you’re not familiar with those APIs, now would be a good time to review them.
// First, we have to use the JavaTV SI API to get a // reference to the WWWBrowserService object representing // the browser on this receiver. // Create an SIManager instance that we can use to do the // query siDatabase = SIManager.createInstance(); // Create an InternetServiceFilter that filters on web // browsers only. We will use this with the JavaTV // service navigation API to get the service // representing the web browser InternetServiceFilter myWebFilter; myWebFilter = new InternetServiceFilter( InternetServiceFilter.WWW_CLIENT); // Get the list of services that match our filter ServiceList services; services = siDatabase.filterServices(myWebFilter); // Now that we've got the list of services, we need to // find the first (and probably only) instance of a // WWWBrowserService that's been found by the filter. // This will store the WWWBrowserService instance once // we've found it Service browserService; // Create an iterator to navigate the service list ServiceIterator iterator; iterator = services.createServiceIterator(); // Iterate over the service list. nextService() will // return the first service in the list the first time // it's called, so we don't need to worry about missing // anything while (iterator.hasNext()) { Service currentService; currentService = iterator.nextService(); if (currentService instanceof WWWBrowserService) { browserService = currentService; break; } } // Exit if we haven't got a browser service on this // receiver if (browserService == null) return; // This represents the service context in which we'll // run the browser ServiceContext context; // Now we establish which service context we'll use. // First we check to see if we can run the browser // without killing ourself. if (browserService.canRunApplication()) { // We can, so try to create a new service context. ServiceContextFactory contextFactury try { context = contextFactory.createServiceContext(); } catch (InsufficientResourcesException ire) { // If we can't create a new service context, we have // to make a decision whether we use our own service // context to start the browser, which will kill // this application and any others running as part // of this service. In this case, we'll just exit return; } } else { // We can't, so we'll choose to kill ourselves and run // the browser in our own service context. } // start the web browser. context.select(browserService); // We should wait here for the selection to complete, but // since this is example code we won't do this. // In the case that we've started the browser in our own // service context, we probably won't get this far since // selecting a new service in our own service context has // just killed us. // In order to get a reference to the instance of the // browser that we've just started, we need to get the // ServiceContentHandlers for the new service and find // the WWWBrowser WWWBrowser browser; ServiceContentHandler[] handlers; handlers = context.getServiceContentHandlers(); int i; for (i = 0; i < handlers.length; i++) { if (handlers[i] instanceof WWWBrowser) { browser = handlers[i]; break; } } // Now that we've got a reference to the browser, we can // do stuff. // Send the browser to a web site. Let's go visit the // offical MHP site today... browser.goToURL(new URL("http://www.mhp.org"));
This code does four things:
- First, it uses the JavaTV SI API to get the
WWWBrowserService
object representing the web browser. - Once it has the service, it then uses the JavaTV service selection API to create a new service context (if possible) and start the browser.
- It then obtains a reference to the
WWWBrowser
object representing the newly-started instance of the browser. One thing to notice is that theWWWBrowser
object is aServiceContentHandler
for the service we’ve just selected. In the same way, a JMF player would be one of theServiceContentHandlers
for a service containing broadcast video. - Finally, it uses the internet client API itself to tell the browser to go to a specific page; in this case, we go to the MHP home page.
While this looks complicated, it’s not really any more complex than selecting any other service. Most of the work here is involved in selecting the service, rather than in using the browser once we have it. For a typical application where the main focus will be on using the browser rathr than merely opening it, this complexity can easily be hidden.