Now that you have a basic app, it’s time to add some more features. To start, you’ll need to handle some more events from the user, create an ongoing notification, and list extra options in a menu. Along the way, you’ll learn the specifics of supporting multiple device configurations; explore event callbacks and multiple event filtering; create notifications, toasts, and dialogs to alert the user; and learn when and how to create menus.
Android is designed to operate on a wide range of hardware devices, but writing a separate interface for every device would be a nightmare. Luckily, the Android OS provides a number of abstractions that support this diverse set of hardware.
Android has many features that are inspired by the web (not surprising given that it was created by Google). Nowhere is this more apparent than in the design philosophy of Android views. In contrast to iOS devices, Android apps don’t know the screen resolution, size, or aspect ratio of the devices they run on, but you can use the View
classes to stretch and shrink layouts to fill the available space, just as you can on the web.
You’ve already seen how to create Android layouts, and you’ve learned how to create a layout that stretches to fill the available space. This creates a flexible layout, but it’s usually not enough to make your app work on every device configuration. Often, you need to do more than just adjust the size of elements—you actually need to create a different layout to provide a useful interface. To make this easier, Android uses a series of layout qualifiers that define different device configurations. The layout qualifiers are appended to the resource folder names. Using these folders, you can create a layout for a specific set of device configurations; Android will automatically select the appropriate layout file for the user’s device.
For example, when a phone is held in portrait, the basic XML layout file for that device will be loaded and displayed. When a phone is rotated to landscape, a landscape-specific layout can be loaded, but only if it is available; if no landscape version is available, the standard portrait version will be loaded. Layout qualifiers exist for screen density, orientation, screen size, mobile country codes, region, platform version, primary navigation mode, and much more. Table 3.1 summarizes the important qualifiers.
Tip
The number of layout options may seem overwhelming at first. Don’t worry. In general, you will only need to handle the orientation, screen size, and screen density qualifiers.
In the Hello World app you created in Chapter 1, the call to setContentView
will load the XML layout file and display it to the user. To add a new landscape version of the layout, follow these steps:
1. In the res/
directory, create a new folder named layout-land/ and put a copy of the main.xml
file into it.
2. Open the new file and change the string to "Hello Landscape"
.
Now when you start the app, you will see the standard layout in portrait but the new layout in landscape.
Android selects the appropriate resources at runtime, based on the device configuration. Since multiple resource folders could match, Android establishes precedence for qualifiers to resolve conflicts. This precedence determines which resources are selected. Consult the Android documentation for the full list of qualifiers and their precedence.
When selecting resources, these are the general steps that Android takes to determine the proper folder:
1. Eliminate all folders that contradict the device configuration.
2. Select the next qualifier in precedence.
3. If any folders match this qualifier, eliminate all folders that do not match. If no folders match this qualifier, return to step 2.
4. Continue until only one resource folder remains.
The exception to these rules is screen pixel density. Android will scale any resources to fit the screen; therefore, all pixel densities are valid. Android will select the closest density that is available, preferring to scale down larger densities.
Tip
When creating resource folders, you must list qualifiers in their precedence order. Your app will not compile otherwise.
An example will better illustrate how Android selects resources. Consider a device with the following configuration:
• Screen orientation: landscape
• Screen pixel density: hdpi
• Screen size: large
• Touchscreen type: finger
You have the following resource folders in your app:
/res/layout/
/res/layout-notouch/
/res/layout-land/
/res/layout-land-ldpi/
/res/layout-land-finger/
/res/layout-hdpi/
Android will run through its steps to select the best resource:
1. Eliminate the /res/layout-notouch/
folder because it conflicts with the touchscreen qualifier.
2. There are no folders with screen-size qualifiers, so skip to the next qualifier.
3. Orientation is the next highest precedence, so eliminate all folders that do not have the land
qualifer. This leaves three folders: /res/layout-land/
, /res/layout-land-ldpi/
, and /res/layout-land-finger/
.
4. The next qualifier is pixel density. There is no exact match, so continue to the next qualifier. If no other qualifiers match, select the nearest pixel density.
5. The last qualifier is touchscreen type. In this case, finger
means that the device has a capacitive touchscreen, so eliminate all folders that do not contain the finger
qualifier.
6. The only remaining folder is /res/layout-land-finger/
. Select the layouts in this folder.
Android performs this procedure for every resource your layouts require. Often, resources will be mixed from multiple locations. For example, the layout may be taken from the /res/layout-ldpi/
folder, but the drawable resources could be taken from the /res/layout-hdpi/
folder. Remember that Android selects each resource independently and will pick the best match. If you start getting strange problems with your layouts, check the precedence on your resource folders. Android may be loading different resources than you expect!
Note
Android will select only screen size resource qualifiers that are smaller than or equal to the device configuration (excluding the pixel density qualifier). If you have only xlarge
resources and the device has a small screen, then your app will crash when it runs.
Using resource qualifiers and Android’s layout containers will let you create layouts that stretch and compress to fill available space. But sometimes you need to specify the exact dimensions of a view. If you’ve done GUI programming, you’re probably used to specifying exact sizes in pixels. Android supports this, but you should avoid using absolute pixel or dimension values when you create your app. The pixel density of devices varies greatly, and the same resource will appear as a different physical size on each device. Figure 3.1 shows an example of a button that has its height and width values specified in pixels. At each screen density, the relative size of the view is different.
To handle this, Android has several ways of declaring dimensions in a density-independent manner, summarized in Table 3.2.
Figure 3.2 shows the previous example, but with the height and width of the button specified in dp. The buttons appear much closer to the same size on the screen.
In general, you should use dp for all units of measure (or sp for text sizes). Using these units will make your layouts appear consistent across device sizes and densities. This will ensure you get maximum device compatibility and will help you avoid layout headaches later.
Using the resource folders and density-independent pixels will get you most of the way to a flexible layout, and Android will scale your images appropriately in most situations. But often you will need to create image resources with rounded corners. These images won’t stretch properly and will appear distorted. To handle that case, Android supports a feature known as a 9-patch graphic. This is simply a PNG file with a 1-pixel border around it. The Draw 9-Patch tool (see Chapter 1) provides an easy way to create 9-patch graphics (Figure 3.3). These images can be stretched in the areas indicated by the shaded region (marked with black pixels in the border of the image). By using the Draw 9-Patch tool, you ensure that your image will stretch but will maintain the proper rounding on corners. All of the stock Android button resources use 9-patch graphics.
Tip
Remember that if you haven’t yet adapted your app to a particular screen configuration, you can specifically declare which configurations your app supports in the Android manifest. This will prevent your app from being installed on unsupported hardware.
Android is designed to run on portable devices that are carried everywhere and used in sporadic bursts. To ensure that users get the full benefit of these devices, Android supplies a robust set of notification techniques to ensure that users are immediately aware of any events. This section covers the notification options in order of increasing interruption to the user.
A toast is the most basic and least intrusive notification. This is a simple message that is flashed on the screen for a short time (typically 5 to 10 seconds). It’s intended to give the user immediate feedback on some event that is relevant to their current situation. For example, if a social networking app is attempting to update a user’s status, it could use a toast to inform the user when the status is successfully updated. Figure 3.4 shows an example toast.
To create a toast, use the static makeText
method on the Toast
class to create a Toast
object. Give it a context, the text you want to display, and a duration. The duration can be either Toast.LENGTH_SHORT
or Toast.LENGTH_LONG
. Calling show()
on the Toast
object will display it to the user. The following code snippet creates a simple toast:
Context context = getApplicationContext();
CharSequence text = "Hello toast!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
Toasts include options for setting their position on the screen. Use the setGravity
method to change the default display location. And as with most views in Android, you can override the default layout of a toast and create a custom toast.
Use toasts when you want to give quick feedback to the user but don’t expect them to take any action.
Tip
It’s possible for toasts to be shown when your application is not in the foreground. For example, a toast generated by a background service could be displayed during any application. Carefully think through the situations that might generate a toast in your application, and choose appropriate text.
The primary notification method in Android is the notification tray. This tray can be pulled down from the top of the screen and contains all ongoing and immediately important notifications. A notification in the tray consists of an icon, title text, and message text. Tapping the notification will take the user to the app that generated the notification (more on this in a bit).
If you need to create a notification, it should generally go in the notification tray. It’s the easiest way to notify the user, it is unobtrusive, and users will expect it. Figure 3.5 shows some example notifications.
Notifications have a few basic parameters you can set:
• An icon to display in the status bar.
• Optional ticker text that will be displayed in the status bar when the notification is first shown.
• The title and message to display in the notification tray. This is also optional.
• A required PendingIntent
to trigger when the user taps the notification.
Tip
Take care not to generate an excessive number of notifications. This will clutter the user’s notification tray, and they will likely uninstall your app. Instead, collapse all notifications into a single summary notification (such as total number of messages received).
A PendingIntent
is simply a holder for an intent and target action. It allows you to pass an Intent
object to another application and invoke that Intent
as if it were invoked by your application. In this way, the PendingIntent
can be used to trigger actions in your app. The PendingIntent
is used by the notification tray application to trigger an event in your app when the user taps your notification.
Here is an example notification:
int icon = R.drawable.icon;
CharSequence ticker = "Hello World!";
long now = System.currentTimeMillis();
Notification notification = new Notification(icon, ticker, now);
A notification is created with an icon, ticker text, and timestamp. This example creates a notification with the resource located at /res/drawable/icon
, the ticker text set to the string "Hello World!"
, and the time set to the current time.
Once you have created a notification, use the NotificationManager
to display that notification to the user:
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Context context = getApplicationContext();
CharSequence message = "Hello World!";
Intent intent = new Intent(this, Example.class);
String title = "Hello World!";
String message = "This is a message.";
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
notification.setLatestEventInfo(context, title, message, pendingIntent);
nm.notify(ID, notification);
Use the setLatestEventInfo
method to update the contents of the notification. Here, the PendingIntent
is set to display the example activity when the notification is tapped. Call notify
to display the notification in the tray. The device status bar will also display the notification briefly before returning to its usual state. You can update a notification that is already displayed by calling setLatestEventInfo
and then notify
again.
Tip
Android 3.0 (Honeycomb) introduced the Notification.Builder
class for creating notifications. This builder replaces the existing constructor for the Notification
class and makes creating notifications easier. The notification ID for each notification should be globally unique in your app. You should list all these IDs in a common file to ensure their uniqueness, or strange behavior may result.
Notifications are great for most events, because they don’t interrupt the user. However, sometimes you need to inform the user of something immediately—perhaps you need to alert the user to some failure in your app or confirm that they want to perform an action. To do this, you use a dialog.
Dialogs are small windows displayed over your application (Figure 3.6). They block all user input into your app and must be dismissed before the user can continue using your app. This makes them the most intrusive of all notification types. Android provides several types of dialogs, with different use cases: an AlertDialog
with simple Accept and Cancel buttons, a ProgressDialog
for displaying long-running progress, and date- and time-picker dialogs for accepting user input.
To create a dialog, extend the DialogFragment
class and implement either the onCreateView
or onCreateDialog
method. Use onCreateView
to set the view hierarchy (what is displayed inside it) for the dialog; use onCreateDialog
to create a completely custom dialog. A typical scenario is to override onCreateDialog
and return an AlertDialog
. An AlertDialog
is a dialog with some text and one, two, or three buttons.
Add a confirm dialog to the TimeTracker app you created in Chapter 2 by following these steps:
1. Create a new class, ConfirmClearDialogFragment
, that extends DialogFragment
:
public class ConfirmClearDialogFragment extends DialogFragment {
}
2. Override the onCreateDialog
method and return a new AlertDialog
:
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.confirm_clear_all_title)
.setMessage(R.string.confirm_clear_all_message)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create();
}
Here, an AlertDialog.Builder
class is used to create the AlertDialog
that will be returned by the onCreateDialog
method. Note that it uses the string resources defined in strings.xml
to set the title, message, and button text of the dialog. In this example, the buttons are set to do nothing when clicked by setting the onClickListener
to null
. You will learn more about handling events using click listeners in the next section.
3. Add the following code to the onCreate
method to create the dialog and display it to the user:
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag("dialog") == null) {
ConfirmClearDialogFragment frag = ConfirmClearDialogFragment.newInstance(mTimeListAdapter);
frag.show(fm, "dialog");
}
If you run the app now, the dialog should appear immediately. Dismiss it by pressing any button. Fragments let you decompose your application into reusable components, such as this dialog. You’ll learn more about fragments in Chapter 5.
Note
Fragments, including the DialogFragment
, require a public no-argument constructor. Failing to provide one will result in odd behavior in your app.
Like most GUI frameworks, Android uses an event-based model to handle user interaction. When the user taps the screen, a touch event is fired and the corresponding onTouch
method of the tapped view is called. By extending the views in your UI, you can receive these events and use them to build gestures into your app; similar methods exist for handling focus and key change events.
These event callbacks form the basis of Android event handling. However, extending every view in your UI is not practical. Further, the low-level nature of the events requires work to implement simple interactions. For this reason, Android has a number of convenience methods for registering listeners on the existing View
class. These listeners provide callback interfaces that will be called when common interactions, like tapping the screen, are triggered. To handle these common events, you register an event listener on a view. The listener will be called when the event occurs on that view. You generally want to register the listener on the specific view the user will interact with. For example, if you have a LinearLayout
container with three buttons inside, you would register the listeners on the buttons rather than the container object. You have already seen an example of this with the onClick
method in the TimeTracker app.
Note
Android event callbacks are made by the main thread (also called the UI thread). It’s important that you not block this thread, or you will trigger an Application Not Responding (ANR) error. Make sure to perform any potentially long-running operations on a separate thread.
The simplest type of event is a simple tap on the screen. To listen for this event, you register an onClickListener
that has a single method: onClick
. This method will be called every time a user taps that view on the screen. An onClickListener
can be registered on any view. In fact, a button is just a view with a background that appears tappable and that responds to focus and press events by changing state.
In the previous section, you saw an example of an AlertDialog
with buttons. In that example, the button event listeners had been set to null
, disabling them. You can add custom button-click actions to the dialog by creating implementations of the OnClickInterface
:
AlertDialog.Builder(getActivity())
.setTitle(R.string.confirm_clear_all_title)
.setMessage(R.string.confirm_clear_all_message)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
mAdapter.clear();
}
})
.setNegativeButton(R.string.cancel, null)
.create();
This code creates a confirmation dialog. The positive button uses a click listener that will dismiss the dialog and clear the list adapter. The negative button again just clears the dialog.
Tip
To avoid creating anonymous classes for click handling, you can implement the click interfaces in your activity and simply pass in this
when registering a click listener.
A long press is triggered when the user taps and holds on the screen. It is used to create an alternate action for a view. This can be used to create a context-specific menu, trigger an alternate action, or drag an icon on the screen. You can think of the long press as analogous to the right-click on a traditional desktop application.
Long presses are handled by registering an onLongPressListener
. Other than the name, the setup of a long-press listener is exactly the same as a standard click listener. Here is a simple example of a long press listener that displays a toast message:
View view = findViewById(R.id.my_view);
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(TimeTrackerActivity.this, "Long pressed!", Toast.LENGTH_LONG).show();
return false;
}
});
The most common usage of long pressing in a UI is for creating a context menu. For those cases, you don’t create a long press listener but instead create a context-menu listener. You’ll learn more about context menus in the next section.
Note
Android will propagate events up the view hierarchy. Returning true
from an event handler will stop the propagation, as the event has been reported as handled. Make sure you want to stop handling events when you return true
.
The majority of Android devices have a touchscreen interface. However, Android is also designed to work on devices that use a keyboard-style input. In this case, the touch/click events don’t apply. To handle those cases, Android uses focus events and key events. Figure 3.7 shows an example of the different states of a button.
The focus event is triggered when a view on the screen gains focus. This happens when a user navigates to it using a trackball or the arrow keys on a keyboard. It is called again when the view loses focus; this is typically used on devices that have a trackball. When the user actually presses an action button on your view, the key event will be called. You can intercept this event using an OnKeyListener
, which will trigger the onKey
event when the user presses a button. You can also directly override the onKeyUp
, onKeyDown
, or onKeyPress
methods of the View
class, providing a lower level of event handling.
While these events may seem unnecessary in an age of touchscreen devices, there are important uses for focus and key events. If you’re designing apps for the Google TV platform, you will want to use these events to handle navigation in your app, because the user will likely be using a remote control. Also, properly handling focus and key events is key to adding accessibility features to your application. Users with disabilities that require screen readers or other alternate input methods will appreciate an app that is designed to work without a touch interface.
All Android devices before version 3.0 include a menu button. This menu button creates an activity-specific menu that you can use to provide extra functionality in your app (Figure 3.8). This frees you to design your UI with only the most important actions and to hide optional functionality. On Android versions 3.0 and later, the menu button is generally part of the application UI; it appears as a button in the action bar. You will learn more about the action bar in Chapter 6.
Tip
You should take care not to provide too much functionality via the options menu. Common user actions should be available with a single touch in your UI. Users may not even know that an action is available if it is buried in a menu.
Like all other Android layouts, menus can be defined using XML or Java code. It’s generally a better idea to use XML, because you can quickly create the menu options and their order without any boilerplate code.
Add a menu to the TimeTracker app to clear the current list of times:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/clear_all"
android:title="@string/clear_all"/>
</menu>
The basic structure of the menu layout is quite simple: A top-level menu element contains the item elements; each item element defines a single menu option. The android:id
attribute is required for each item and is how you will reference the options in your code. The android:title
attribute provides the string resource name that will appear in your app. Although a title is not required, you should always provide one; otherwise, your menu option will appear as a blank space.
You can optionally assign to your menu items an icon that will be displayed alongside the text. Icons can help the user quickly understand the available options in your menu. Menus can be nested inside items, creating submenus. Figure 3.9 shows an example of a menu leading to a submenu.
There are many more options available for menu items. You should explore the range of options available and take advantage of menus in your application.
To provide an options menu in your activity, you need to override the callback methods onCreateOptionsMenu
and onOptionsItemSelected
. The onCreateOptionsMenu
callback is called when the user presses the menu button; this is where you create the menu by using the layout resource file. To do this, inflate the layout file by using the MenuInflater
class. The following code snippet provides an example:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
Once the menu has been created, you override onOptionsItemSelected
to handle the menu selection. This method is called with the menu item that the user selected. You then select the appropriate action based on the selected menu item.
Add an option to clear all tasks to the TimeTracker application:
1. Override the onOptionsItemSelected
method:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.clear_all:
return true;
default:
return super.onOptionsItemSelected(item);
}
}
2. Move the dialog creation code from the onCreate
method. The result is a dialog confirming that the user wants to clear all the tasks:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.clear_all:
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag("dialog") == null) {
ConfirmClearDialogFragment frag = ConfirmClearDialogFragment.newInstance(mTimeListAdapter);
frag.show(fm, "dialog");
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
The method returns true
when it has finished creating the dialog to indicate that the event has been handled and should not be propagated to any other handlers.
A context menu is like a right-click menu in a standard desktop computing environment. Context menus work the same way as options menus but are triggered when the user long-presses a view. To create a context menu, you set the context menu listener for a view and implement the onCreateContextMenu
method. In this method, you inflate the context menu to display to the user. Finally, you implement the onContextItemSelected
method to handle user selections. You register the context menu listener using either View.setOnCreateContextMenuListener
or Activity.registerForContextMenu
. Figure 3.10 shows an example context menu. Here is the code to create that menu:
TextView tv = (TextView) findViewById(R.id.text);
tv.setOnCreateContextMenuListener(this);
// Alternatively, use Activity.registerForContextMenu(tv);
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.item1:
return true;
case R.id.item2:
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Now that you’ve added a notification, a menu, and a dialog to the TimeTracker app, you should have something that looks like Figure 3.11. It still runs only while the app is in the foreground, though. To fix this, you’ll need to create a service to handle running the timer and updating the notification. A service is how you perform background tasks on Android. Its life cycle is similar to that of the activity, but it does not have a UI component. Anytime you need to execute code when the user is not actively using your app, you should create a service.
Note
The service life cycle callbacks are run by the Android main thread. Just as with activities, you should avoid performing long-running operations in those methods. Instead, start a thread or use message handlers with background workers to perform the actual background work.
1. Create a new TimerService
class that extends Service
. Move all the Handler
code from the TimeTrackerActivity
to the TimerService
, and add some convenience methods for stopping and resetting the timer:
public class TimerService extends Service {
private static final String TAG = "TimerService";
public static int TIMER_NOTIFICATION = 0;
private NotificationManager mNM = null;
private Notification mNotification = null;
private long mStart = 0;
private long mTime = 0;
public class LocalBinder extends Binder {
TimerService getService() {
return TimerService.this;
}
}
private final IBinder mBinder = new LocalBinder();
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
long current = System.currentTimeMillis();
mTime += current - mStart;
mStart = current;
updateTime(mTime);
mHandler.sendEmptyMessageDelayed(0, 250);
};
};
@Override
public void onCreate() {
Log.i(TAG, "onCreate");
mNM = (NotificationManager)getSystemService (NOTIFICATION_SERVICE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Show notification when we start the timer
showNotification();
mStart = System.currentTimeMillis();
// Only a single message type, 0
mHandler.removeMessages(0);
mHandler.sendEmptyMessage(0);
// Keep restarting until we stop the service
return START_STICKY;
}
@Override
public void onDestroy() {
// Cancel the ongoing notification.
mNM.cancel(TIMER_NOTIFICATION);
mHandler.removeMessages(0);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public void stop() {
mHandler.removeMessages(0);
stopSelf();
mNM.cancel(TIMER_NOTIFICATION);
}
public boolean isStopped() {
return !mHandler.hasMessages(0);
}
public void reset() {
stop();
timerStopped(mTime);
mTime = 0;
}
}
You still need to update the activity, though, so the service will need to notify the activity of the current time via the updateTime
method.
2. Create the updateTime
method, and inside it create a broadcast intent to send the current time:
private void updateTime(long time) {
Intent intent = new Intent(TimeTrackerActivity. ACTION_TIME_UPDATE);
intent.putExtra("time", time);
sendBroadcast(intent);
}
3. Create a timerStopped
method to notify the activity that the timer has finished:
private void timerStopped(long time) {
// Broadcast timer stopped
Intent intent = new Intent(TimeTrackerActivity. ACTION_TIMER_FINISHED);
intent.putExtra("time", time);
sendBroadcast(intent);
}
4. In the TimeTrackerActivity onCreate
method, create an IntentFilter
to be called when the intent is broadcast:
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_TIME_UPDATE);
filter.addAction(ACTION_TIMER_FINISHED);
registerReceiver(mTimeReceiver, filter);
Now when the service updates the time, the activity will be notified and can update its counter. This will also come in handy later when you create a widget.
5. Add the notification code to the service, and call it when the timer is updated:
private Notification mNotification;
private void updateNotification(long time) {
String title = getResources().getString (R.string.running_timer_notification_title);
String message = DateUtils.formatElapsedTime(time/1000);
Context context = getApplicationContext();
Intent intent = new Intent(context, TimeTrackerActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
mNotification.setLatestEventInfo(context, title, message, pendingIntent);
mNM.notify(TIMER_NOTIFICATION, mNotification);
}
You should now be able to run the timer in the background (Figure 3.12).
This chapter introduced basic Android UI concepts for supporting multiple device configurations, notifications, and options menus. Along the way, you learned that
• Android uses a combination of folder naming conventions, image scaling, and density-independent dimensions to create flexible layouts for different device configurations.
• Touch, focus, and key events are available, but you’ll probably want to use an event listener to handle common user actions such as tapping on the screen.
• Notifications are the primary method of notifying your users, but dialogs and toasts can be used when you need more or less urgency.
• Menus allow you to add functionality to your app without cluttering the layout, but you should take care not to hide essential actions from the user.
3.147.58.199