Android Message Center Widget

Android widgets provide a way to display a view of the app to the user on the homescreen or keyguard. When building widgets, you have full access to the Urban Airship SDK, including access to the app’s Message Center.

The following example code shows how to create a home screen widget that lists the messages. This example utilizes the RemoteViewsService class in order to display a collection in a widget, making the widget incompatible with Android Gingerbread.

Displaying the Inbox

In order to display the inbox, the App Widget needs to be backed by a RemoteViewsService. The RemoteViewsService is responsible for providing a RemoteViewsFactory that builds the list item views for the App Widget.

Start by defining a basic RemoteViewsService that creates RemoteViews with each message’s title:

public class RichPushWidgetService extends RemoteViewsService {

    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new MessageRemoteViewsFactory();
    }

    private class MessageRemoteViewsFactory implements RemoteViewsFactory {

        @Override
        public int getCount() {
            return UAirship.shared().getInbox().getCount();
        }

        @Override
        public int getViewTypeCount() {
            return 1;
        }

        @Override
        public RemoteViews getViewAt(int position) {
            RichPushMessage message = UAirship.shared().getInbox().getMessages().get(position);

            // Create a remote view with the message title
            RemoteViews remoteView = new RemoteViews(getApplicationContext().getPackageName(), android.R.layout.simple_list_item_1);
            remoteView.setTextViewText(android.R.id.text1, message.getTitle());

            return remoteView;
        }

        @Override
        public void onCreate() {}

        @Override
        public void onDataSetChanged() {}

        @Override
        public void onDestroy() {}

        @Override
        public RemoteViews getLoadingView() {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public boolean hasStableIds() {
            return false;
        }
    };
}

The next step is to create an AppWidgetProvider. The AppWidgetProvider is a broadcast receiver used to communicate with the App Widget. Its primary responsibility is to update the App Widget.

public class RichPushWidgetProvider extends AppWidgetProvider {

      @Override
      public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
          // Update each widget based on their id
          for (int id : appWidgetIds) {
            // Create a new remote view for the widget
            RemoteViews remoteViews = createAppWidgetRemoteViews(context, id);

            // Update the widget
            appWidgetManager.updateAppWidget(id, remoteViews);
          }
      }

      private RemoteViews createAppWidgetRemoteViews(Context context, int appWidgetId) {
          RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

          // Specify the service to provide data for the collection widget.  Note that we need to
          // embed the appWidgetId via the data otherwise it will be ignored.
          Intent intent = new Intent(context, RichPushWidgetService.class);
          intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
          remoteViews.setRemoteAdapter(appWidgetId, R.id.message_list, intent);

          // Set the empty view to be displayed if the collection is empty.  It must be a sibling
          // view of the collection view.
          remoteViews.setEmptyView(R.id.message_list, R.id.empty_view);

          return remoteViews;
      }
}

Define the App Widget layout in res/layout/widget_layout:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:layout_gravity="center" >

    <ListView
        android:id="@+id/message_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <TextView
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:visibility="gone"
        android:text="@string/empty_view_text"
        android:textColor="#F8F8F8"
        android:textSize="20sp" />
</FrameLayout>

Before the App Widget can be added to the main screen, you must provide details about the widget.

Define the widget’s info in res/values/widget_info.xml:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider>
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:minWidth="180dp"
   android:minHeight="120dp"
   android:updatePeriodMillis="1800000"
   android:initialLayout="@layout/widget_layout"
</appwidget-provider>

Update the AndroidManifest.xml with the new widget provider, remote views service, and the widget info:

<receiver android:name=".RichPushWidgetProvider">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>

    <!-- This specifies the widget provider info -->
    <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_info" />
</receiver>

<!-- The service serving the RemoteViews to the collection widget -->
<service
    android:name=".RichPushWidgetService"
    android:permission="android.permission.BIND_REMOTEVIEWS"
    android:exported="false" />

Now the Message Center Inbox should be able to be added to the home screen. It will display the messages in a list view and update every 30 minutes (defined by the App Widget info).

Handling Click Events

Displaying a list of inbox messages is not very useful if the user cannot interact with the messages. Click events can be captured in the App Widget by providing a pending intent to be fired when a remote view is clicked. For this example, we will set up a pending intent that sends a broadcast with the ID of the message that was clicked back to the AppWidgetProvider.

Override the AppWidgetProvider’s onReceive method to handle “ACTION_MESSAGE_CLICKED” broadcast:

private static final String ACTION_MESSAGE_CLICKED = "ACTION_MESSAGE_CLICKED";
private static final String EXTRA_MESSAGE_ID = "EXTRA_MESSAGE_ID";

@Override
public void onReceive(Context context, Intent intent) {

    // Handle the ACTION_MESSAGE_CLICKED intent action
    if (ACTION_MESSAGE_CLICKED.equals(intent.getAction())) {
        String messageId = intent.getStringExtra(EXTRA_MESSAGE_ID);

        // Launch the message activity
        UAirship.shared().getInbox().startMessageActivity(messageId);
    }

    super.onReceive(context, intent);
}

Also provide a pending intent template that will broadcast a message back to the AppWidgetProvider when a message is clicked:

private RemoteViews createAppWidgetRemoteViews(Context context, int appWidgetId) {
     ...

     Intent openIntent = new Intent(context, RichPushWidgetProvider.class).setAction(ACTION_MESSAGE_CLICKED);
     PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

     remoteViews.setPendingIntentTemplate(R.id.message_list, pendingIntent);

     return remoteViews;
}

Then in the MessageRemoteViewsFactory, fill in the intent with the message’s ID when constructing the view:

@Override
public RemoteViews getViewAt(int position) {
    ...

    Intent fillInIntent = new Intent().putExtra(RichPushWidgetProvider.EXTRA_MESSAGE_ID, message.getMessageId());
    remoteView.setOnClickFillInIntent(android.R.id.text1, fillInIntent);

    return remoteView;
}

Listening for Inbox Changes

The widget will only refresh its content every 30 minutes. However, the inbox could be updated during this interval. To keep the inbox up-to-date, add a listener to the inbox that refreshes the widget.

After UAirship.takeOff:

airship.getInbox().addListener(new RichPushInbox.Listener() {
     @Override
     public void onInboxUpdated() {
         // Get all the widget IDs for this provider
         AppWidgetManager widgetManager = AppWidgetManager.getInstance(context);
         ComponentName widgetComponent = new ComponentName(context, RichPushWidgetProvider.class);
         int[] widgetIds = widgetManager.getAppWidgetIds(widgetComponent);

         // Update the widget
         widgetManager.notifyAppWidgetViewDataChanged(widgetIds, R.id.message_list);
     }
 });