|
|
- Xamarin
- Android
- Tutorials
- Introduction to Ice Cream Sandwich
Introduction to Ice Cream Sandwich
With Mono for Android 4
Brief
This article describes several of the new features available to application developers with the Android 4 API - Ice Cream Sandwich. It covers several new user interface technologies and then examines a variety of new capabilities that Android 4 offers for sharing data between applications and between devices.
Sample Code:
ICS samples
Related Articles:
Tab Layout Tutorial
Related Google Documentation:
Introducing Ice Cream Sandwich
Android 4.0 Platform
Overview
Android OS version 4.0 (API Level 14) represents a major reworking of the Android Operating System and includes a number of important changes and upgrades, including:
- Updated User Interface – Several new UI features give developers more power and flexibility when they create application user interfaces. These new features include:
GridLayout, PopupMenu, Switch widget, and TextureView. - Better Hardware Acceleration – 2D rendering now takes place on the GPU for all Android controls. Additionally, hardware acceleration is on, by default, in all applications developed for Android 4.0.
- New Data APIs – There’s new access to data that was not previously officially accessible, such as calendar data and the user profile of the device owner.
- App Data Sharing – Sharing data between applications and devices is now easier than ever via technologies such as the
ShareActionProvider, which makes it easy to create a sharing action from an Action Bar, and Android Beam for Near Field Communications (NFC), which makes it a snap to share data across devices in close proximity to each other.
In this article, we’re going to explore these features and other changes that have been made to the Android 4.0 API, and we’ll explain how to use each feature with Mono for Android.
User Interface Features
A variety of new user interface technologies are available with Android 4, including:
- GridLayout – Supports 2D grid layout of controls.
- Switch widget – Allows toggling between ON or OFF.
- TextureView – Enables video and OpenGL content within a view.
- Navigation Bar – Contains virtual buttons for back, home, and multi-tasking.
Additionally, other UI elements have been enhanced, such as the PopupMenu, which is now easier to work with, and tabs, which have a more polished appearance. Let’s examine each of these.
GridLayout
The GridLayout is a new ViewGroup subclass that supports laying out views in a 2D grid, similar to an HTML table, as shown below:

GridLayout works with a flat-view hierarchy, where child views set their locations in the grid by specifying the rows and columns they should be in. This way, the GridLayout is able to position views in the grid without requiring that any intermediate views provide a table structure, such as seen in the table rows used in the TableLayout. By maintaining a flat hierarchy, GridLayout is able to more swiftly layout its child views. Let’s take a look at an example to illustrate what this concept actually means in code.
Creating a Grid Layout
The following XML adds several TextView controls to a GridLayout.
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="2"
android:columnCount="2">
<TextView
android:text="Cell 0"
android:textSize="14dip" />
<TextView
android:text="Cell 1"
android:textSize="14dip" />
<TextView
android:text="Cell 2"
android:textSize="14dip" />
<TextView
android:text="Cell 3"
android:textSize="14dip" />
</GridLayout>
The layout will adjust the row and column sizes so that the cells can fit their content, as illustrated by the following diagram:

This results in the following user interface when run in an application:

Specifying Orientation
Notice in the XML above, each TextView does not specify a row or column. When these are not specified, the GridLayout assigns each child view in order, based upon the orientation. For example, let’s change the GridLayout’s orientation from the default, which is horizontal, to vertical like this:
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="2"
android:columnCount="2"
android:orientation="vertical">
Now, the GridLayout will position the cells from top to bottom in each column, instead of left to right, as shown below:

This results in the following user interface at runtime:

Specifying Explicit Position
If we want to explicitly control the positions of the child views in the GridLayout, we can set their layout_row and layout_column attributes. For example, the following XML will result in the layout shown in the first screenshot (shown above), regardless of the orientation.
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="2"
android:columnCount="2">
<TextView
android:text="Cell 0"
android:textSize="14dip"
android:layout_row="0"
android:layout_column="0" />
<TextView
android:text="Cell 1"
android:textSize="14dip"
android:layout_row="0"
android:layout_column="1" />
<TextView
android:text="Cell 2"
android:textSize="14dip"
android:layout_row="1"
android:layout_column="0" />
<TextView
android:text="Cell 3"
android:textSize="14dip"
android:layout_row="1"
android:layout_column="1" />
</GridLayout>
Specifying spacing
We have a couple of options that will provide spacing between the child views of the GridLayout. We can use the layout_margin attribute to set the margin on each child view directly, as shown below
<TextView
android:text="Cell 0"
android:textSize="14dip"
android:layout_row="0"
android:layout_column="0"
android:layout_margin="10dp" />
Additionally, in Android 4, a new general-purpose spacing view called Space is now available. To use it, simply add it as a child view. For example, the XML below adds an additional row to the GridLayout by setting its rowcount to 3, and adds a Space view that provides spacing between the TextViews.
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="3"
android:columnCount="2"
android:orientation="vertical">
<TextView
android:text="Cell 0"
android:textSize="14dip"
android:layout_row="0"
android:layout_column="0" />
<TextView
android:text="Cell 1"
android:textSize="14dip"
android:layout_row="0"
android:layout_column="1" />
<Space
android:layout_row="1"
android:layout_column="0"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:text="Cell 2"
android:textSize="14dip"
android:layout_row="2"
android:layout_column="0" />
<TextView
android:text="Cell 3"
android:textSize="14dip"
android:layout_row="2"
android:layout_column="1" />
This XML creates spacing in the GridLayout as shown below:

The benefit of using the new Space view is that it allows for spacing and doesn’t require us to set attributes on every child view.
Spanning Columns and Rows
The GridLayout also supports cells that span multiple columns and rows. For example, say we add another row containing a button to the GridLayout as shown below:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="4"
android:columnCount="2"
android:orientation="vertical">
<TextView
android:text="Cell 0"
android:textSize="14dip"
android:layout_row="0"
android:layout_column="0" />
<TextView
android:text="Cell 1"
android:textSize="14dip"
android:layout_row="0"
android:layout_column="1" />
<Space
android:layout_row="1"
android:layout_column="0"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:text="Cell 2"
android:textSize="14dip"
android:layout_row="2"
android:layout_column="0" />
<TextView
android:text="Cell 3"
android:textSize="14dip"
android:layout_row="2"
android:layout_column="1" />
<Button
android:id="@+id/myButton"
android:text="@string/hello"
android:layout_row="3"
android:layout_column="0" />
</GridLayout>
This will result in the first column of the GridLayout being stretched to accommodate the size of the button, as we see here:

In order to keep the first column from stretching, we can set the button to span two columns by setting its columnspan like this:
<Button
android:id="@+id/myButton"
android:text="@string/hello"
android:layout_row="3"
android:layout_column="0"
android:layout_columnSpan="2" />
Doing this results in a layout for the TextViews that is similar to the layout we had earlier, with the button added to the bottom of the GridLayout as shown below:

Popup Menus
The PopupMenu class was introduced in Android 3 to add support for displaying popup menus that are attached to a particular view. The following illustration shows a popup menu presented from a button, with the second item highlighted just as it is selected:

Android 4 adds a couple of new features to PopupMenu that make it a bit easier to work with, namely:
- Inflate - The Inflate method is now available directly on the PopupMenu class.
- DismissEvent - The PopupMenu class now has a DismissEvent.
Let’s take a look at these improvements. In this example, we have a single Activity that contains a button. When the user clicks the button, a popup menu is displayed as shown below:

Creating a Popup Menu
When we create an instance of the PopupMenu, we need to pass its constructor a reference to the Context, as well as the view to which the menu is attached. In this case, we create the PopupMenu in the click event handler for our button, which is named showPopupMenu. This button is also the view to which we’ll attach the PopupMenu, as shown in the following code:
showPopupMenu.Click += (s, arg) => {
PopupMenu menu = new PopupMenu (this, showPopupMenu);
}
In Android 3, the code to inflate the menu from an XML resource required that you first get a reference to a MenuInflator, and then call its Inflate method with the resource ID of the XML that contained the menu and the menu instance to inflate into. Such an approach still works in Android 4 as the code below shows:
showPopupMenu.Click += (s, arg) => {
PopupMenu menu = new PopupMenu (this, showPopupMenu);
menu.MenuInflater.Inflate (Resource.Menu.popup_menu, menu.Menu);
};
As of Android 4 however, you can now call Inflate directly on the instance of the PopupMenu. This makes the code more concise as shown here:
showPopupMenu.Click += (s, arg) => {
PopupMenu menu = new PopupMenu (this, showPopupMenu);
menu.Inflate (Resource.Menu.popup_menu);
menu.Show ();
};
In the code above, after inflating the menu we simply call menu.Show to display it on the screen.
Handling Menu Events
When the user selects a menu item, the MenuItemClick event will be raised and the menu will be dismissed. Tapping anywhere outside the menu will simply dismiss it. In either case, as of Android 4, when the menu is dismissed, its DismissEvent will be raised. The following code adds event handlers for both the MenuItemClick and DismissEvent events:
showPopupMenu.Click += (s, arg) => {
PopupMenu menu = new PopupMenu (this, showPopupMenu);
menu.Inflate (Resource.Menu.popup_menu);
menu.MenuItemClick += (s1, arg1) => {
Console.WriteLine ("{0} selected", arg1.Item.TitleFormatted);
};
menu.DismissEvent += (s2, arg2) => {
Console.WriteLine ("menu dismissed");
};
menu.Show ();
};
TextureView
The TextureView class is a view that uses hardware-accelerated 2D rendering to enable a video or OpenGL content stream to be displayed. For example, the following screenshot shows the TextureView displaying a live feed from the device’s camera:

Unlike the SurfaceView class, which can also be used to display OpenGL or video content, the TextureView is not rendered into a separate window. Therefore, TextureView is able to support view transformations like any other view. For example, rotating a TextureView can be accomplished by simply setting its Rotation property, its transparency by setting its Alpha property, and so on.
Therefore, with the TextureView we can now do things like display a live stream from the camera and transform it, as shown in the following code:
public class TextureViewActivity : Activity,
TextureView.ISurfaceTextureListener
{
Camera _camera;
TextureView _textureView;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
_textureView = new TextureView (this);
_textureView.SurfaceTextureListener = this;
SetContentView (_textureView);
}
public void OnSurfaceTextureAvailable (
Android.Graphics.SurfaceTexture surface,
int width, int height)
{
_camera = Camera.Open ();
var previewSize = _camera.GetParameters ().PreviewSize;
_textureView.LayoutParameters =
new FrameLayout.LayoutParams (previewSize.Width,
previewSize.Height, (int)GravityFlags.Center);
try {
_camera.SetPreviewTexture (surface);
_camera.StartPreview ();
} catch (Java.IO.IOException ex) {
Console.WriteLine (ex.Message);
}
// this is the sort of thing TextureView enables
_textureView.Rotation = 45.0f;
_textureView.Alpha = 0.5f;
}
…
}
The above code creates a TextureView instance in the Activity’s OnCreate method and sets the Activity as the TextureView’s SurfaceTextureListener. To be the SurfaceTextureListener, the Activity implements the TextureView.ISurfaceTextureListener interface. The system will call the OnSurfaceTextAvailable method when the SurfaceTexture is ready for use. In this method, we take the SurfaceTexture that is passed in and set it to the camera’s preview texture. Then we are free to perform normal view-based operations, such as setting the Rotation and Alpha, as in the example above. The resulting application, running on a device, is shown below:

To use the TextureView, hardware acceleration must be enabled, which it will be by default as of API Level 14. Also, since this example uses the camera, both the android.permission.CAMERA permission and the android.hardware.camera feature must be set in the AndroidManifest.xml.
Switch Widget
Android 4 now includes a Switch widget (shown below) that allows a user to toggle between two states, such as ON or OFF. The Switch default value is OFF. The widget is shown below in both its ON and OFF states:

Creating a Switch
To create a switch, simply declare a Switch element in XML as follows:
<Switch android:layout_width="wrap_content"
android:layout_height="wrap_content" />
This creates a basic switch as shown below:

Changing Default Values
Both the text that the control displays for the ON and OFF states and the default value are configurable. For example, to make the Switch default to ON and read NO/YES instead of OFF/ON, we can set the checked, textOn, and textOff attributes in the following XML.
<Switch android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:textOn="YES"
android:textOff="NO" />
Providing a Title
The Switch widget also supports including a text label by setting the text attribute as follows:
<Switch android:text="Is Mono for Android great?"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:textOn="YES"
android:textOff="NO" />
This markup produces the following screenshot at runtime:

When a Switch’s value changes, it raises a CheckedChange event. For example, in the following code we capture this event and present a Toast widget with a message based upon the isChecked value of Switch, which is passed to the event handler as part of the CompoundButton.CheckedChangeEventArg argument.
Switch s = FindViewById<Switch> (Resource.Id.monitored_switch);
s.CheckedChange += delegate(object sender, CompoundButton.CheckedChangeEventArgs e) {
var toast = Toast.MakeText (this, "Your answer is " +
(e.IsChecked ? "correct" : "incorrect"), ToastLength.Short);
toast.Show ();
};
Navigation Bar
Android 4 includes a new system user interface feature called a Navigation Bar, which provides navigation controls on devices that don’t include hardware buttons for Home, Back, and Menu. The following screenshot shows the Navigation Bar from a Nexus Prime device:

Several new flags are available that control the visibility of the Navigation Bar and its controls, as well as the visibility of the System Bar that was introduced in Android 3. The flags are defined in the Android.View.View class and are listed below:
SystemUiFlagVisible – Makes the Navigation Bar visible. SystemUiFlagLowProfile – Dims out controls in the Navigation Bar. SystemUiFlagHideNavigation – Hides the Navigation Bar.
These flags can be applied to any view in the view hierarchy by setting the SystemUiVisibility property. If multiple views have this property set, the system combines them with an OR operation and applies them so long as the window in which the flags are set retains focus. When you remove a view, any flags it has set will also be removed.
The following example shows a simple application where clicking any of the buttons changes the SystemUiVisibility:

The code to change the SystemUiVisibility sets the property on a TextView from each button’s click event handler as shown below:
var tv = FindViewById<TextView> (Resource.Id.systemUiFlagTextView);
var lowProfileButton = FindViewById<Button>(Resource.Id.lowProfileButton);
var hideNavButton = FindViewById<Button> (Resource.Id.hideNavigation);
var visibleButton = FindViewById<Button> (Resource.Id.visibleButton);
lowProfileButton.Click += delegate {
tv.SystemUiVisibility =
(StatusBarVisibility)View.SystemUiFlagLowProfile;
};
hideNavButton.Click += delegate {
tv.SystemUiVisibility =
(StatusBarVisibility)View.SystemUiFlagHideNavigation;
};
visibleButton.Click += delegate {
tv.SystemUiVisibility = (StatusBarVisibility)View.SystemUiFlagVisible;
}
Also, a SystemUiVisibility change raises a SystemUiVisibilityChange event. Just like setting the SystemUiVisibility property, a handler for the SystemUiVisibilityChange event can be registered for any view in the hierarchy. For example, the code below uses the TextView instance to register for the event:
tv.SystemUiVisibilityChange +=
delegate(object sender, View.SystemUiVisibilityChangeEventArgs e) {
tv.Text = String.Format ("Visibility = {0}", e.Visibility);
};
Tabs
Tabs in Android 4.0 have changed quite a bit as shown in the screenshot below:

The screenshot shows a tabbed application that was created by using the TabHost, TabSpec, and TabActivity classes running in an Android 4.0 emulator. The only difference between these two examples is that the target framework is set to Android 4.0 for the image on the right and the target framework is set to Android 2.2 on the left. As you can see, this results in quite a different appearance.
TabActivity Deprecated
When using TabActivity, the code to create the tab icons has no effect when run against the Android 4.0 framework. Although functionally it works as it did in versions of Android prior to 2.3, the TabActivity class itself has been deprecated in 4.0. A new way to create a tabbed interface has been introduced that uses the Action Bar, which we’ll discuss next.
Action Bar Tabs
The Action Bar includes support for adding tabbed interfaces in Android 4.0. The following screenshot shows an example of such an interface.

To create tabs in the Action Bar, we first need to set its NavigationMode property to support tabs. In Android 4, an ActionBar property is available on the Activity class, which we can use to set the NavigationMode like this:
this.ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
Once this is done, we can create a tab by calling the NewTab method on the Action Bar. With this tab instance, we can call the SetText and SetIcon methods to set the tab’s label text and icon; these calls are made in order in the code shown below:
var tab = this.ActionBar.NewTab ();
tab.SetText (tabText);
tab.SetIcon (Resource.Drawable.ic_tab_white);
Before we can add the tab however, we need to handle the TabSelected event. In this handler, we can create the content for the tab. Action Bar tabs are designed to work with Fragments, which are classes that represent a portion of the user interface in an Activity. For this example, the Fragment’s view contains a single TextView, which we inflate in our Fragment subclass like this:
class SampleTabFragment: Fragment
{
public override View OnCreateView (LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView (inflater, container, savedInstanceState);
var view = inflater.Inflate (
Resource.Layout.Tab, container, false);
var sampleTextView =
view.FindViewById<TextView> (Resource.Id.sampleTextView);
sampleTextView.Text = "sample fragment text";
return view;
}
}
The event argument passed in the TabSelected event is of type TabEventArgs, which includes a FragmentTransaction property that we can use to add the fragment as shown below:
tab.TabSelected += delegate(object sender, ActionBar.TabEventArgs e) {
e.FragmentTransaction.Add (Resource.Id.fragmentContainer,
new SampleTabFragment ());
};
Finally, we can add the tab to the Action Bar by calling the AddTab method as shown in this code:
this.ActionBar.AddTab (tab);
For the complete example, see the HelloTabsICS project in the sample code for this document.
Sharing Features
Android 4 includes several new technologies that let us share data across devices and across applications. It also provides access to various types of data that were not previously available, such as calendar information and the device owner’s user profile. In this section we’ll examine a variety of features offered by Android 4 that address these areas including:
- Android Beam – Allows data sharing via NFC.
- Calendar API – Provides access to calendar data from the calendar provider.
- ShareActionProvider – Creates a provider that allows developers to specify sharing actions from the Action Bar.
- User Profile – Provides access to profile data of the device owner.
Android Beam
Android Beam is a new Near Field Communication (NFC) technology in Android 4 that allows applications to share information over NFC when in close proximity.

Android Beam works by pushing messages over NFC when two devices are in range. An Activity on one device creates a message and specifies an Activity (or Activities) that can handle pushing it. When the specified Activity is in the foreground and the devices are in range, Android Beam will push the message to the second device. On the receiving device, an Intent is invoked containing the message data.
Android supports two ways of setting messages with Android Beam:
SetNdefPushMessage - Before Android Beam is initiated, an application can call SetNdefPushMessage to specify an NdefMessage to push over NFC, and the Activity that is pushing it. This mechanism is best used when a message doesn’t change while an application is in use. SetNdefPushMessageCallback - When Android Beam is initiated, an application can handle a callback to create an NdefMessage. This mechanism allows for message creation to be delayed until devices are in range. It supports scenarios where the message may vary based upon what’s happening in the application.
In either case, to send data with Android Beam, an application sends an NdefMessage, packaging the data in several NdefRecords. Let’s take a look at the key points that must be addressed before we can trigger Android Beam. First, we’ll work with the callback style of creating an NdefMessage.
Creating a Message
We can register callbacks with an NfcAdapter in the Activity’s OnCreate method. For example, assuming an NfcAdapter named mNfcAdapter is declared as a class variable in the Activity, we can write the following code to create the callback that will construct the message:
mNfcAdapter = NfcAdapter.GetDefaultAdapter (this);
mNfcAdapter.SetNdefPushMessageCallback (this, this);
The Activity, which implements NfcAdapter.ICreateNdefMessageCallback, is passed to the SetNdefPushMessageCallback method above. When Android Beam is initiated, the system will call CreateNdefMessage, from which the Activity can construct an NdefMessage as shown below:
public NdefMessage CreateNdefMessage (NfcEvent evt)
{
DateTime time = DateTime.Now;
var text = ("Beam me up!\n\n" + "Beam Time: " +
time.ToString ("HH:mm:ss"));
NdefMessage msg = new NdefMessage (
new NdefRecord[]{ CreateMimeRecord (
"application/com.example.android.beam",
Encoding.UTF8.GetBytes (text)) });
} };
return msg;
}
public NdefRecord CreateMimeRecord (String mimeType, byte [] payload)
{
byte [] mimeBytes = Encoding.UTF8.GetBytes (mimeType);
NdefRecord mimeRecord = new NdefRecord (
NdefRecord.TnfMimeMedia, mimeBytes, new byte [0], payload);
return mimeRecord;
}
Receiving a Message
On the receiving side, the system invokes an Intent with the ActionNdefDiscovered action, from which we can extract the NdefMessage as follows:
IParcelable [] rawMsgs = intent.GetParcelableArrayExtra (NfcAdapter.ExtraNdefMessages);
NdefMessage msg = (NdefMessage) rawMsgs [0];
For a complete code example that uses Android Beam, shown running in the screenshot below, see the Android Beam demo in the Mono for Android samples.

Calendar API
A new set of calendar APIs supports applications that are designed to read or write data to the calendar provider. These APIs support a wealth of interaction options with calendar data, including the ability to read and write events, attendees, and reminders. By using the calendar provider in your application, data you add through the API will appear in the built-in calendar app that comes with Android 4.
Adding Permissions
When working with the new calendar APIs in your application, the first thing you need to do is add the appropriate permissions to the Android manifest. The permissions you need to add are android.permisson.READ_CALENDAR and android.permission.WRITE_CALENDAR, depending on whether you are reading and/or writing calendar data.
Using the Calendar Contract
Once you set the permissions, you can interact with calendar data by using the CalendarContract class. This class provides a data model that applications can use when they interact with the calendar provider. The CalendarContract allows applications to resolve the Uris to calendar entities, such as calendars and events. It also provides a way to interact with various fields in each entity, such as a calendar’s name and ID, or an event’s start and end date.
Let’s look at an example that uses the Calendar API. In this example, we’ll examine how to enumerate calendars and their events, as well as how to add a new event to a calendar.
Listing Calendars
First, let’s examine how to enumerate the calendars that have been registered in the calendar app. To do this, we can call the ManagedQuery method. At a minimum, we’ll need to specify the content Uri for calendars and the columns we want to return; this column specification is known as a projection. Calling ManagedQuery allows us to query a content provider for data, such as the calendar provider, and returns a Cursor with the results of the query.
The CalendarContract assists us in specifying both the content Uri and the projection. To get the content Uri for querying calendars, we can simply use the CalendarContract.Calendars.ContentUri property like this:
var calendarsUri = CalendarContract.Calendars.ContentUri;
Using the CalendarContract to specify which calendar columns we want is equally simple. We just add fields in the CalendarContract.Calendars.InterfaceConsts class to an array. For example, the following code includes the calendar’s ID, display name, and account name:
string[] calendarsProjection = {
CalendarContract.Calendars.InterfaceConsts.Id,
CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName,
CalendarContract.Calendars.InterfaceConsts.AccountName
};
The Id is important to include if you are using a SimpleCursorAdapter to bind the data to the UI, as we will see shortly.
With the content Uri and projection in place, we call the ManagedQuery method to return a cursor with the calendar data as shown below:
var cursor = ManagedQuery (calendarsUri, calendarsProjection, null, null, null);
The UI for this example contains a ListView, with each item in the list representing a single calendar. The following XML shows the markup that includes the ListView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@android:id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Also, we need to specify the UI for each list item, which we place in a separate XML file as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView android:id="@+id/calDisplayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dip" />
<TextView android:id="@+id/calAccountName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12dip" />
</LinearLayout>
From this point on, it’s just normal Android code to bind the data from the cursor to the UI. We’ll use a SimpleCursorAdapter as follows:
string[] sourceColumns = {
CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName,
CalendarContract.Calendars.InterfaceConsts.AccountName };
int[] targetResources = {
Resource.Id.calDisplayName, Resource.Id.calAccountName };
SimpleCursorAdapter adapter = new SimpleCursorAdapter (this,
Resource.Layout.CalListItem, cursor, sourceColumns, targetResources);
ListAdapter = adapter;
In the above code, the adapter takes the columns specified in the sourceColumns array and writes them to the user interface elements in the targetResources array for each calendar entry in the cursor. The Activity used here is a subclass of ListActivity; it includes the ListAdapter property to which we set the adapter.
Here’s a screenshot showing the end result, with the calendar info displayed in the ListView:

Listing Calendar Events
Next let’s look at how to enumerate the events for a given calendar. Building upon the example above, we’ll present a list of events when the user selects one of the calendars. Therefore, we’ll need to handle the item selection in the previous code:
ListView.ItemClick += (sender, e) => {
int i = (e as ItemEventArgs).Position;
cursor.MoveToPosition(i);
int calId =
cursor.GetInt (cursor.GetColumnIndex (calendarsProjection [0]));
var showEvents = new Intent(this, typeof(EventListActivity));
showEvents.PutExtra("calId", calId);
StartActivity(showEvents);
};
In this code, we’re creating an Intent to open an Activity of type EventListActivity, passing the calendar’s ID in the Intent. We will need the ID in order to know which calendar to query for events. In the EventListActivity’s OnCreate method, we can retrieve the ID from the Intent as shown below:
_calId = Intent.GetIntExtra ("calId", -1);
Now let’s query events for this calendar ID. The process to query for events is similar to the way we queried for a list of calendars earlier, only this time we’ll work with the CalendarContract.Events class. The following code creates a managed query to retrieve events:
var eventsUri = CalendarContract.Events.ContentUri;
string[] eventsProjection = {
CalendarContract.Events.InterfaceConsts.Id,
CalendarContract.Events.InterfaceConsts.Title,
CalendarContract.Events.InterfaceConsts.Dtstart
};
var cursor = ManagedQuery (eventsUri, eventsProjection,
String.Format ("calendar_id={0}", _calId), null, "dtstart ASC");
In this code, we first get the content Uri for events from the CalendarContract.Events.ContentUri property. Then we specify the event columns we want to retrieve in the eventsProjection array. Finally, we build a managed query by calling the ManagedQuery method, which returns a cursor with event data.
To display the event data in the UI, we can use markup and code just like we did before to display the list of calendars. Again, we use SimpleCursorAdapter to bind the data to a ListView as shown in the following code:
string[] sourceColumns = {
CalendarContract.Events.InterfaceConsts.Title,
CalendarContract.Events.InterfaceConsts.Dtstart };
int[] targetResources = {
Resource.Id.eventTitle,
Resource.Id.eventStartDate };
var adapter = new SimpleCursorAdapter (this, Resource.Layout.EventListItem,
cursor, sourceColumns, targetResources);
adapter.ViewBinder = new ViewBinder ();
ListAdapter = adapter;
The main difference between this code and the code that we used earlier to show the calendar list is the use of a ViewBinder, which is set on the line:
adapter.ViewBinder = new ViewBinder ();
The ViewBinder class allows us to further control how we bind values to views. In this case, we use it to convert the event start time from milliseconds to a date string, as shown in the following implementation:
class ViewBinder : Java.Lang.Object, SimpleCursorAdapter.IViewBinder
{
public bool SetViewValue (View view, Android.Database.ICursor cursor,
int columnIndex)
{
if (columnIndex == 2) {
long ms = cursor.GetLong (columnIndex);
DateTime date = new DateTime (1970, 1, 1, 0, 0, 0,
DateTimeKind.Utc).AddMilliseconds (ms).ToLocalTime ();
TextView textView = (TextView)view;
textView.Text = date.ToLongDateString ();
return true;
}
return false;
}
}
This displays a list of events as shown below:

Adding a Calendar Event
We’ve seen how to read calendar data. Now let’s see how to add an event to a calendar. For this to work, be sure to include the android.permission.WRITE_CALENDAR permission we mentioned earlier. To add an event to a calendar, we will:
- Create a
ContentValues instance. - Use keys from the
CalendarContract.Events.InterfaceConsts class to populate the ContentValues instance. - Use a
ContentResolver to insert the event data into the calendar.
The code below illustrates these steps:
ContentValues eventValues = new ContentValues ();
eventValues.Put (CalendarContract.Events.InterfaceConsts.CalendarId,
_calId);
eventValues.Put (CalendarContract.Events.InterfaceConsts.Title,
"Test Event from M4A");
eventValues.Put (CalendarContract.Events.InterfaceConsts.Description,
"This is an event created from Mono for Android");
eventValues.Put (CalendarContract.Events.InterfaceConsts.Dtstart,
GetDateTimeMS (2011, 12, 15, 10, 0));
eventValues.Put (CalendarContract.Events.InterfaceConsts.Dtend,
GetDateTimeMS (2011, 12, 15, 11, 0));
var uri = ContentResolver.Insert (CalendarContract.Events.ContentUri,
eventValues);
If we add a button to the event list UI and run the above code in the button’s click event handler, the event is added to the calendar and updated in our list as shown below:

If we open the calendar app, then we will see that the event is written there as well:

As you can see, Android 4.0 allows powerful and easy access to retrieve and persist calendar data, allowing applications to seamlessly integrate calendar capabilities.
ShareActionProvider
The ShareActionProvider class enables a sharing action to take place from an Action Bar. It takes care of creating an action view with a list of apps that can handle a sharing Intent and keeps a history of the previously used applications for easy access to them later from the Action Bar. This allows applications to share data via a user experience that’s consistent throughout Android.
Image Sharing Example
For example, below is a screenshot of an Action Bar with a menu item to share an image. When the user taps the menu item on the Action Bar, the ShareActionProvider loads the application to handle an Intent that is associated with the ShareActionProvider. In this example, the messaging application has been previously used, so it is presented on the Action Bar.

When the user clicks on the item in the Action Bar, the messaging app that contains the shared image is launched, as shown below:

Specifying the action Provider Class
To use the ShareActionProvider, set the android:actionProviderClass attribute on a menu item in the XML for the Action Bar’s menu as follows:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/shareMenuItem"
android:showAsAction="always"
android:title="@string/sharePicture"
android:actionProviderClass="android.widget.ShareActionProvider" />
</menu>
Inflating the Menu
To inflate the menu, we override OnCreateOptionsMenu in the Activity subclass. Once we have a reference to the menu, we can get the ShareActionProvider from the ActionProvider property of the menu item and then use the SetShareIntent method to set the ShareActionProvider’s Intent, as shown below:
public override bool OnCreateOptionsMenu (IMenu menu)
{
MenuInflater.Inflate (Resource.Menu.ActionBarMenu, menu);
var shareMenuItem = menu.FindItem (Resource.Id.shareMenuItem);
var shareActionProvider =
(ShareActionProvider)shareMenuItem.ActionProvider;
shareActionProvider.SetShareIntent (CreateIntent ());
}
Creating the Intent
The ShareActionProvider will use the Intent, passed to the SetShareIntent method in the above code, to launch the appropriate Activity. In this case we create an Intent to send an image by using the following code:
Intent CreateIntent ()
{
var sendPictureIntent = new Intent (Intent.ActionSend);
sendPictureIntent.SetType ("image/*");
var uri = Android.Net.Uri.FromFile (GetFileStreamPath ("monkey.png"));
sendPictureIntent.PutExtra (Intent.ExtraStream, uri);
return sendPictureIntent;
}
The image in the code example above is included as an asset with the application and copied to a publicly accessible location when the Activity is created, so it will be accessible to other applications, such as the messaging app. The sample code that accompanies this article contains the full source of this example, illustrating its use.
User Profile
Android has supported enumerating contacts with the ContactsContract provider since API Level 5. For example, to list contacts is as simple as using the ContactContracts.Contacts class, as shown in the following code:
var uri = ContactsContract.Contacts.ContentUri;
string[] projection = {
ContactsContract.Contacts.InterfaceConsts.Id,
ContactsContract.Contacts.InterfaceConsts.DisplayName };
var cursor = ManagedQuery (uri, projection, null, null, null);
if (cursor.MoveToFirst ()) {
do {
Console.WriteLine ("Contact ID: {0}, Contact Name: {1}",
cursor.GetString (cursor.GetColumnIndex (projection [0])),
cursor.GetString (cursor.GetColumnIndex (projection [1])));
} while (cursor.MoveToNext());
}
With Android 4 (API Level 14), a new ContactsContact.Profile class is available through the ContactsContract provider. The ContactsContact.Profile provides access to a personal profile for the owner of a device, which includes contact data such as the device owner’s name and phone number.
Required Permissions
To read and write contact data, applications must request the Read_Contacts and Write_Contacts permissions, respectively. Additionally, to read and edit the user profile, applications must request the Read_Profile and Write_Profile permissions.
Updating Profile Data
Once these permissions have been set, an application can use normal Android techniques to interact with the user profile’s data. For example, to update the profile’s display name we would call ContentResolver.Update with a Uri retrieved through the ContactsContract.Profile.ContentRawContactsUri property, as shown below:
var values = new ContentValues ();
values.Put (ContactsContract.Contacts.InterfaceConsts.DisplayName,
"John Doe");
ContentResolver.Update (ContactsContract.Profile.ContentRawContactsUri,
values, null, null);
Reading Profile Data
Issuing a query to the ContactsContact.Profile.ContentUri reads back the profile data. For example, the following code will read the user profile’s display name:
string[] projection = {
ContactsContract.Contacts.InterfaceConsts.DisplayName };
var cursor = ManagedQuery (uri, projection, null, null, null);
if (cursor.MoveToFirst ()) {
Console.WriteLine(
cursor.GetString (cursor.GetColumnIndex (projection [0])));
}
Navigating to the People App
Finally, to navigate to the user profile in the new People app that comes with Android 4, simply create an Intent with an ActionView action and a ContactsContract.Profile.ContentUri, and pass it to the StartActivity method like this:
var intent = new Intent (Intent.ActionView,
ContactsContract.Profile.ContentUri);
StartActivity (intent);
When running the above code, the People app will load to the user profile, as shown in the following screenshot:

Working with the user profile is now similar to interacting with other data in Android, and offers an additional level of device personalization.
x86 Emulators
ICS does not yet support development with an x86 emulator. x86 emulators are only supported with Android 2.3.3, API level 10. See Configuring the x86 Emulator for more information.
This article covered a variety of the new technologies that are now available with Android 4. We reviewed new user interface features such as the GridLayout, PopupMenu, and Switch widget. We also looked at some of the new support for controlling the system UI, as well as how to work with the TextureView. Then we discussed a variety of new sharing technologies. We covered how Android Beam let’s you share information across devices that use NFC, discussed the new Calendar API, and also showed how to use the built in ShareActionProvider. Finally, we examined how to use the ContactsContract provider to access user profile data.
|