NeatoCode Techniques
Glanceable UI on Android: Don’t Bury Vital Info in Apps

Help get people the information they need to check often in an easy glance on Android by using the HTC Lockscreen API and Android Widget API. These APIs let an app offer the user UI controls that can be put on the homescreen and the lock screen. The lock screen is the first screen the user sees when they turn the phone on. Here is an example with information about an upcoming flight:

 

A person may need to check this to figure out when to leave their home in the morning, check it to figure out where in the airport to go, check it for updates from the airline, check it to see if they can grab an extra sandwich or bathroom trip before boarding, etc.. Make it easy for the person by publishing frequent check data like this in easy to see places.

This next flight example was written for a company that we’re working with. I’ve also published it on GitHub. When installed and run, users can set what reminder information they would like to be displayed:

Then they can hold down on their home screen and pick the app to show a home screen widget with this reminder information:

Or they can use the lock screen style chooser from the phone’s personalize settings to show it on their lock screen:

The first time the app is run, or when the help button is pressed, it explains how this works and offers a button to quickly open the lock screen style chooser directly:

Implementation

Both lock screen and widget APIs require being declared in the AndroidManifest.xml of the Android app using them. This file specifies the components of the Android app. For the lock screen, these permissions are needed in the manifest element:

<uses-permission a:name="android.permission.WAKE_LOCK"/>
<uses-permission a:name="com.htc.idlescreen.permission.IDLESCREEN_SERVICE" />

And these components are needed in the application element:

<uses-library a:name="com.htc.lockscreen.fusion" a:required="false" />
<uses-library a:name="com.htc.fusion.fx" a:required="false" />

<service a:name="com.htc.sample.nextflight.lockscreen.SampleService" a:enabled="false">
  <intent-filter>
    <action a:name="com.htc.lockscreen.idlescreen.IdleScreenService"/>
  </intent-filter>
  <meta-data a:name="com.htc.lockscreen.idlescreen" a:resource="@xml/idlescreen"/>
</service>

  <service a:name="com.htc.sample.nextflight.lockscreen.SampleServiceLegacy" a:enabled="false">
  <intent-filter>
    <action a:name="com.htc.lockscreen.idlescreen.IdleScreenService"/>
  </intent-filter>
  <meta-data a:name="com.htc.lockscreen.idlescreen" a:resource="@xml/idlescreen"/>
</service>  

For the widget API this component is needed in the application element:

<receiver
  a:icon="@drawable/ic_launcher"
  a:label="@string/widget_label"
    a:name=".widget.WidgetProvider" >
  <intent-filter >
    <action a:name="android.appwidget.action.APPWIDGET_UPDATE" />
  </intent-filter>
  
  <meta-data
    a:name="android.appwidget.provider"
    a:resource="@xml/widget_info" />
</receiver>

The complete AndroidManifest.xml for the sample app is available on GitHub. Both of these components refer to another XML specifying settings, such as res/xml/idlescreen.xml for the lockscreen, and res/xml/widget_info.xml for the widget API.

To enable the lockscreen component and show the user a help dialog explaining the feature is available, this code is used in the main activity:

final boolean enabledLockScreen = LockScreenHelper.enable(this);
if (enabledLockScreen) {
  if (!mPrefs.getShowedLockScreenDialog()) {
    new LockScreenDialog(this).show();
    mPrefs.setShowedLockScreenDialog(true);
  }
}

To display the user’s chosen reminder info on the lockscreen this code is used in LockScreenImple.java:

@Override
public void onCreate(final SurfaceHolder holder) {
  setContent(R.layout.lock_screen);
  setShowLiveWallpaper(true);
  mNextFlightName = (TextView)findViewById(R.id.next_flight_name);
  mNextFlightDetails = (TextView)findViewById(R.id.next_flight_details);
}

@Override
public void onResume() {
  mNextFlightName.setText(mPrefs.getNextFlightName());
  mNextFlightDetails.setText(mPrefs.getNextFlightDetails());
}

To show it in a widget, this code is used in WidgetProvider.java:

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    int[] appWidgetIds) {
  update(context);
}
public static void update(final Context context) {
  final Prefs prefs = new Prefs(context);
  ComponentName thisWidget = new ComponentName(context, WidgetProvider.class);
  updateWidgets(context, thisWidget, prefs.getNextFlightName(), prefs.getNextFlightDetails());
}
public static void updateWidgets(final Context context, final ComponentName thisWidget,
    final String fligtName, final String flightDetails) {
  final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context
      .getApplicationContext());
  final int[] allWidgetIds2 = appWidgetManager.getAppWidgetIds(thisWidget);
  for (final int widgetId : allWidgetIds2) {
    final RemoteViews remoteViews = new RemoteViews(context
        .getApplicationContext().getPackageName(),
        R.layout.widget_layout);
    remoteViews.setTextViewText(R.id.next_flight_name, fligtName);
    final PackageManager pm = context.getPackageManager();
    final Intent clickIntent = pm.getLaunchIntentForPackage(context.getPackageName());
    clickIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    final PendingIntent clickPI = PendingIntent.getActivity(context, 0,
        clickIntent,
        PendingIntent.FLAG_UPDATE_CURRENT);
    remoteViews.setOnClickPendingIntent(R.id.widget_content, clickPI);
    appWidgetManager.updateAppWidget(widgetId, remoteViews);
  }
}
Blog comments powered by Disqus