The entry point for this demo (MainActivity) uses a simple layout containing a single button that creates an intent with an action to start the background service that will be doing all of the work in our example.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView( R.layout.activity_main );
mLaunchNotificationButton = (Button) findViewById( R.id.launch_notification );
mLaunchNotificationButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick( View view ) {
Intent intent = new Intent( getApplicationContext(), CustomNotificationService.class );
intent.setAction( CustomNotificationService.ACTION_NOTIFICATION_PLAY_PAUSE );
startService( intent );
}
});
}
Once the intent for the service is fired, onStartCommand is called in the service, which is where I pass the intent to a function that handles filtering out the action and calling the appropriate methods.
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handleIntent( intent );
return super.onStartCommand(intent, flags, startId);
}
private void handleIntent( Intent intent ) {
if( intent != null && intent.getAction() != null )
{
if( intent.getAction().equalsIgnoreCase( ACTION_NOTIFICATION_PLAY_PAUSE ) )
{
mIsPlaying = !mIsPlaying;
showNotification(mIsPlaying);
} else if( intent.getAction().equalsIgnoreCase( ACTION_NOTIFICATION_FAST_FORWARD ) )
{
//fast forward function
} else if( intent.getAction().equalsIgnoreCase( ACTION_NOTIFICATION_REWIND ) )
{
//rewind action
}
}
}
If the action is to play or pause the service, then the service would perform these actions and then display a new notification with the updated UI from the showNotification function.
private void showNotification( boolean isPlaying ) {
Notification notification = new NotificationCompat.Builder( getApplicationContext() )
.setAutoCancel( true )
.setSmallIcon( R.drawable.ic_launcher )
.setContentTitle( getString( R.string.app_name ) )
.build();
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN )
notification.bigContentView = getExpandedView( isPlaying );
NotificationManager manager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
manager.notify( 1, notification );
}
The code here follows the same convention as my previous post for standard notifications, with the exception of the Jelly Bean code to create a bigContentView Remote Views. The code for pre-Jelly Bean devices creates a notification that looks like this:
The getExpandedView function returns a RemoteViews object that consists of an inflated custom view that has PendingIntents associated with each of the buttons that will be sent to the existing service in order to control the operations within with the service. The layout is fairly standard with a fixed size of 128dp and three buttons under some custom text:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_expanded_height">
<ImageView
android:id="@+id/large_icon"
android:layout_width="@dimen/notification_expanded_height"
android:layout_height="@dimen/notification_expanded_height"
android:scaleType="centerCrop"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
/>
<LinearLayout
android:id="@+id/buttons_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/large_icon"
android:orientation="horizontal">
<ImageButton
android:id="@+id/ib_rewind"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="@dimen/notification_button_height"
android:scaleType="fitCenter" />
<ImageButton
android:id="@+id/ib_play_pause"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="@dimen/notification_button_height"
android:scaleType="fitCenter" />
<ImageButton
android:id="@+id/ib_fast_forward"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="@dimen/notification_button_height"
android:scaleType="fitCenter" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/app_name"
android:textSize="@dimen/notification_text_size"
android:layout_gravity="center"
android:gravity="center"
android:layout_toRightOf="@+id/large_icon"
android:layout_above="@+id/buttons_row"/>
</RelativeLayout>
which in turn looks like this when it is created:
In the getExpandedView method, each image in the notification is set using the setImageViewResource method
customView.setImageViewResource( R.id.ib_rewind, R.drawable.ic_rewind );
and each button has a pending intent with action associated with it
Intent intent = new Intent( getApplicationContext(), CustomNotificationService.class );
intent.setAction( ACTION_NOTIFICATION_PLAY_PAUSE );
PendingIntent pendingIntent = PendingIntent.getService( getApplicationContext(), 1, intent, 0 );
customView.setOnClickPendingIntent( R.id.ib_play_pause, pendingIntent );
Since each of these pending intents goes back to the service, they are filtered through the handleIntent method and actions can be carried out based on the button clicks from this notification. Since the notification and operations are controlled through a service, the notification can control media when the app has been closed out or the Android device is locked. As this demo is not using an actual audio service to determine what controls should be shown, the mIsPlaying flag is set and passed to the custom view creation method in order to show either the play or pause button.
And that's how simple it is to create a custom expanded view with buttons!