Saturday, August 15, 2015

Where'd you go?

Update: I've been hired on to write for Tuts+, so I've been posting articles there rather than here. If you're interested in more content, please follow my posts on Tuts+. I may still write more here when a topic comes up that I'd like to cover that doesn't fit with their publishing schedule.

Sunday, February 22, 2015

Sending Messages to Android Auto

    In November of 2014, Google provided developers with two simulators, one for media apps and another for message notifications, to start updating and testing apps to work with the new Android Auto platform. In a previous post, I shared how to create a service that works with Android Auto in order to play media, and in this post I will go over how to interact with and send messages to the Android Auto notification screen. All source code for this tutorial can be found on GitHub. The previous post (linked above) has instructions for installing the Auto simulators, so I'm going to skip over that in this post for brevity.

Main message screen on Android Auto
    To start, we're going to want to get everything set up. We'll do this by first making sure our app is targeting SDK version 21 or higher in build.gradle. Next we'll move over to adding in a new xml file under /res/xml called automotive_app_desc.xml and add in the following content to let Android Auto know that this app supports notifications
<automotiveApp>
    <uses name="notification"/>
</automotiveApp>
    Next we're going to want to go into AndroidManifest.xml and add a metadata tag within the application node to direct the OS towards our previously created xml file.
<meta-data android:name="com.google.android.gms.car.application"
     android:resource="@xml/automotive_app_desc" />
    In addition to our metadata tag, we're going to add two broadcast receivers with intent-filters that look for specific actions for 'read' or 'reply' user actions. While I'm using broadcast receivers here, it should be noted that you could also create Services to handle the read and reply situations instead, which I'll talk about a little more when we start tying in these broadcast receivers in code.
<receiver android:name=".AutoMessageReadReceiver" android:exported="false">
    <intent-filter>
        <action android:name="com.ptrprograms.androidautomessenger.ACTION_MESSAGE_READ"/>
    </intent-filter>
</receiver>

<receiver android:name=".AutoMessageReplyReceiver" android:exported="false">
    <intent-filter>
        <action android:name="com.ptrprograms.androidautomessenger.ACTION_MESSAGE_REPLY"/>
    </intent-filter>
</receiver>
    Now that the general setup is done, we can jump into our Java code. The way Android Auto's messaging system works is an Android application connected to Auto runs all of the logic for when a notification should be constructed, then it sends that notification to the Auto dashboard. To keep things simple, I'm going to build out the notification in our application's MainActivity.java file in order to point out what needs to be done. The first part of this should seem familiar if you've dealt with Android notifications before (and if not, I've written other posts on creating notifications): we're going to create a general NotificationCompat.Builder object.
NotificationCompat.Builder notificationBuilder =
    new NotificationCompat.Builder( getApplicationContext() )
            .setSmallIcon( R.drawable.ic_launcher )
            .setLargeIcon( BitmapFactory.decodeResource( getResources(), R.drawable.ic_launcher ) )
            .setContentText( "content text" )
            .setWhen( Calendar.getInstance().get( Calendar.SECOND ) )
            .setContentTitle( "content title" );
    With the NotificationCompat.Builder created, we can start adding in the Android Auto portions of the code. To do this, we need to use the .extend() method of NotificationCompat.Builder to add functionality from NotificationCompat.CarExtender(), which also uses a builder pattern to add functionality to our app (more options are available than shown here, such as setting text color for the Auto notification, but I'll leave that off for now). Each message group on the Auto dashboard is called a Conversation, so we also need to create an UnreadConversation to add to our notification.
notificationBuilder.extend( new NotificationCompat.CarExtender()
        .setUnreadConversation( getUnreadConversation() ) );
   getUnreadConversation() handles creating the messages that we will display, the actions taken when the user has read them and if they reply
private NotificationCompat.CarExtender.UnreadConversation getUnreadConversation() {
    NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder =
            new NotificationCompat.CarExtender.UnreadConversation.Builder( UNREAD_CONVERSATION_BUILDER_NAME );

    unreadConversationBuilder
        .setReadPendingIntent( getMessageReadPendingIntent() )
        .setReplyAction( getMessageReplyPendingIntent(), getVoiceReplyRemoteInput() )
        .addMessage( "Message 1")
        .addMessage( "Message 2" )
        .addMessage( "Message 3" )
        .setLatestTimestamp( Calendar.getInstance().get( Calendar.SECOND ) );

    return unreadConversationBuilder.build();
}
    The important things to pay attention to here are getMessageReadPendingIntent(), getMessageReplyPendingIntent() and getVoiceReplyRemoteInput(). As mentioned before, I'm using a set of BroadcastReceivers to handle responding to when the user reads or replies to a message, but since we're using a system of PendingIntents here, we could just as easily create a set of intents that go to a service to deal with these actions. getMessageReadPendingIntent() is pretty straight forward, creating a pending intent with an action that will be caught by AutoMessageReadReceiver.java after the user has tapped on the message and it has been read aloud by the system.
private Intent getMessageReadIntent() {
    return new Intent()
        .addFlags( Intent.FLAG_INCLUDE_STOPPED_PACKAGES )
        .setAction( MESSAGE_READ_ACTION )
        .putExtra( MESSAGE_CONVERSATION_ID_KEY, 1 );
}

private PendingIntent getMessageReadPendingIntent() {
    return PendingIntent.getBroadcast( getApplicationContext(),
            1,
            getMessageReadIntent(),
            PendingIntent.FLAG_UPDATE_CURRENT );
}
    AutoMessageReadReceiver simply listens for our intent with the MESSAGE_READ_ACTION and dismisses the notification associated with the intent.
public class AutoMessageReadReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        int conversationId = intent.getIntExtra( MainActivity.MESSAGE_CONVERSATION_ID_KEY, -1 );
        Log.d( "Message", "id: " + conversationId );
        NotificationManagerCompat.from( context ).cancel( conversationId );
    }
}
Messages before they have been listened to
Message notification after all 'UnreadConversations' have been read
    The reply action is a little more interesting, though equally straight forward. We create another PendingIntent for a separate BroadcastReceiver, and also attach a RemoteInput for a voice reply with the setReplyAction method of our UnreadConversationBuilder.
private Intent getMessageReplyIntent() {
    return new Intent()
            .addFlags( Intent.FLAG_INCLUDE_STOPPED_PACKAGES )
            .setAction( MESSAGE_REPLY_ACTION )
            .putExtra( MESSAGE_CONVERSATION_ID_KEY, 1 );
}

private PendingIntent getMessageReplyPendingIntent() {
    return PendingIntent.getBroadcast( getApplicationContext(),
            1,
            getMessageReplyIntent(),
            PendingIntent.FLAG_UPDATE_CURRENT );
}

private RemoteInput getVoiceReplyRemoteInput() {
    return new RemoteInput.Builder( VOICE_REPLY_KEY )
            .setLabel( "Reply" )
            .build();
}
    where our BroadcastReceiver, AutoMessageReplyReceiver.java, not only marks a message as read, but will also extract a voice reply provided by the user in order to use it within our app.
public class AutoMessageReplyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText( context, "Message Received", Toast.LENGTH_LONG ).show();

        int conversationId = intent.getIntExtra( MainActivity.MESSAGE_CONVERSATION_ID_KEY, -1 );
        Log.d( "Message", "id: " + conversationId );
        NotificationManagerCompat.from(context).cancel( conversationId );

        String message = getMessageFromIntent( intent );
    }

    private String getMessageFromIntent( Intent intent ) {
        //Note that Android Auto does not currently allow voice responses in their simulator
        Bundle remoteInput = RemoteInput.getResultsFromIntent( intent );
        if( remoteInput != null && remoteInput.containsKey( "extra_voice_reply" ) ) {
            return remoteInput.getCharSequence( "extra_voice_reply" ).toString();
        }

        return null;
    }
}
Reply button pressed. Currently the Android Auto simulator does not allow for voice responses, so it sends an empty string value with the RemoteInput and posts a Toast with "Canned response sent".
    Once the notification is properly set up for displaying on Auto, we use NotificationManagerCompat to display it on our device and Auto from MainActivity.java
NotificationManagerCompat.from( this ).notify( 1, notificationBuilder.build() );
    Once the notification has been sent, it will be visible to the user on the Android Auto dashboard. While this system may be simple to work with, given a proper location or context aware app, it could be an invaluable feature for users as they travel. While there are still a few things missing from the official documentation that I'd like to see, such as how to hide the reply button, I'm definitely excited to see what else becomes available as Auto is released and matures, and hope this tutorial helps others create great apps.

Monday, February 16, 2015

Using the Android Auto Media Browser

    During the 2014 I/O, Google made the announcement that Android would be available for in-vehicle systems, but left everyone hanging on the major details. Luckily, in November 2014 they released two simulator APKs for developers to begin testing different features of their apps for Android Auto: audio and messaging services. For this tutorial, I will go over using the Android Auto framework to browse media and create a simple media player (which can obviously be expanded on for a real application). Source code for this tutorial can be found on GitHub.

   In order to start, we'll first need to install the Media Browser Simulator APK for Android Auto onto a Lollipop device. I ended up using a 2013 Nexus 7, though a Lollipop emulator should also work for our purposes. This can be found under your Android SDK folder along the path /extras/google/simulators/media-browser-simulator.apk after installing Android Auto API Simulators from the SDK Manger. You can install this through the Android Device Bridge (ADB) with the following command:
adb install media-browser-simulator.apk
    Once this is loaded up, we should be able to open the application on our device to see a screen similar to this (minus the AndroidAutoMedia item that we'll build through this tutorial):


    Next we'll want to get going on our actual app. We'll start by creating an app in Android Studio for phone/tablet. Once that's done and we have our standard "Hello World" template together, we'll need to go under /res/xml and create a new xml file - under my source code it's called automotive_app_desc.xml, though you can call it whatever you'd like. This file will be used by our application to let Android Auto know that we're building components to work with it. For now, simply copy the following source code into the XML file that tells Android Auto that we're making a media Auto plugin.
<automotiveApp>
    <uses name="media" />
</automotiveApp>
    With our XML file created, we'll move over and open AndroidManifest.xml to make a few additions to work with Auto. Within the application tag, we're going to want to add two meta-data items - one points to the XML file we just created, and the other provides the Android Auto launcher icon used in the media browser shown above (I just used the standard ic_launcher.png).
<meta-data android:name="com.google.android.gms.car.application"
    android:resource="@xml/automotive_app_desc"/>

<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
    android:resource="@drawable/ic_launcher" />
    After the meta-data nodes are made, we'll want to add a new Service node to the manifest, and include an intent-filter for "android.media.browse.MediaBrowserService."
<service android:name="com.ptrprograms.androidautomedia.service.AutoMediaBrowserService">
    <intent-filter>
        <action android:name="android.media.browse.MediaBrowserService" />
    </intent-filter>
</service>
    Now that the manifest is filled out, we can start working on our Java code. We'll create a service (I called mine AutoMediaBrowserService, as seen above) and extend the MediaBrowserService class provided in the SDK. There are two methods that we will want to override here in order to get started. The first is onGetRoot( String clientPackageName, int clientUid, Bundle rootHints ), which is called by the Auto media browser application when first interacting with our service. In this method, we're only going to add one line to return a new BrowserRoot object using our root identifier string (I've defined mine at the top of the class as a final variable of BROWSEABLE_ROOT), though you can also use this method to validate the calling package to verify that it should have access to your media assets and return null if it fails validation.
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
    return new BrowserRoot(BROWSEABLE_ROOT, null);
}
    The other method that we'll need to override is onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result). This method is called when the root item or any subsequent items are clicked on in the Android Auto media browser. This is where you will check the parentId parameter and build out the list of MediaBrowser.MediaItems based on that id in order to provide a file structure for your app. In order to simplify things, I've moved this logic into a separate method so that onLoadChildren() is easily digestible.
@Override
public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {

    List<MediaBrowser.MediaItem> items = getMediaItemsById( parentId );
    if( items != null ) {
        result.sendResult( items );
    }

}
    Now before I go into explaining how getMediaItemsById() works for providing media items and folders, I need to go over a bit of the prep code that I placed into onCreate() for this service. I created a model object called Song that consists of general information about the media object, such as artist, title, genre, etc. and a quick generator class to create the media objects that will be used for this sample. How you create and work with your own media objects will depend on your implementation and applications, so I tried to keep mine as simple as possible The Song model object source can be found here, and the generator helper class can be found here. With that in mind, my onCreate() looks like this
    @Override
    public void onCreate() {
        super.onCreate();

        mSongs = SongGenerator.generateSongs();

        initMediaSession();
    }
    where initMediaSession simply builds out the MediaSession and Token used for this sample:
private void initMediaSession() {
    mMediaSession = new MediaSession( this, "Android Auto Audio Demo" );
    mMediaSession.setActive( true );
    mMediaSession.setCallback( mMediaSessionCallback );

    mMediaSessionToken = mMediaSession.getSessionToken();
    setSessionToken( mMediaSessionToken );
}
    With this foundation in mind, we can move over to looking at getMediaItemsById(). This method will take an id, either our root id or one associated with a clicked item in the Auto browser, and create a folder or file structure to display to the user.
    private List<MediaBrowser.MediaItem> getMediaItemsById( String id ) {
        List<MediaBrowser.MediaItem> mediaItems = new ArrayList<MediaBrowser.MediaItem>();
        if( BROWSEABLE_ROOT.equalsIgnoreCase( id ) ) {
            mediaItems.add( generateBrowseableMediaItemByGenre(BROWSEABLE_CAJUN) );
            mediaItems.add( generateBrowseableMediaItemByGenre(BROWSEABLE_JAZZ) );
            mediaItems.add( generateBrowseableMediaItemByGenre(BROWSEABLE_ROCK) );
        } else if( !TextUtils.isEmpty( id ) ) {
            return getPlayableMediaItemsByGenre( id );
        }

        return mediaItems;
    }
    As you can see, if BROWSEABLE_ROOT is the clicked item, we create three media items with the method generateBrowseableMediaItemByGenre() and pass in one of three strings defined at the top of the class, and then return those items as part of our MediaItems list. generateBrowseableMediaItemByGenre() simply creates a MediaItem with the FLAG_BROWSEABLE flag set and some basic information to define the folders, as shown here:
private MediaBrowser.MediaItem generateBrowseableMediaItemByGenre( String genre ) {
    MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
    mediaDescriptionBuilder.setMediaId( genre );
    mediaDescriptionBuilder.setTitle( genre );
    mediaDescriptionBuilder.setIconBitmap( BitmapFactory.decodeResource( getResources(), R.drawable.folder ) );

    return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE );
}
    This will provide us with three folders for three different genres under our root structure, like so


    When one of these three folders is tapped on by a user, we return to onLoadChildren(), which in turn calls getMediaItemsById() again, but this getMediaItemsById() will have an id that is not our root folder, so getPlayableMediaItemsByGenre( String genre ) is called. This will loop through all of our songs and create a list of MediaItems based on the passed genre
private List<MediaBrowser.MediaItem> getPlayableMediaItemsByGenre( String genre ) {
    if( TextUtils.isEmpty( genre ) )
        return null;

    List<MediaBrowser.MediaItem> mediaItems = new ArrayList();

    for( Song song : mSongs ) {
        if( !TextUtils.isEmpty( song.getGenre() ) && genre.equalsIgnoreCase( song.getGenre() ) ) {
            mediaItems.add( generatePlayableMediaItem( song ) );
        }
    }
    return mediaItems;
}
    where generatePlayableMediaItem( song ) creates a MediaItem with properties based on the song and the FLAG_PLAYABLE flag set
private MediaBrowser.MediaItem generatePlayableMediaItem( Song song ) {
    if( song == null )
        return null;

    MediaDescription.Builder mediaDescriptionBuilder = new MediaDescription.Builder();
    mediaDescriptionBuilder.setMediaId( song.getuId() );

    if( !TextUtils.isEmpty( song.getTitle() ) )
        mediaDescriptionBuilder.setTitle( song.getTitle() );

    if( !TextUtils.isEmpty( song.getArtist() ) )
        mediaDescriptionBuilder.setSubtitle( song.getArtist() );

    if( !TextUtils.isEmpty( song.getThumbnailUrl() ) )
        mediaDescriptionBuilder.setIconUri( Uri.parse( song.getThumbnailUrl() ) );

    return new MediaBrowser.MediaItem( mediaDescriptionBuilder.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE );
}
    At this point we should be able to click through the Auto browser to get to a screen similar to this:



    Now if you remember, back in initMediaSession() we had this line:
mMediaSession.setCallback( mMediaSessionCallback );
    mMediaSessionCallback is defined at the top of our class and is meant to handle actions from the buttons available in the Auto media player. This is where we start and stop our media, define which controls are available and set the metadata for our media that will be displayed by Android Auto.
private MediaSession.Callback mMediaSessionCallback = new MediaSession.Callback() {
    @Override
    public void onPlay() {
        super.onPlay();

        toggleMediaPlaybackState( true );
        playMedia( PreferenceManager.getDefaultSharedPreferences( getApplicationContext() ).getInt( CURRENT_MEDIA_POSITION, 0 ), null );
    }

    //This is called when the pause button is pressed, or when onPlayFromMediaId is called in
    //order to pause any currently playing media
    @Override
    public void onPause() {
        super.onPause();

        toggleMediaPlaybackState( false );
        pauseMedia();
    }

    @Override
    public void onPlayFromMediaId(String mediaId, Bundle extras) {
        super.onPlayFromMediaId(mediaId, extras);

        initMediaMetaData( mediaId );
        toggleMediaPlaybackState( true );
        playMedia( 0, mediaId );
    }
};
    As you can see, I have a few helper methods here that I'll go over next. onPlayFromMediaId( String mediaId, Bundle extras ) is the callback that the system uses when a MediaItem with the FLAG_PLAYABLE property is clicked. In this callback we first set up our metadata for displaying information about the MediaItem with initMediaMetaData()
private void initMediaMetaData( String id ) {

    for( Song song : mSongs ) {
        if( !TextUtils.isEmpty( song.getuId() ) && song.getuId().equalsIgnoreCase( id ) ) {
            MediaMetadata.Builder builder = new MediaMetadata.Builder();

            if( !TextUtils.isEmpty( song.getTitle() ) )
                builder.putText( MediaMetadata.METADATA_KEY_TITLE, song.getTitle() );

            if( !TextUtils.isEmpty( song.getArtist() ) )
                builder.putText( MediaMetadata.METADATA_KEY_ARTIST, song.getArtist() );

            if( !TextUtils.isEmpty( song.getGenre() ) )
                builder.putText( MediaMetadata.METADATA_KEY_GENRE, song.getGenre() );

            if( !TextUtils.isEmpty( song.getAlbum() ) )
                builder.putText( MediaMetadata.METADATA_KEY_ALBUM, song.getAlbum() );

            if( !TextUtils.isEmpty( song.getAlbumUrl() ) )
                builder.putText( MediaMetadata.METADATA_KEY_ALBUM_ART_URI, song.getAlbumUrl() );

            mMediaSession.setMetadata( builder.build() );
        }
    }
}
     Then we toggle our MediaState and set our controls with toggleMediaPlaybackState(). Note that the controls are set using setAction on our PlaybackState.Builder() by bitwise ORing our actions, so when our media is playing we'll display the Pause, Skip to Next and Skip to Previous, and when our media is paused we'll only display the Play button.
private void toggleMediaPlaybackState( boolean playing ) {
    PlaybackState playbackState;
    if( playing ) {
        playbackState = new PlaybackState.Builder()
                .setActions( PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS )
                .setState( PlaybackState.STATE_PLAYING, 0, 1 )
                .build();
    } else {
        playbackState = new PlaybackState.Builder()
                .setActions( PlaybackState.ACTION_PLAY_PAUSE )
                .setState(PlaybackState.STATE_PAUSED, 0, 1)
                .build();
    }

    mMediaSession.setPlaybackState( playbackState );
}
 


        The final methods in our service simply control starting, pausing and resuming our media. For this sample I only have one MP3 available to keep things simple, though you can set what plays based on the media id passed to the callback methods. The MP3 I'm using here was provided by a good friend of mine, Geoff Ledak, who you can catch DJing online at AfterHoursDJs.org.
private void playMedia( int position, String id ) {
    if( mMediaPlayer != null )
        mMediaPlayer.reset();

    //Should check id to determine what to play in a real app
    int songId = getApplicationContext().getResources().getIdentifier("geoff_ledak_dust_array_preview", "raw", getApplicationContext().getPackageName());
    mMediaPlayer = MediaPlayer.create(getApplicationContext(), songId);

    if( position > 0 )
        mMediaPlayer.seekTo( position );
    mMediaPlayer.start();

}

private void pauseMedia() {
    if( mMediaPlayer != null ) {
        mMediaPlayer.pause();
        PreferenceManager.getDefaultSharedPreferences( this ).edit().putInt( CURRENT_MEDIA_POSITION,
                mMediaPlayer.getCurrentPosition() ).commit();
    }
}

@Override
public void onDestroy() {
    super.onDestroy();
    if( mMediaPlayer != null ) {
        pauseMedia();
        mMediaPlayer.release();
        PreferenceManager.getDefaultSharedPreferences( this ).edit().putInt( CURRENT_MEDIA_POSITION,
                0 ).commit();
    }

}
    One more thing we can do is add a colorAccent to our app theme in order to add a bit of personalization to fit our app under /res/values-v21
<style name="AppTheme" parent="android:Theme.Material.Light">
    <item name="android:colorAccent">@android:color/holo_green_dark</item>
</style>
    And with that, we should have a working Android Auto browser service plugin. I hope you've enjoyed the tutorial, and good luck building your own implementations for this new platform!

Sunday, January 25, 2015

Implementing and Using Custom Drawable States

    One of the things that Android comes with out of the box is pressed states, which allows colors and drawables to change what's displayed on a view based on whether or not a user is pressing down on that view. In this post I'm going to take that a step further and go over a simple example for how to implement custom drawable states that allow you to tailor your user experience based on data. All code for this post can be found on GitHub.

    To keep things simple, the first class we're going to define is an enum containing the expected states that we want to work with. In this case we're going to use a traffic signal set of "Go", "Slow Down" and "Stop".

public enum CustomState {
    GO,
    SLOW_DOWN,
    STOP
}

    Next we're going to want to create an 'attrs.xml' file to store the attributes that we'll use for our states

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="state_go" format="boolean" />
    <attr name="state_slow_down" format="boolean" />
    <attr name="state_stop" format="boolean" />
</resources>

    Once our attributes are defined, we're going to move to where the magic happens: our custom View (CustomDrawableTextView.java, in this example). The first thing we're going to do is add a set of int arrays that represent each state that we want to support as member variables. These int arrays are going to be used later to merge in with our drawable states. We're also going to want to create a variable to keep track of which state is currently active from our custom enum.

    protected static int[] STATE_GO = { R.attr.state_go };
    protected static int[] STATE_SLOW_DOWN = { R.attr.state_slow_down };
    protected static int[] STATE_STOP = { R.attr.state_stop };

    private CustomState mState;

    In order to drive this view, I've added an update method that is manually called on a timer from MainActivity, though this View could just as easily be built to use an event bus or other technique to drive the data. The key thing to notice is that the new desired state is passed to and saved by the View, and refreshDrawableState() is called.

    public void update( CustomState state ) {

        if( state != null )
            mState = state;

        if( CustomState.GO.equals( mState ) ) {
            setText( "GO" );
        } else if( CustomState.SLOW_DOWN.equals( mState ) ) {
            setText( "SLOW DOWN" );
        } else if( CustomState.STOP.equals( mState) ) {
            setText( "STOP" );
        }

        refreshDrawableState();
    }

    The final thing we need to do with our custom View is override onCreateDrawableState to add in our custom state attributes. We do this by saving the result of the super method with an incremented parameter passed to it, and then calling mergeDrawableStates with the result and our int array attribute to add our custom attribute to the active drawable states for our View.

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        if( mState == null )
            return super.onCreateDrawableState(extraSpace);

        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);

        if( CustomState.GO.equals( mState ) ) {
            mergeDrawableStates( drawableState, STATE_GO );
            return drawableState;
        } else if( CustomState.SLOW_DOWN.equals( mState ) ) {
            mergeDrawableStates( drawableState, STATE_SLOW_DOWN );
            return drawableState;
        } else if( CustomState.STOP.equals( mState) ) {
            mergeDrawableStates( drawableState, STATE_STOP );
            return drawableState;
        } else {
            return super.onCreateDrawableState(extraSpace);
        }
    }

    Now that that's all set, let's define a new drawable selector file to take advantage of our custom drawable states

<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:customapp="http://schemas.android.com/apk/res-auto">
    <item customapp:state_go="true" android:drawable="@android:color/holo_green_light" />
    <item customapp:state_slow_down="true" android:drawable="@color/yellow" />
    <item customapp:state_stop="true" android:drawable="@android:color/holo_red_light" />
    <item android:drawable="@android:color/white" />
</selector>

    Notice the custom attributes of state_go, state_slow_down and state_stop that match our attrs.xml file and our custom int arrays from our view. Android will automatically handle displaying the proper color for this state drawable now that we've laid out the groundwork for it. Finally we'll assign this drawable as a background for our custom View, and we'll see the color change as our states change.

    <com.ptrprograms.customdrawablestates.CustomDrawableTextView
        android:id="@+id/custom_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="40sp"
        android:gravity="center"
        android:text="@string/hello_world"
        android:background="@drawable/custom_state_background"/>




Saturday, December 20, 2014

Using the Android Toolbar Widget

    Introduced with Android Lollipop, the Toolbar is meant to be a replacement for the ActionBar when additional customization is required. In this tutorial I will go over some of the basic customizations for the Toolbar and how to customize it with some styles. All code for this tutorial can be found on GitHub.

    The first thing we're going to want to do is import the AppCompat library in build.gradle, as this'll provide the support version of the Toolbar back to Android API 7.

compile 'com.android.support:appcompat-v7:21.0.3'

    Once we have the library imported, we can add the Toolbar to our activity layout file. One of the biggest advantages to using the Toolbar widget is that you can place the view anywhere in your layout. For this example we'll just keep it at the top of the layout, like the standard ActionBar. One thing to note is that the Toolbar extends ViewGroup, so the layout file can also contain additional views if needed.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        style="@style/AppTheme.Toolbar" />

    <TextView
        android:id="@+id/content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"/>

</LinearLayout>

    Now that the activity layout is built, we need to adjust the theme used for the activity to exclude the default ActionBar, as it is not compatible with a Toolbar. This same theme can be used to customize some general features of the Toolbar, such as the background color, text color and overflow popup theme. The following styles will provide us with a green Toolbar and overflow dropdown, white text and white overflow button color.

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

        <!-- Used for Toolbar background color -->
        <item name="colorPrimary">#005500</item>
        <!-- Toolbar + Overflow menu text color -->
        <item name="android:textColorPrimary">#FFFFFF</item>
        <!-- Overflow menu button color -->
        <item name="android:textColorSecondary">#FFFFFF</item>

    </style>

    <style name="AppTheme.Toolbar" parent="Widget.AppCompat.Toolbar">
        <item name="android:background">?attr/colorPrimary</item>
        <item name="android:minHeight">?attr/actionBarSize</item>
        <item name="popupTheme">@style/AppTheme.ActionBar.Popup</item>
    </style>

    <!-- Overflow drop down style -->
    <style name="AppTheme.ActionBar.Popup" parent="Widget.AppCompat.Light.PopupMenu.Overflow">
        <item name="android:background">?attr/colorPrimary</item>
    </style>


    With the layout and styles applied, we can start applying the logic behind our Toolbar. To start, we declare the Toolbar and a custom view that will be added to the Toolbar, in this case an AnalogClock (mainly because it's really obvious/visible for this example), at the top of our activity

    private Toolbar mToolbar;
    private AnalogClock mCustomView;

    After the ContentView has been set to our layout file, we can initialize the Toolbar and call setSupportActionBar() to assign the Toolbar to our activity.

        mToolbar = (Toolbar) findViewById( R.id.toolbar );
        setSupportActionBar( mToolbar );

    The overflow menu is populated in the same way that you would populate the ActionBar, by inflating a menu xml file in onCreateOptionsMenu() and handling the actions in onOptionsItemSelected(). For this example we'll use the actions in the overflow menu to call methods for customizing the Toolbar.


    From this point, the rest of the Toolbar actions are fairly straight forward. The Toolbar title, subtitle and logo can be set with a few method calls from getSupportActionBar().

    private void showCustomTitleAndSubtitle() {
        getSupportActionBar().setTitle( "Custom Title");
        getSupportActionBar().setSubtitle( "subtitle" );
        getSupportActionBar().setDisplayShowTitleEnabled( true );
    }

    private void hideTitleAndSubtitle() {
        getSupportActionBar().setDisplayShowTitleEnabled( false );
    }

    private void showDefaultLogo() {
        getSupportActionBar().setLogo( R.drawable.ic_launcher );
        getSupportActionBar().setDisplayUseLogoEnabled( true );
    }

    private void hideLogo() {
        getSupportActionBar().setDisplayUseLogoEnabled( false );
    }


     Adding and removing a custom view to the Toolbar is equally straight forward. The biggest thing to remember is that we need to attempt to remove the custom view from our Toolbar first, otherwise the app will crash if we attempt to add the same view to the Toolbar more than once. Once we're certain that the custom view is not attached to our Toolbar, we can the view and our Toolbar will automatically update.

    private void showCustomView() {
        if( mCustomView == null )
            mCustomView = (AnalogClock) LayoutInflater.from( this ).inflate( R.layout.ab_clock, null );

        mToolbar.removeView( mCustomView );
        mToolbar.addView( mCustomView );
    }

    private void hideCustomView() {
        mToolbar.removeView( mCustomView );
        setSupportActionBar( mToolbar );
    }


    With that, we have a Toolbar that is easily customizable, able to be placed anywhere in our layout and easily styled to fit our app color scheme.

Tuesday, December 9, 2014

Building Data Driven Hierarchical Views

    One common scenario for applications is displaying a UI that is driven by event data. In this tutorial I will go over a very useful technique that I work with almost daily, which I learned from one of the incredibly talented engineers that taught me early on, for building multiple custom views that can be used in various layouts and updated by passing data to one container view per layout. All code for this tutorial can be found on GitHub.

    The first thing we need to do for this example is create an interface that will be used by all of our updateable views. This interface only has one method, update, which is used by our views in order to update their state.

public interface Updateable {
    public void update( Weather weather );
}

    Next we'll implement a simple data model to house all of our data that will drive the views

public class Weather {
    private int temperature;
    private int windSpeed;
    private WindDirection windDirection;
    private WeatherCondition condition;

    public Weather( int temperature, int windSpeed, WindDirection windDirection, WeatherCondition condition ) {
        this.temperature = temperature;
        this.windSpeed = windSpeed;
        this.windDirection = windDirection;
        this.condition = condition;

    }

    public void setTemperature( int temperature ) {
        this.temperature = temperature;
    }

    public int getTemperature() {
        return temperature;
    }
...

    where WindDirection and WeatherCondition simply are enums defined like so

public enum WeatherCondition {
    RAIN,
    SNOW,
    LIGHTNING,
    SUN,
    CLOUDY,
    FOG
}

    Now that we have our data models and interface set up, it's time to start creating the updateable views for our project. To start, we'll go over the standard child views, in this case modified TextView and ImageViews, and then we'll move on to the container view that triggers the state update in all of its child views. Our first example of an updateable view is WeatherImage, which simply implements our Updateable interface and extends ImageView.

public class WeatherImage extends ImageView implements Updateable

    In the update method that comes from the Updateable interface, we check the Weather object for data pertaining to the weather condition, and then display an image based on that data.

@Override
public void update( Weather weather ) {
    Log.e( "WeatherImage", "update!" );
    if( weather == null || weather.getWeatherCondition() == null )
        return;

    switch( weather.getWeatherCondition() ) {
        case CLOUDY: {
            setImageResource( R.drawable.cloudy );
            break;
        }
        case FOG: {
            setImageResource( R.drawable.fog );
            break;
        }
        case LIGHTNING: {
            setImageResource( R.drawable.lightning );
            break;
        }
        case RAIN: {
            setImageResource( R.drawable.rain );
            break;
        }
        case SNOW: {
            setImageResource( R.drawable.snow );
            break;
        }
        case SUN: {
            setImageResource( R.drawable.sun );
            break;
        }
    }
}

    We use this technique for children views for WindWeatherTextView, WeatherTextView and WeatherTemperatureTextView as well, though instead of extending an ImageView, we extend a TextView, as the class names imply.

    Where the magic really happens is in the container view for these updateable child views, UpdateableLinearLayout. Our container view implements Updateable as well, however instead of adjusting itself during update, it calls update on all of its child views.

public class UpdateableLinearLayout extends LinearLayout implements Updateable

@Override
public void update( Weather weather ) {
    Log.e("UpdateableLinearLayout", "Update!" );
    if( weather != null && mUpdateableViews != null && !mUpdateableViews.isEmpty() ) {
        for( Updateable view : mUpdateableViews ) {
            view.update( weather );
        }
    }
}

    mUpdateableViews is simply a list of Updateable objects, and it is populated in onFinishInflate() by looping and recursing through all of the children views under UpdateableLinearLayout and its children in order to create a comprehensive list of views in the current layout that implement Updateable

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    mUpdateableViews = findTopLevelUpdateables( this );
}

public List<Updateable> findTopLevelUpdateables( ViewGroup view ) {
    ArrayList<Updateable> results = new ArrayList<Updateable>();

    int childCount = view.getChildCount();
    for( int i = 0; i < childCount; i++ ) {
        results = findTopLevelUpdateables( view.getChildAt( i ), results );
    }
    return results;
}

protected ArrayList<Updateable> findTopLevelUpdateables( View view,
                                                        ArrayList<Updateable> results ) {

    if( ( view instanceof ViewGroup ) && !( view instanceof Updateable ) ) {
        ViewGroup viewGroup = (ViewGroup) view;
        int childCount = viewGroup.getChildCount();
        for( int i = 0; i < childCount; i++ ) {
            findTopLevelUpdateables( viewGroup.getChildAt( i ), results );
        }
    }

    Updateable result = ( view != null && view instanceof Updateable ) ? (Updateable) view : null;
    if( result != null ) {
        results.add( result );
    }
    results.trimToSize();
    return results;
}

    These same methods could easily be used for a RelativeLayout class, or any other ViewGroup that a developer would want to use for their event driven layout.

    Once our view classes are created, we can start to implement them in a custom layout file, as shown in activity_main.xml

<com.ptrprograms.eventdrivenhierarchicalviews.view.UpdateableLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherImage
        android:layout_width="match_parent"
        android:layout_height="300dp" />

    <com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp" />

    <com.ptrprograms.eventdrivenhierarchicalviews.view.WeatherTemperatureTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp" />

    <com.ptrprograms.eventdrivenhierarchicalviews.view.WindWeatherTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp" />

</com.ptrprograms.eventdrivenhierarchicalviews.view.UpdateableLinearLayout>

    As you can see, our UpdateableLinearLayout is our root ViewGroup for this layout, and all of its children are custom views that implement our Updateable interface. It is good to note that our UpdateableLinearLayout can support other children views that do not implement Updateable, though they will not automatically update when new data is present.

    For this example we implement this layout in our MainActivity and save a reference to the root UpdateableLinearLayout in onCreate.

setContentView(R.layout.activity_main);
mRootView = (UpdateableLinearLayout) findViewById( R.id.root );

    For this example, since we aren't using a live API in order to drive our data, I have created a method that simply goes through random Weather data every three seconds and calls update on the root view in order to simulate fresh data changing our view.

private void startSimulation() {
    mHandler = new Handler();
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            if( mCurrentItem >= mWeather.size() )
                mCurrentItem = 0;

            mRootView.update( mWeather.get( mCurrentItem++ ) );

            if( mHandler != null )
                mHandler.postDelayed( this, 3000 );
        }
    };

    runnable.run();
}

   And with that we now have custom views that change how they are displayed based on updating data, and can be reused in multiple places across our app by simply building multiple layout files. This technique has been invaluable in my every day development, and I hope it'll be useful for other Android developers as well.



Friday, November 28, 2014

Building a Widget to Silence a Phone

    One of the key parts of Android is the ability to customize your experience through components like widgets. In this tutorial I will go over creating a widget to silence a phone with one click, and in Lollipop (at least with the Nexus 4) it will place the phone into priority mode and silence the ringer, allowing the user to still get their notifications without a vibration. All code for this app can be found on GitHub.


    The first thing we're going to want to do is set up our manifest. Under the applications node, we'll add a receiver and service.

    <receiver android:name=".SilenceRingerWidget">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
            android:resource="@xml/silence_ringer_widget" />
    </receiver>

    <service android:name=".SilenceRingerService" />

    SilenceRingerService is responsible for the actual background work that our widget performs. The receiver is an extension of AppWidgetProvider, which is a special extension of BroadcastReceiver for app widgets. This allows it to be set to listen for APPWIDGET_UPDATE from the system. The meta-data provided is an appwidget-provider xml file that simply sets the layout for the widget, refresh rate and size.

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

    The only other additions we need to make to our manifest is to set the permissions that our widget will need in order to silence the phone.

    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />

    SilenceRingerWidget only has one method in this example, onUpdate, which is called when the widget is placed on the home screen and when the widget updates (which, in this case, won't happen since the updatePeriodMillis is set to 0 in silence_ringer_widget.xml). This method starts up the background service that builds the widget and silences the device.

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    context.startService(new Intent(context, SilenceRingerService.class));
}

    SilenceRingerService is where the bulk of this widget is handled. onStartCommand silences the phone, builds the remote view and then kills the service until the widget button is pressed again.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    silencePhone();
    RemoteViews views = buildViews();
    updateWidget( views );

    stopSelf();
    return START_NOT_STICKY;
}

    silencePhone() uses the AudioManager to silence the device, and because of Lollipop we also include a thread that starts up after a second and does the same action a second time, because Lollipop first places the device into Priority mode without silencing the device, then silences the ringer after the second setRingerMode call.

private void silencePhone() {
    setPriorityAndSilence();
    new Thread( new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep( 1000 );
            } catch( InterruptedException e ) {

            }

            setPriorityAndSilence();

        }
    } ).run();
}

private void setPriorityAndSilence() {
    AudioManager audioManager;
    audioManager = (AudioManager) getBaseContext().getSystemService( Context.AUDIO_SERVICE );
    audioManager.setRingerMode( AudioManager.RINGER_MODE_SILENT );
}

    The last part of onStartCommand that we need to look at has to do with RemoteViews. We simply grab the button from our layout file and set a pending intent to it that will start up SilenceRingerService again, and associate it with our widget.

private RemoteViews buildViews() {
    RemoteViews views = new RemoteViews( getPackageName(), R.layout.widget_silence_ringer );
    PendingIntent silenceIntent = PendingIntent.getService( this, 0, new Intent( this, SilenceRingerService.class ), 0 );
    views.setOnClickPendingIntent( R.id.button_silence, silenceIntent );
    return views;
}

private void updateWidget( RemoteViews views ) {
    AppWidgetManager manager = AppWidgetManager.getInstance( this );
    ComponentName widget = new ComponentName( this, SilenceRingerWidget.class );
    manager.updateAppWidget( widget, views );
}

    Now we have a simple widget put together that can be expanded on to fit other situations that may come up for you as a developer. Enjoy!