Streamezzo S.A. - All rights reserved
Copyright 2001-2010
Adaptation Framework – Hands on
This article explains how to create Rich Media Applications that work on any device, relying on the Adaptation Framework provided by Streamezzo.
This article assumes you already have working knowledge of RSP, Components, Java and Workbench Developer.
Overview
We will create a Rich Media Application that must support Keypad and TouchScreen equipped devices, and QVGA and HVGA screen resolutions. And this is just the initial contract as we will also see how easy it is to support additional devices then.
We will focus on one specific scene of the application; this scene is supposed to display a carousel that lets one choose among multiple categories. Depending on the focused category, a list of related items will be displayed at the center of the screen.

This is a [link] to the Workbench Developer project used in this article. Just drag and drop the .swz file in Workbench Developer to import this project.
Setup
Setting up Workbench Developer
Create a new project in Workbench Developer (File > New Service).
For the present sample we will need to create:
- 2 skins (hvga & qvga),
- the layout for the main page,
- and the device properties mapping file.
The adaptation.xml file will look like this:
- <Adaptation>
- <!--
- ===========================
- Skins declaration
- ===========================
- -->
- <Skinning>
- <Theme id="stz">
- <Skin id="qvga" width="240" height="320" xmlsource="skins/240x320.xml"/>
- <Skin id="hvga" width="320" height="480" xmlsource="skins/320x480.xml"/>
- </Theme>
- </Skinning>
- <!--
- ===========================
- Layouts declaration
- ===========================
- -->
- <Layouting>
- <LayoutSet id="std">
- <Layout name="main" xmlsource="layouts/main.xml"/>
- </LayoutSet>
- </Layouting>
- <!--
- ==========================
- Device Properties
- ==========================
- -->
- <DeviceProperties>
- <DeviceMappings>
- </DeviceMappings>
- </DeviceProperties>
- </Adaptation>
The adaptation.xml file is used to configure the Adaptation Framework, it contains informations about:
- skinning (see line 07),
- layouting (see line 18),
- and at last specific devices customizations (line 28)
Here we define 2 skins (lines 09 and 10), one for the qvga screen size family and one for the hvga one. The skin elements are not declared inside the adaptation file - though they could - but instead they are declared in two separates files 240x320.xml and 320x480.xml. It is the same for the layout description, which is declared in the layouts/main.xml file.
We will declare the device properties mapping later on.
Accessing the Adaptation Connector
Let's now create our first RSP file.
Create a new server side RSP file named 'main.rsp'.
- <%@page import="com.streamezzo.odp.adaptation.layout.*" %>
- <%@page import="com.streamezzo.odp.adaptation.skin.*" %>
- <%@page import="com.streamezzo.odp.connector.*" %>
- <header
- size="<%=userAgent.getScreenWidth() + " " + userAgent.getScreenHeight()%>"
- colorBits="8" lengthBits="16" resolution="2" idBits="16" scaleBits="15"
- encodingType="0"/>
- <%
- // Get the connector using the ConnectorManager
- AdaptationConnector adaptationConnector = StzConnectorsManager.getAdaptationConnector(stzRequest.getService(), 0);
- // Begin a transaction for the current request.
- String theme = "theme"; // TODO set the accurate theme value
- String layoutset = "layoutset"; // TODO set the accurate layoutset value
- AdaptationTransaction tx = null;
- try {
- tx = (AdaptationTransaction) adaptationConnector.beginTransaction(stzRequest, theme, layoutset);
- // The skin, layout and device properties can be accessed from the transaction
- %>
- <AUnit time="0">
- <Background color="1.0 1.0 1.0"/>
- </AUnit>
- <%
- } finally {
- if (tx != null) {
- tx.endTransaction();
- }
- }
- %>
- </Streamezzo>
Line 07: Notice that the scene size in the header is set using the current device screen dimensions; we don't want to use auto adaptation.
Line 12: That's how you can have access to the AdaptationConnector; the StzConnectorManager manages and caches the connectors used by your application.
Line 19: Like for all connectors, one will have to start a transaction; the transaction needs the skin theme id and the layout set id to be instantiated. These are the ids defined in the adaptation.xml file.
In order to locate the adaptation.xml file, by default the framework searches for a file named 'adaptation.xml' in the project resources. To specify a different path the most convenient way it to specify a service parameter like 'adaptation.content.source' with the path to your config file.
The 'adaptation.content.source' parameter is set to 'Adaptation/adaptation.xml' (which will be accessible by the RSP, as long as 'Adaptation' folder is set as a client / server resource).

Matching strategy
In the adaptation file, a skin is defined for a given screen resolution. The adaptation connector, using the skin theme and the current User-Agent, is capable to choose the 'best matching skin'. Indeed the framework looks for a skin that exactly matches the current screen resolution, otherwise it picks the closest skin (based on the mean of the width and height delta).
There is no such mechanism for the layout, one must explicitly specify the layout you want to use. This is done when initializing the adaptation transaction:
- AdaptationTransaction tx = (AdaptationTransaction) adaptationConnector.beginTransaction(stzRequest,"stz","std"/* layout set id */);
That doesn't mean that the layout being set must be hardcoded in the RSP: the layout id can be retrieved from the device properties or a service parameter, for example.
Defining the layout
The Box Layout
Layouting is about creating boxes. A box is used to arrange containers either horizontaly or vertically. We want to create this layout:

We can do this by creating one vertical box and two horizontal boxes:

Here is the main.xml file :
- <Container id="Main" style="vertical">
- <!--
- The header container takes 100% of the horizontal space.
- -->
- <Container id="Header" width="100%" height="40"/>
- <Container id="Content" width="FILL" height="FILL"/>
- <!-- An anonymous horizontal container -->
- <Container style="horizontal" width="FILL" height="70">
- <!-- Left Margin -->
- <Container id="LeftArrow" width="10"/>
- <!-- The carousel container takes the remaining space -->
- <Container id="Carousel"/>
- <!-- Right Margin -->
- <Container id="RightArrow" width="10"/>
- </Container>
- <Container id="Footer" style="horizontal" width="100%" height="35">
- <!-- Left Soft Key -->
- <Container id="LSK" width="50"/>
- <Container width="FILL" height="FILL"/>
- <!-- Right Soft Key -->
- <Container id="RSK" width="50"/>
- </Container>
- </Container>
Workbench Developers comes with some facilities to visualize the layouts for a given project:
- Layout Preview: enables visualizing the currently select layout while editing it in real-time

- Layouts Sheet: enables visualizing all layouts of a given layout set

Debugging the layout
There is a small utility (LayoutDebugUtils) that allows debugging the current layout. The following snippet shows how to use it.
- <AUnit time="0">
- <Insert>
- <Transform>
- <Transform DEF="Global:DebugLayout" active="false">
- <%
- LayoutDebugUtils.debugLayout(authoring, stzRequest, currentTransform, layout, userAgent.getScreenWidth(), userAgent.getScreenHeight());
- %>
- </Transform>
- <Action keyCodes="0">
- <ActionKey animObject="ToggleDebugLayout"/>
- </Action>
- <Conditional DEF="ToggleDebugLayout">
- <Script>
- boolean isActive = script.util.DomApi.getNodeActive("DebugLayout");
- script.util.DomApi.setNodeActive("DebugLayout",!isActive);
- </Script>
- </Conditional>
- </Transform>
- </Insert>
- </AUnit>
A simple click on '0' will show the layout boxes on screen during emulation:

Using the layout in your RSP
The layout simply computes the position and size of containers that one simply has to apply to a scene Transform.
First, you must create all the needed Transform nodes as siblings of the same root Transform, and then get the appropriate container and apply its translation to the corresponding Transform.
- <AUnit>
- <Insert>
- <Transform>
- <Transform DEF="Header" translation="<%= layout.getChildByIdRecurs("Header").getTranslation()%>"/>
- <Transform DEF="Content" translation="<%= layout.getChildByIdRecurs("Content").getTranslation()%>"/>
- <Transform DEF="LeftArrow" translation="<%= layout.getChildByIdRecurs("LeftArrow").getTranslation() %>"/>
- <Transform DEF="CarouselTr" translation="<%= layout.getChildByIdRecurs("Carousel").getTranslation() %>"/>
- <Transform DEF="RightArrow" translation="<%= layout.getChildByIdRecurs("RightArrow").getTranslation() %>"/>
- <Transform DEF="Footer" translation="<%= layout.getChildByIdRecurs("RightArrow").getTranslation() %>"/>
- <Transform DEF="LSK" translation="<%= layout.getChildByIdRecurs("LSK").getTranslation() %>"/>
- <Transform DEF="RSK" translation="<%= layout.getChildByIdRecurs("RSK").getTranslation() %>"/>
- </Transform>
- </Insert>
- </AUnit>
Pixel, percentage or skin reference
When setting the width or height of a container in the layout file, you can choose between a size in pixels (absolute), in relative pixels (relative) - the number of pixels being then computed so that the area always has the same metric size, which is especially convenient to ensure finger interactions will be properly handled - a percentage of the parent (relative), or the size of an image defined in the skin (reference).
In our sample the height of the header is set in pixels, which means that for all devices the header will have the exact same size in pixels; this may be inappropriate, that's why you have the ability to set this size in percentage.
If set in percentage the size of the header will change when the screen size changes and proportionally the header will always occupy the same surface. The problem is that on a small screen (QCIF), it is hard to put all the content on screen and as a consequence typically the header size (and the percentage of space occupation) is reduced to gain some useable space.
That is where the last alternative, the 'skin reference', can be relevant. Instead of specifying the size of a container, you can just say that this container must use the size of a given skin image (by using the '@' token in width/height). Thus for small screens if you specify a small header image in your skin, the header container size will stick to the image size.
- <Container id="Header" width="100%" height="@header"/>
Defining the skin
The Skin File
Let's now define the skin.
A skin is a collection of elements like images, colors, shapes and custom properties. Create a file named '240x320.xml' in the Adaptation/skins folder with the following contents:
- <SkinElements>
- <Image id="header" url="qvga/header.png" width="240" height="26"/>
- <Image id="footer" url="qvga/footer.png" width="240" height="34"/>
- <Image id="background" url="qvga/background.png" width="240" height="320"/>
- <Image id="backOption" url="qvga/back.png" width="57" height="19"/>
- <Image id="rightArrow" url="qvga/rightArrow.png" width="10" height="14"/>
- <Image id="leftArrow" url="qvga/leftArrow.png" width="10" height="14"/>
- <!--
- Color Nodes are used to store colors as html color code.
- -->
- <Color id="TitleColor" color="#ffffff"/>
- <!--
- Property Nodes are used to store properties relative to a given phone
- family, the header resolution is typically the kind of property that
- change when the screen size change.
- -->
- <Property id="HeaderResolution">3</Property>
- </SkinElements>
Workbench Developers comes with some facilities to visualize the skins for a given project: Skins Sheet enables visualizing and modifying (add/delete/edit) all skin assets of a given theme and skin.

URL resolution
The path specified in the url attributes of the skin images are relative to the location of the skin file. That means that if your skin file is:
- a remote resource like http://www.backend.com/Adaptation/skins/240x320.xml, then the framework will try to load the header image from this path: http://www.backend.com/Adaptation/skins/qvga/header.png
- a server resource like Adaptation/skins/240x320.xml, then the framework will try to load the header image from this path: Adaptation/skins/240x320.xml/qvga/header.png
In our sample one just has to ensure that the 'Adaptation' folder is defined as a resource folder: go to the Service Browser view.
Notice that it is also possible to put an absolute URL in the image url attribute, like /header.png, in this case the URL is considered as relative to the adaptation.xml file (not to the current skin.xml file).
Using the skin in your RSP
Using the skin in the RSP code is very simple, simply ask for a skin item referring to its unique id. In the following sample you can see how to insert an image from the skin, and how to get a color from the skin.
- <image source="<%= skin.getImage("background").getSource()%>" streamID="background"/>
- <image source="<%= skin.getImage("header").getSource()%>" streamID="header"/>
- <image source="<%= skin.getImage("footer").getSource()%>" streamID="footer"/>
- <image source="<%= skin.getImage("rightArrow").getSource()%>" streamID="rightArrow"/>
- <image source="<%= skin.getImage("leftArrow").getSource()%>" streamID="leftArrow"/>
- [ code ... ]
- <Transform DEF="Header" translation="<%= layout.getChildByIdRecurs("Header").getTranslation()%>">
- <Bitmap streamID="header"/>
- <Text color="<%= skin.getColor("TitleColor").getHtmlCode() %>" string="Nature Store" horizAlign="CENTERED" vertAlign="MIDDLE" size="LARGE"/>
- </Transform>
Here we make a really basic usage of the skin, we do not use the width/height/anchors attributes of the skin images, for example. We willll see how to take advantage of those extra features in the next section.
Interacting with the Layout
From now on we have only defined one skin, let's see how the application is rendered for a screen resolution not specifically handled. Let's just modify our layout and change the height of the header to @header, and the height of the footer to @footer. This is how the application is rendered with QCIF, QVGA and HVGA screen resolutions:

As one can see there is no magic, the application looks bad in the HVGA resolution. The layout and skin do not make any decision about adaptation, one has to specify the relevant decision in the code based on the information retrieved from the framework.
For example in our case we can decide to resize the background image on the server, and to stretch horizontally the header and the footer images on the client:
- <%
- /* Resize the background image on server */
- InputStream is = stzRequest.resizeImage(skin.getImage("background").getSource(),userAgent.getScreenWidth(),userAgent.getScreenHeight(),false/*server cache*/,0/*ttl*/);
- authoring.addImage("background",is,0/*time*/);
- %>
- [...]
- <!-- Resize the footer and header on the client -->
- <Replace target="headerBitmap" field="Bitmap.displaybox" vec2fvalue="<%= layout.getChildByIdRecurs("Header").getSize() %>"/>
- <Replace target="headerBitmap" field="Bitmap.boxfit" enumvalue="STRETCH_WIDTH"/>
- <Replace target="footerBitmap" field="Bitmap.displaybox" vec2fvalue="<%= layout.getChildByIdRecurs("Footer").getSize() %>"/>
- <Replace target="footerBitmap" field="Bitmap.boxfit" enumvalue="STRETCH_WIDTH"/>
The service will look like this now:

Of course this example is exagerated, but one would better design the application so that it can adapt to small size change around a known screen size, e.g. the 240x310, 240x300 around QVGA.
- <%
- SkinImage rightArrow = skin.getImage("rightArrow");
- %>
- [...]
- <image source="<%= rightArrow.getSource()%>" streamID="header"/>
- [...]
- <Transform>
- <%
- currentTransform.setTranslationMatrix(rightArrow.getAnchorPointX(),rightArrow.getAnchorPointY());
- %>
- <Bitmap streamID="rightArrow"/>
- </Transform>
Integrating a Component in a container
UI and Navigation components can be parameterized with a display rectangle, the number of visible items, margins and spacing between items. With the information retrieved from the layout and skin you can define these parameters.
For example, given the size of an item (deduced from the size of the item background skin image), and the size of a container (retrieved from the layout), one can compute how much items can be displayed, then depending on the context one can decide to add/remove an item and tune the spacing, or to size up the items if they are not large enough. Again the framework does not make the decision by itself but gives all the information needed to decide how the content should be properly adapted to suit your needs.
In the following sample we just compute how many items can be displayed in the available space (line 12->14).
- <!-- ... -->
- <%
- Container contentContainer = layout.getChildByIdRecurs("Content");
- %>
- <Transform DEF="Content" translation="<%= ContentContainer.getTranslation()%>">
- <Transform DEF="Global:ListAnchor"/>
- </Transform>
- <!-- ... -->
- <%
- // Adapt the component to the available space
- SkinImage itemBg = skin.getImage("listItemBg");
- int availableHeight = (int) contentContainer.getHeight();
- int nbItems = availableHeight / itemBg.getHeight();
- int spacing = (availableHeight - nbItems * itemBg.getHeight()) / (nbItems - 1);
- //Limit the spacing to 1 pixel
- spacing = Math.min(1,spacing);
- RequestParameterContext ctx = new RequestParameterContext();
- ctx.addParameter(MediaListViewParameters.INSERT_TARGET, "Global:ListAnchor");
- ctx.addParameter(MediaListViewParameters.DEF_PREFIX, "IPLS");
- ctx.addParameter(MediaListViewParameters.NB_VISIBLE_ITEMS, nbItems);
- ctx.addParameter(MediaListViewParameters.ITEM_SPACING, spacing);
- ctx.addParameter(MediaListViewParameters.ITEM_SIZE, itemBg.getWidth() + " " + itemBg.getHeight());
- ctx.addParameter(MediaListViewParameters.SCROLL_PREVIOUS_COMMAND, "Global:ScrollPrevCommand");
- ctx.addParameter(MediaListViewParameters.SCROLL_NEXT_COMMAND, "Global:ScrollNextCommand");
- ctx.addParameter(MediaListViewParameters.NAVIGATION_MODE, MediaListConstants.MIXED_NAVIGATION);
- ctx.setup(stzRequest);
- %>
- <include rspfile="MediaListView.rsp"/>
- <%
- ctx.clean(stzRequest);
- %>
Let's see how the application reacts for a standard 240x320 screen and a 240x310 (e.g. qvga with a native status bar).


Device Properties
Now we have an application that is capable to adapt to different screen size families, but this is not enough for the real world.
One will be able to deal with the device characteristics and features supports thanks to the device database granted by Streamezzo and available in Workbench Developer, relying on such code:
- Device device = tx.getDevice();
- String brand = device.getBrand();
- String model = device.getModel();
- if (device.hasKeypad()) {
- ...
- }
- if (device.hasTouchscreen()) {
- ...
- }
For any other parameter that can affect the application, one can rely on the Device Properties mechanism granted in the Adaptation Framework. For example the fonts are never rendered the same on different devices, so the application must adapt the fonts depending on the targeted device. Device Properties were designed for this.
The Mapping File
The framework allows defining a property file for one (or more) device(s); in this property file one can define a set of properties.
In the adaptation.xml file (or in a dedicated file, thanks to xmlsource attribute) one can define a set of rules to map a specific User-Agent name to a property file.
- <DeviceMappings>
- <!-- W850i, W800i, W810i ... -->
- <Mapping>
- <And>
- <Contains casesensitive="true">SonyEricsson</Contains>
- <Contains>W8</Contains>
- </And>
- <PropertyFile>se-w8-series.properties</PropertyFile>
- </Mapping>
- <!-- Nokia generic rule -->
- <Mapping>
- <StartsWith>0x2000</StartsWith>
- <PropertyFile>nokia.properties</PropertyFile>
- </Mapping>
- </DeviceMappings>
One can define:
- StartsWith rules: checks if the current User-Agent name starts with the specified string pattern
- Contains rules: checks if the current User-Agent name contains the specified string pattern
- Equals rules: checks if the current User-Agent name is equal to the specified string pattern
- ref attribute can be specified to identify which device attribute to be taken as an input for the matching rule, i.e. either brand (device brand name) or model (device model name) or uaname (device name as found in the User-Agent string) or osname (device os name as found in the User-Agent) string
- Rules can be combined thanks to the And, Or operators.
- One can define a default property file using the DefaultPropertyFile tag.
The property file looks like this:
- title.font.size = LARGE
- title.font.style = PLAIN
- title.font.face = PROPORTIONAL
Using Device Properties in RSP
The deviceProperties can be accessed thanks to the adaptationTransaction.
When retrieving a property from the DeviceProperties object one must always specify a default value, so not hesitate to use properties, even if one does not have to set the property in every file if the default value is convenient.
Use properties everywhere you can. For example if one has to define an animation, put the animation type, and duration in a property file, and put a boolean to enable/disable the animation for low-end devices.
- <%
- [...]
- DeviceProperties devProperties = tx.getProperties();
- %>
- <Text string="Nature Store" horizAlign="CENTERED" vertAlign="MIDDLE"
- size="<%= devProperties.getProperty("title.font.size","LARGE")%>"
- style="<%= devProperties.getProperty("title.font.style","PLAIN")%>"
- face="<%= devProperties.getProperty("title.font.face","PROPORTIONAL")%>"
- color="<%= skin.getColor("TitleColor").getHtmlCode() %>">
- </Text>
Summary
The Adaptation Framework provides all the needed information and tools, and if those good practices are applied it is not such an effort to support a new device.
More on Adaptation Framework...
Adaptation Framework – Guideline: the "one size fits all" strategy
| Attachment | Size |
|---|---|
| AdaptationTutorial.swz | 867.02 KB |