Friday, August 29, 2014

Getting Started with Android L Animations

    Among many other new features, Android L has brought a slew of new animations that can be added to your apps. In this post I will go over some of those new animations and how to implement them into an app. All of the code for this post can be found on GitHub.

Ripples and Accents


    Android now supports a handful of predefined style attributes that relate to specific parts of the app, such as the status bar or navigation bar. This new system gives us an easy way to introduce two very simple, yet useful, animations: ripples and accented UI widgets. Ripples are the preferred way of giving users feedback from their actions on UI elements, and their color can be set by applying a color value to the colorControlHighlight attribute in your app theme.

<item name="android:colorControlHighlight">#0000AA</item>



    Just as easily, UI widgets, such as checkboxes, can be set using the colorAccent property in your styles folder to give them a color that fits with your app theme without having to use a set of different  images and state drawables.

<item name="android:colorAccent">#00FF00</item>


Circular Reveal


    One common task in Android is changing the visibility of an element on the screen. Using a ValueAnimator, developers now have an extra option for making this task stand out a bit more: the circular reveal. This can be used by using the ViewAnimationUtil.createCircularReveal method, and then changing the visibility of your view at the appropriate time using an animation listener. Here are the two very similar methods used for making a view visible or gone. Note that the reveal method has a duration set, causing it to take a second to finish the animation, whereas the hide animation is a lot faster. The getX() and getY() methods simply return the center points of the image along its given X and Y axes, and getRadius() simply returns the width of the view.

private void hideImageCircular() {
    int x = getX();
    int y = getY();
    int radius = getRadius();

    ValueAnimator anim =
            ViewAnimationUtils.createCircularReveal(mImageView, x, y, radius, 0);

    anim.addListener(new AnimatorListenerAdapter() {

        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            mImageView.setVisibility( View.INVISIBLE );
        }
    });

    anim.start();
}

private void revealImageCircular() {
    int x = getX();
    int y = getY();
    int radius = getRadius();

    ValueAnimator anim =
            ViewAnimationUtils.createCircularReveal(mImageView, x, y, 0, radius);

    anim.setDuration( 1000 );
    anim.addListener( new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animation) {
            super.onAnimationStart(animation);
            mImageView.setVisibility( View.VISIBLE );
        }
    });

    anim.start();
}


Activity Transitions


    In addition to the previous animations, Android L added a few stock activity transition animations to help make apps look and feel more polished - explode, slide and fade. In order to use these transitions, you must request the content transitions feature in both your entering and exiting activities before setting your content view.

getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

    While setting transitions through a style is supported, for this case I will go over how to set transitions in the activities. Simply use the getWindow().setExitTransition( Transition ) and getWindow().setEnterTransition( Transition ) methods to tell your activities how to act when opening and closing. In the case of the explode transition, the code for going from MainActivity and our ListFragment to the second activity will look like this:

ListFragment:
getActivity().getWindow().setExitTransition( new Explode() );
intent = new Intent( getActivity(), ExplodeAnimationActivity.class );
startActivity( intent );

ExplodeAnimationActivity:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
    getWindow().setEnterTransition( new Explode() );
    getWindow().setExitTransition( new Explode() );
    setContentView(R.layout.activity_explode_animation);
}

@Override
public void onBackPressed() {
    super.onBackPressed();
    finishAfterTransition();
}

    Notice the onBackPressed method - this is important because it lets the OS know that the animation should finish before it hides the calling second activity. Using these few simple lines of code, we have our three types of simple activity transitions:
Explode Transition

Slide Transition

Fade Transition

    One useful tool that I am not using, but definitely deserves mentioning, is the Transition.TransitionListener. Applying this to your enter or exit transition allows for performing tasks at different points in the animation lifecycle, giving you more control over how your app should act. This listener should also be removed in onDestroy in order to prevent a leak.

getWindow().getEnterTransition().addListener( new Transition.TransitionListener {
    @Override
    public void onTransitionStart(Transition transition) {

    }

    @Override
    public void onTransitionEnd(Transition transition) {

    }

    @Override
    public void onTransitionCancel(Transition transition) {

    }

    @Override
    public void onTransitionPause(Transition transition) {

    }

    @Override
    public void onTransitionResume(Transition transition) {

    }
});


Shared Element Activity Transitions


    On top of the standard activity transitions, it is now possible to support animating shared elements across two activities. The first thing that has to be done to support this is setting your activity to use content transitions and allow overlapping transitions. This can be done through the style applied to your activities:

<item name="android:windowContentTransitions">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowExitTransitionOverlap">true</item>

    While shared element transitions can be applied in Java, for this example I will use styles to apply the shared element transitions:

<item name="android:windowSharedElementEnterTransition">@transition/changebounds</item>
<item name="android:windowSharedElementExitTransition">@transition/changebounds</item>

    where the changebounds transition is defined as an xml file in our resources folder. Notice that I added two additional properties, duration and interpolator, in order to make the animation a little more fun.

changebounds.xml:
<changeBounds
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:interpolator/bounce" />

    The next step is to make sure you have a comparable view type (in this case we'll use ImageView) in both of your activity layouts, and that they both must have the same viewName attribute value. In our first activity, the view that will have the shared element will look like this:

<ImageView
    android:id="@+id/image"
    android:viewName="image"
    android:layout_width="match_parent"
    android:layout_height="250dp" />

    and the view in the second activity will look like this:

<ImageView
    android:id="@+id/image"
    android:layout_alignParentBottom="true"
    android:viewName="image"
    android:layout_width="match_parent"
    android:layout_height="250dp" />

    Now that all of the xml setup is ready to go, we can start coding in the transition from our activities. In the first activity, we set the transition group for the parent container view to false, and then take the drawable from the imageview that we want to share and compress and convert it into a byte stream that can be stored in an intent bundle. We then create ActivityOptions with a scene transition animation for our imageview and start the next activity.

Intent intent = new Intent( this, SharedElementSecondAnimationActivity.class );

((ViewGroup) mImageView.getParent()).setTransitionGroup( false );

ByteArrayOutputStream stream = new ByteArrayOutputStream();
( (BitmapDrawable) mImageView.getDrawable() ).getBitmap().compress(Bitmap.CompressFormat.PNG, 100, stream);
intent.putExtra( "image", stream.toByteArray() );

ActivityOptions options;

try {
    options = ActivityOptions.makeSceneTransitionAnimation( this, mImageView, "image" );
} catch( NullPointerException e ) {
    Log.e( "SharedElementAnimationChangeBoundsActivity", "Did you set your ViewNames in the layout file?" );
    return;
}

if( options == null ) {
    Log.e("sharedelementanimation", "Options is null. Something broke. Good luck!");
} else {
    startActivity(intent, options.toBundle());
}

    In the second activity, we read the byte array from the intent bundle and decode it into a bitmap. We then apply that bitmap to an imageview. This second activity also has the onBackPressed method with finishAfterTransition in order to apply the transition when hitting the back button.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_shared_element_second_animation);
    mImageView = (ImageView) findViewById( R.id.image );

    byte[] byteArray = getIntent().getByteArrayExtra("image");
    Bitmap bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
    mImageView.setImageBitmap(bitmap);
}

@Override
public void onBackPressed() {
    super.onBackPressed();
    finishAfterTransition();
}

    And with that we now have a shared element transition that moves our shared element to its new location in the second activity, and has an extra bounce effect:


    These few examples of animations only begin to scratch the surface of what's possible in Android L, not to mention the scene transitions that were introduced in Kit Kat. I hope this tutorial helps other developers get started with the new possibilities, as applying animations makes for beautiful apps that are also fun to work on.

Friday, August 22, 2014

Programmatically Coloring Drawables

    With the introduction of Android L, developers now have an easy way to change the color of drawables to match the theme of their app. Despite this, many developers will need to support earlier Android versions in their apps, so I have decided to share a technique for changing drawable asset colors using Picasso for both local and remote assets. The source code for this example can be found on GitHub.

    The key to this technique is using the Transform interface provided in the Picasso library, which provides a transform method to take a bitmap and return an altered bitmap for use in the Picasso image loading method. This interface can be useful for things like changing the size of an image, or in this case changing the image color. The ColorTransformation class that we will use in this example implements the Transform interface and can either accept a color value in its constructor, or have a color set from a resource value.

public ColorTransformation( int color ) {
    setColor( color );
}

public void setColor( int color ) {
    this.color = color;
}

public void setColorFromRes( Context context, int colorResId ) {
    setColor( context.getResources().getColor( colorResId ) );
}

    The transform method is where the magic happens. We provide a source bitmap (which is passed to the transform by Picasso automagically in its drawable building process) and return an altered version. In this example we create a new drawable from the source bitmap in order to get the dimensions of the image, then create a new bitmap and canvas for that bitmap. We then set the bounds for the drawable and apply a color filter using the SRC_IN version of the PorterDuff algorithm - applying the color on top of the visible portions of the drawable ( if you haven't read up on PorterDuff, it's definitely a worth while and interesting topic ) and then draw the altered drawable onto the canvas of the new bitmap before returning it. We also recycle the source bitmap once it is no longer needed per best practices for managing memory with Bitmaps in 2.3 and lower Android devices.

@Override
public Bitmap transform(Bitmap source) {
    if( color == 0 ) {
        return source;
    }

    BitmapDrawable drawable = new BitmapDrawable(Resources.getSystem(), source );
    Bitmap result = Bitmap.createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888 );
    Canvas canvas = new Canvas( result );
    drawable.setBounds( 0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight() );
    drawable.setColorFilter( color, PorterDuff.Mode.SRC_IN );
    drawable.draw(canvas);
    drawable.setColorFilter(null);
    drawable.setCallback(null);

    if( result != source ) {
        source.recycle();
    }

    return result;
}

    One additional point to notice in the ColorTransformation class is that there is a key method provided. This is used by Picasso for retaining transformed drawables in memory, allowing for quicker loading when a drawable is transformed and displayed multiple times, such as in a listview.

@Override
public String key() {
    return "DrawableColor:" + color;
}

    Now that the transform class is built, we can use it to load drawables in our app with specific colors. In MainActivity I have four image views: one that uses an unaltered local drawable, one that loads in and transforms a local drawable, one that loads in an unaltered asset through a URL and one that loads in the same asset via URL, but applies a transform to change its color. The latter three imageviews have their drawables populated like so:

Picasso.with( this )
        .load( R.drawable.ic_star )
        .transform( new ColorTransformation(getResources().getColor( R.color.local_drawable_color ) ) )
        .into( mDrawableTransformedImage );

Picasso.with( this )
        .load( getString( R.string.image_url ) )
        .into( mDrawableUrlImage );

Picasso.with( this )
        .load( getString( R.string.image_url ) )
        .transform( new ColorTransformation( getResources().getColor( R.color.remote_image_color ) ) )
        .into( mDrawableUrlTransformedImage );

    As you can see, the ColorTransform class is created for each drawable and a color is passed to it, and Picasso handles the rest by calling Transform and dealing with memory management/caching behind the scenes. The last part of MainActivity uses a Picasso Target to load a drawable into an object that has callbacks associated with it. This technique is used for colorizing and placing an image into the action bar, as the Picasso.into method does not work with the action bar icon directly. First we declare our Target and the actions it should perform with its drawable once it has been loaded or if it fails:

private Target ActionBarIconTarget = new Target()
{
    @Override
    public void onBitmapLoaded( Bitmap bitmap, Picasso.LoadedFrom from )
    {
        getSupportActionBar().setIcon( new BitmapDrawable( getResources(), bitmap ) );
    }

    @Override
    public void onBitmapFailed( Drawable errorDrawable )
    {
        getSupportActionBar().setIcon( R.drawable.ic_launcher );
    }

    @Override
    public void onPrepareLoad( Drawable placeHolderDrawable )
    {

    }
};

    Once we have our target and what it should do defined, we can load our altered drawable into the target so that the image can be loaded into the action bar.

Picasso.with( this )
    .load( R.drawable.ic_star )
    .transform( new ColorTransformation( getResources().getColor( R.color.action_bar_icon_color ) ) )
    .into( ActionBarIconTarget );

    And with that we now have an easy way to load recolored drawables across our app without generating new assets in each color. This is particularly useful when applying techniques like my last post on using Gradle to reskin the same code base for multiple applications. Below I have provided an image from the demo app showing the same icons in various colors after being run through this code. Hopefully it will help you other developers out there save a fair bit of time.

The unaltered URL drawable may be a bit hard to see, but it's originally a black icon of the Android logo

Sunday, August 3, 2014

Build Variations using Gradle and Resource Folders

    The Android framework allows for customization of apps based on different attributes of a physical device through the structuring of an apps resource folders, letting a developer change things such as layouts, integers and string values in order to fit their apps needs for a given device. Using this, developers can easily change configurations between phones, small and large tablets, and devices in landscape or portrait mode. With the introduction of Gradle and Android Studio, the ability to use folder structures has been expanded to allow for overriding resource folders and values per different product flavors and build types, such as debug and release. In this tutorial I will go over using Gradle to reskin a base application using product flavors. All source code can be found on GitHub.

    To start, let's touch on using resource values in an application. If you append a quantifying value such as -xxhdpi, -land or -sw600dp to a resource folder, then the resource values in those folders will override the values in their main counterpart folder for devices that fit the appended values criteria. This means that if a device is in landscape mode, it will use resources from values-land over those in values if available, and if the device is at least a small tablet, then items in values-sw600dp will take precedence.

    The differences here can be seen in the following example screen shot, where one line of text is changed depending on the default value (for portrait devices) and the values-land string value. Without any code changes, the device will choose which string value to use based on the orientation of the phone.
default orientation string - portrait
landscape orientation string on a Nexus 4

    Now that we have a base understanding of resource hierarchies in the Android framework, let's move on to the overall goal of this tutorial - reskinning an app using product flavors. This is done almost entirely in the build.gradle file, though there will be some additions in the project folder structure. A default Android application in Android Studio will already come with the build type node populated for debug and release.

buildTypes {
    release {
        //This is where the signing cert would be referenced for the release build
    }
    debug {
        //This is where the signing cert would be referenced for the debug build
    }
}

    Under the buildTypes node, we can define the main categories of product flavors that we will want to use. When generating a project, one type of each of these flavors will be used, as well as one of the build types.

flavorDimensions "color", "number"

    Once the types of flavor dimensions are defined, we can start declaring types of product flavors and assigning them to a flavor dimension.

productFlavors {
    blue {
        applicationId "com.ptrprograms.gradleproductflavorsblue"
        flavorDimension "color"
    }
    green {
        applicationId "com.ptrprograms.gradleproductflavorsgreen"
        flavorDimension "color"
    }
    orange {
        applicationId "com.ptrprograms.gradleproductflavorsorange"
        flavorDimension "color"
    }
    purple {
        applicationId "com.ptrprograms.gradleproductflavorspurple"
        flavorDimension "color"
    }
    red {
        applicationId "com.ptrprograms.gradleproductflavorsred"
        flavorDimension "color"
    }
    yellow {
        applicationId "com.ptrprograms.gradleproductflavorsyellow"
        flavorDimension "color"
    }

    one {
        flavorDimension "number"
    }

    two {
        flavorDimension "number"
    }
}

    Note that each of the color flavors has an applicationId value set. This allows us to differentiate between our products, getting multiple reskinned applications onto the Play Store with the same code base. With the product flavors defined, we can now use the Build Variants window in Android Studio to pick a mix of build types and product flavors to install or build.


    Similar to overriding values within one application by device size or orientation, we can also override values by product flavor. By creating directories in the same folder as 'main' titled after their product flavors, the resources in those directories will take precedence over the main resource values. In this example I override all of the launcher icons, the background color resource and a few strings matching the product flavor in each of the color flavors. Likewise I override a string in the number product flavor directories.



    With these values filled out, we can quickly generate similar apps with cosmetic changes, as can be seen from the blue version of the app at the beginning of this post and this green product running on a Nexus 7.

    And with that we are able to generate many different applications using a similar code base with different values per product for colors, images, text and any other available type of resource, so that multiple apps can be built quickly with just some planning and organization.

Saturday, July 19, 2014

Creating a Media App for AndroidTV

    One of the newest technologies released by Google this year at I/O is the AndroidTV, a platform that takes what they learned from the Chromecast and Google TV and makes it even better by building on the Android OS. For this tutorial I will go over putting together a basic media app for the AndroidTV in order to help other developers get their own apps and content out for this platform in order to push for its success. While the base Android project from Android Studio does let you create a media app, it provides far more than the basics and needs a lot of clean up, so I went ahead and did that clean up and will go over the different methods used in order to hopefully provide some clarity. While I happened to have received my AndroidTV ADT-1 developer box at I/O by luckily sitting in on the session, other developers can request a kit through Google's site. The source code for this project can be found on GitHub.

    The first thing we need to do in a new Android project is set up our gradle file. AndroidTV is currently using the Android-L SDK and has dependencies on the new support library for leanback, recyclerview, and appcompat, then third party libraries used are GSON and Picasso

android {
    compileSdkVersion 'android-L'
    buildToolsVersion "20.0.0"
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:recyclerview-v7:+'
    compile 'com.android.support:leanback-v17:+'
    compile 'com.android.support:appcompat-v7:+'
    compile 'com.squareup.picasso:picasso:2.3.2'
    compile 'com.google.code.gson:gson:2.2.4'
}

    Next we need to set up our manifest to use the Internet permission and know what activities we will use in the project. The main two activities for this project are the MainActivity and PlayerActivity, though I also include Bonus.SlothActivity, which I added to this project to play around with creating a custom activity and decided to leave in the app to show that you can use any activity on this platform (I'll go over this at the end, since I had fun with it). Note that the category the the MainActivity intent-filter is leanback_launcher, as this is what allows the app icon to show up on the television.

<uses-permission android:name="android.permission.INTERNET" />
<uses-feature
    android:name="android.hardware.touchscreen"
    android:required="false" />

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
        android:name=".Activity.MainActivity"
        android:label="@string/app_name"
        android:logo="@drawable/giraffes"
        android:screenOrientation="landscape" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".Activity.PlayerActivity"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
    <activity android:name=".Activity.DetailsActivity" />

    <activity
        android:name=".Bonus.SlothActivity"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />

</application>

    Under our styles file, we need to set AppTheme to extends the Leanback theme, otherwise the app will not load, however it's still not certain if this will be the case when Android L and AndroidTV officially launch.

<style name="AppTheme" parent="@style/Theme.Leanback">

    Now that the general configuration is set, we should be able to start moving into the actual java code. MainActivity is simply a base activity with a layout that contains a fragment, so we'll put together the layout for now and then jump into how MainFragment works.

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}


<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_browse_fragment"
    android:name="com.ptrprograms.androidtvmediaplayer.Fragment.MainFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    tools:deviceIds="tv"
    tools:ignore="MergeRootFrame" />


    MainFragment.java extends the new BrowseFragment provided in the support library, which gives us a lot of the functionality we need for a media app such as the fast lane (side navigation bar) and view for the rows of content. The only member variable that we'll need here is a List of Movie objects, where Movie is just a standard serialized model object containing information about a movie like the title, category, description and URLs to associated images and the video.

public class Movie implements Serializable {
    private String title;
    private String description;
    private String studio;
    private String videoUrl;
    private String category;
    private String cardImageUrl;
    private String backgroundImageUrl;
...

    The first thing that MainFragment does in onActivityCreated is load the data for the list of Movie objects. In this case I'm simply using a locally stored JSON file and converting it into the list using GSON, but this method can be used however works for your project to load in data, be it locally or from an online API.

private void loadData() {
    String json = Utils.loadJSONFromResource( getActivity(), R.raw.movies );
    Gson gson = new Gson();
    Type collection = new TypeToken<ArrayList<Movie>>(){}.getType();
    mMovies = gson.fromJson( json, collection );
}

   Next we can go ahead and initialize the base UI by setting the title in the top right of the screen, enabling category headers, setting the action the back button on the remote should take (opening the fast lane or returning to the home screen), setting fragment colors and background. I should mention that I only set the background once in MainFragment, however one set of polish that you can add to your app is changing the background whenever a new item is selected in order to match the selected item. Also, there is a method built into BrowseFragment called setBadgeDrawable which lets you pass a drawable resource id to display an image in the top right corner rather than plain text.

private void initUI() {
    setTitle( getString( R.string.browse_title ) );
    setHeadersState( HEADERS_ENABLED );

    //Back button goes to the fast lane, rather than home screen
    setHeadersTransitionOnBackEnabled( true );

    setBrandColor( getResources().getColor( R.color.fastlane_background ) );
    setSearchAffordanceColor( getResources().getColor( R.color.search_button_color ) );
    setBackground();
}

Example of the fast lane with background color, search circle with background color and title text. I stuck with a black/grey theme, which may not be the best for examples, but was far easier on my eyes without having to be great at design :)
    After our colors and base UI are set, we can start populated data in order to get the categories in the fast lane and cards that you see in the image above. We do this by creating a new adapter of ListRowPresenters to hold the rows, then create sets of ListRows containing our cards to put into that adapter (think linked list of linked lists) before assigning the adapter of ListRows to the fragment, similar to using setAdapter in a ListFragment. For this method each row will consist of cards representing Movie objects from the same category - comedy, horror, or action.

private void loadRows() {

    ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter( new ListRowPresenter() );
    CardPresenter cardPresenter = new CardPresenter();

    List<String> categories = getCategories();
    if( categories == null || categories.isEmpty() )
        return;

    for( String category : categories ) {
        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter( cardPresenter );
        for( Movie movie : mMovies ) {
            if( category.equalsIgnoreCase( movie.getCategory() ) )
                listRowAdapter.add( movie );
        }
        if( listRowAdapter.size() > 0 ) {
            HeaderItem header = new HeaderItem( rowsAdapter.size() - 1, category, null );
            rowsAdapter.add( new ListRow( header, listRowAdapter ) );
        }
    }

    setupPreferences( rowsAdapter );
    setAdapter( rowsAdapter );

}

    As you can see in the second loop, for each movie we check to see if the movie category matches the current row category, and if it does we add it to our adapter of CardPresenter objects. The CardPresenter extends the Presenter class to bind our movie object to a programmatically created view and also implements the ViewHolder pattern. The main methods for the card presenter are shown here and are from CardPresenter.java

static class ViewHolder extends Presenter.ViewHolder {
    private ImageCardView mCardView;
    private PicassoImageCardViewTarget mImageCardViewTarget;

    public ViewHolder( View view ) {
        super( view );
        mCardView = (ImageCardView) view;
        mImageCardViewTarget = new PicassoImageCardViewTarget( mCardView );
    }

    public ImageCardView getCardView() {
        return mCardView;
    }

    protected void updateCardViewImage( Context context, String link ) {
        Picasso.with( context )
                .load(link)
                .resize( mCardView.getResources().getInteger( R.integer.card_presenter_width ), mCardView.getResources().getInteger( R.integer.card_presenter_height ) )
                .centerCrop()
                .error( R.drawable.default_background )
                .into( mImageCardViewTarget );
    }
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
    ImageCardView cardView = new ImageCardView( parent.getContext() );
    cardView.setFocusable( true );
    cardView.setFocusableInTouchMode( true );
    return new ViewHolder(cardView);
}

@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
    Movie movie = (Movie) item;

    if ( !TextUtils.isEmpty( movie.getCardImageUrl() ) ) {
        ((ViewHolder) viewHolder).mCardView.setTitleText( movie.getTitle() );
        ((ViewHolder) viewHolder).mCardView.setContentText( movie.getStudio() );
        ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(
                ( (ViewHolder) viewHolder ).mCardView.getContext().getResources().getInteger( R.integer.card_presenter_width ),
                ( (ViewHolder) viewHolder ).mCardView.getContext().getResources().getInteger( R.integer.card_presenter_height ) );
        ( (ViewHolder) viewHolder ).updateCardViewImage( ( (ViewHolder) viewHolder ).getCardView().getContext(), movie.getCardImageUrl() );
    }
}

Switching between rows using the fast lane
Example of the cards full view in a row with their category header
    Returning to our loadRows method in MainFragment.java, we have a method called setupPreferences. All this does is low another row at the end of our rows adapter that contains a PreferenceCardPresenter (similar to the CardPresenter class above, but greatly simplified to just display text in a square). I'm only adding one preference here as an example.

private void setupPreferences( ArrayObjectAdapter adapter ) {

    HeaderItem gridHeader = new HeaderItem( adapter.size(), "Preferences", null );
    PreferenceCardPresenter mGridPresenter = new PreferenceCardPresenter();
    ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter( mGridPresenter );
    gridRowAdapter.add( getResources().getString( R.string.sloth ) );
    adapter.add( new ListRow( gridHeader, gridRowAdapter ) );

}


    The final thing that we need to do once MainActivity/MainFragment are created is initialize our event listeners. The search button in BrowseFragment has a special method for this called setOnSearchClickedListener, however I will leave implementing search for another post. The other event listener that needs to be set is the BrowseFragment's setOnItemClickedListener like so

private void setupEventListeners() {
    setOnItemClickedListener( getDefaultItemClickedListener() );
    setOnSearchClickedListener( new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Toast.makeText(getActivity(), "Implement your own in-app search", Toast.LENGTH_LONG).show();
        }
    });
}

    where the default item clicked listener checks to see if the data item is a Movie object, and if it is then it packages it into an intent to launch the DetailsActivity which simply houses VideoDetailsFragment. If it is a string, then we know it belongs to our Preference row (though we could just as easily have created a special Preference object to populate that row) and we launch the SlothActivity that I will show later.

protected OnItemClickedListener getDefaultItemClickedListener() {
    return new OnItemClickedListener() {
        @Override
        public void onItemClicked( Object item, Row row ) {
            if( item instanceof Movie ) {
                Movie movie = (Movie) item;
                Intent intent = new Intent( getActivity(), DetailsActivity.class );
                intent.putExtra( VideoDetailsFragment.EXTRA_MOVIE, movie );
                startActivity( intent );
            } else if( item instanceof String ) {
                if( ((String) item).equalsIgnoreCase( getString( R.string.sloth ) ) ) {
                    Intent intent = new Intent( getActivity(), SlothActivity.class );
                    startActivity( intent );
                }
            }
        }
    };
}

    The VideoDetailsFragment extends the new DetailsFragment class and provides a quick overview of a media object with options for the user to select. The first thing we do in this fragment is retrieve the Movie object from the intent that started the DetailsActivity and set up the background for the activity using Picasso from the Movie's background URL property

private void initBackground() {
    BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
    backgroundManager.attach(getActivity().getWindow());
    mBackgroundTarget = new PicassoBackgroundManagerTarget( backgroundManager );

    mMetrics = new DisplayMetrics();
    getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);

    if( mSelectedMovie != null && !TextUtils.isEmpty( mSelectedMovie.getBackgroundImageUrl() ) ) {
        try {
            updateBackground(new URI(mSelectedMovie.getBackgroundImageUrl()));
        } catch (URISyntaxException e) { }
    }
}

protected void updateBackground(URI uri) {
    if( uri.toString() == null ) {
        try {
            uri = new URI("");
        } catch( URISyntaxException e ) {}
    }

    Picasso.with(getActivity())
            .load( uri.toString() )
            .error( getResources().getDrawable( R.drawable.default_background ) )
            .resize( mMetrics.widthPixels, mMetrics.heightPixels )
            .into( mBackgroundTarget );
}

    Next we use an AsyncTask to create the DetailsOverviewRow and assign the selected Movie object to it, populate information and set up our actions (the buttons at the bottom of the details card) to do their jobs. In this case I only have one action, and it sends an intent to the PlayerActivity in order to play the selected item.

@Override
protected DetailsOverviewRow doInBackground( Movie... movies ) {
    mSelectedMovie = movies[0];
    DetailsOverviewRow row = null;
    try {
        row = new DetailsOverviewRow( mSelectedMovie );
        Bitmap poster = Picasso.with( getActivity() )
                .load( mSelectedMovie.getCardImageUrl() )
                .resize(Utils.dpToPx( getActivity().getResources().getInteger( R.integer.detail_thumbnail_square_size ), getActivity().getApplicationContext() ),
                        Utils.dpToPx( getActivity().getResources().getInteger( R.integer.detail_thumbnail_square_size ), getActivity().getApplicationContext() ) )
                .centerCrop()
                .get();
        row.setImageBitmap( getActivity(), poster );
    } catch ( IOException e ) {
        getActivity().finish();
        return null;
    } catch( NullPointerException e ) {
        getActivity().finish();
        return null;
    }

    row.addAction( new Action( ACTION_WATCH, getResources().getString(
            R.string.watch ), getResources().getString( R.string.watch_subtext) ) );

    return row;
}

@Override
protected void onPostExecute(DetailsOverviewRow detailRow) {
    if( detailRow == null )
        return;

    ClassPresenterSelector ps = new ClassPresenterSelector();
    DetailsOverviewRowPresenter dorPresenter =
            new DetailsOverviewRowPresenter( new DetailsDescriptionPresenter() );
    // set detail background and style
    dorPresenter.setBackgroundColor( getResources().getColor( R.color.detail_background ) );
    dorPresenter.setStyleLarge( true );
    dorPresenter.setOnActionClickedListener( new OnActionClickedListener() {
        @Override
        public void onActionClicked( Action action ) {
            if (action.getId() == ACTION_WATCH ) {
                Intent intent = new Intent( getActivity(), PlayerActivity.class );
                intent.putExtra( EXTRA_MOVIE, mSelectedMovie );
                intent.putExtra( EXTRA_SHOULD_AUTO_START, true );
                startActivity( intent );
            }
        }
    });

    ps.addClassPresenter( DetailsOverviewRow.class, dorPresenter );
    ps.addClassPresenter( ListRow.class,
            new ListRowPresenter() );


    ArrayObjectAdapter adapter = new ArrayObjectAdapter( ps );
    adapter.add( detailRow );
    loadRelatedMedia( adapter );
    setAdapter( adapter );
}



    Notice the loadRelatedMedia method in onPostExecute. This method is used to (you guessed it) load related media onto the details screen. The only thing I'm doing in this method is extracting all of the movies of the same category as the selected movie from the Movies List and displaying them using the CardPresenter presenter from MainFragment, though more appropriate related movies can be gathered or media apps that have that logic in place.

private void loadRelatedMedia( ArrayObjectAdapter adapter ) {

    String json = Utils.loadJSONFromResource( getActivity(), R.raw.movies );
    Gson gson = new Gson();
    Type collection = new TypeToken<ArrayList<Movie>>(){}.getType();
    List<Movie> movies = gson.fromJson( json, collection );
    List<Movie> related = new ArrayList<Movie>();
    for( Movie movie : movies ) {
        if( movie.getCategory().equals( mSelectedMovie.getCategory() ) ) {
            related.add( movie );
        }
    }

    if( related.isEmpty() )
        return;

    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter( new CardPresenter() );
    for( Movie movie : related ) {
        listRowAdapter.add( movie );
    }

    HeaderItem header = new HeaderItem( 0, "Related", null );
    adapter.add( new ListRow( header, listRowAdapter ) );
}



    The final important part to a base media project is the actual video player. In this case all of that logic is handled by PlayerActivity.java (which I took from the Android sample app, rather than writing my own, though I do have a post on making a native video player from a few months ago that isn't as great as the Android sample player) and there isn't anything too out of the norm for a video player in this class. The most important part of this video player is the addition of a onKeyDown method that listens for input from the remote control dpad for controlling playback:

@Override
public boolean onKeyDown( int keyCode, KeyEvent event ) {
    int currentPos = 0;
    int delta = mDuration / getResources().getInteger( R.integer.scrub_segment_divisor );
    if ( delta < getResources().getInteger( R.integer.min_scrub_time ) )
        delta = getResources().getInteger( R.integer.min_scrub_time );

    if ( mControllers.getVisibility() != View.VISIBLE ) {
        mControllers.setVisibility( View.VISIBLE );
    }
    switch ( keyCode ) {
        case KeyEvent.KEYCODE_DPAD_CENTER:
            return true;
        case KeyEvent.KEYCODE_DPAD_DOWN:
            return true;
        case KeyEvent.KEYCODE_DPAD_LEFT:
            currentPos = mVideoView.getCurrentPosition();
            currentPos -= delta;
            if (currentPos > 0)
                play(currentPos);
            return true;
        case KeyEvent.KEYCODE_DPAD_RIGHT:
            currentPos = mVideoView.getCurrentPosition();
            currentPos += delta;
            if( currentPos < mDuration )
                play( currentPos );
            return true;
        case KeyEvent.KEYCODE_DPAD_UP:
            return true;
    }

    return super.onKeyDown(keyCode, event);
}

Let there be lips!
    And with that you can have a pretty solid media app for the AndroidTV. Now that the technical part is done, if you're not interested in some random ridiculousness, you're alright to stop reading now :)

    Still with me? Alright, cool. So earlier I added a preference square item to MainFragment, and when selected it launches an intent to SlothActivity. I wanted to play around with AndroidTV a bit and see if you can throw in a custom activity, and it turns out you can. With a bit of stylized changes to this project from Bakhtiyor Khodjayev from a couple years ago that originally displayed animated snowflakes falling, I threw together a great custom activity of a sloth putting in a hard days work. Sadly recording though ADB doesn't capture audio, and I removed the MP4 from the project because I didn't want the hassle of being busted for more copyright infringement than I'm (probably, who knows?) doing, but the original audio to this was Led Zeppelin's Boogie with Stu. Anywho, for your viewing pleasure, the custom SlothActivity


Sunday, July 6, 2014

Android Wear - Creating a Custom Watch Face with Options

    For this tutorial I will go over creating a custom watch face for the Android Wear. There are rumors that Google will be releasing a watch face builder class, but right now no one is sure when/if that will happen. The point of this tutorial is to be a central place for information on building custom watch faces with the current system, as the information is currently scattered across Reddit and Google+ posts.

    This demo app allows the user to select a school from a list and then displays the time in the corner with the school logo below it. The settings activity for selecting the school is not a requirement for the watch face, as you can choose to only display one type of face, but I included it to show an additional feature. I also removed the mobile activity launcher, as it's not a necessity for the app. The code for this app is available on GitHub.
Watch face for Fresno State
    The first thing that needs to be done is to open up the mobile and wear AndroidManifest.xml files and add the permissions that will be needed for selecting a watch face:

<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

    In the wear AndroidManifest.xml file, add the following properties to the application tag:

<application
    android:allowBackup="true"
    android:icon="@drawable/bulldog_wallpaper"
    android:label="@string/app_name"
    android:theme="@android:style/Theme.DeviceDefault" >

    and the following properties to the activity tag for the watch face activity. Note the intent-filter and meta-data tags in the activity section, as those are required for determining if the activity should be selectable as a watch face. I use a general icon for the preview here, but generally a screen shot of the actual watch face (like above) should be used:

<activity
    android:name=".WatchFaceActivity"
    android:label="@string/app_name"
    android:enabled="true"
    android:taskAffinity=""
    android:allowEmbedded="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="com.google.android.clockwork.home.category.HOME_BACKGROUND" />
    </intent-filter>
    <meta-data
        android:name="com.google.android.clockwork.home.preview"
        android:resource="@drawable/bulldog_wallpaper"/>
</activity>

Preview icon for the watch face

    In order to have the list of schools in the application area, we need to have an intent-filter added to the SettingsActivity activity tag:

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

    Once the manifest is put together, we can go ahead and fill in SettingsActivity to display a list of schools and save the selected school in a shared preference. In this example I use an object called SchoolObj that simply loads a list of school names and codes for organizing data:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate( savedInstanceState );
    setContentView( R.layout.activity_settings );
    final SchoolObj schools = new SchoolObj();
    ListView listView = (ListView) findViewById( R.id.list );
    ArrayAdapter<String> adapter = new ArrayAdapter( this, android.R.layout.simple_list_item_1, schools.schoolList );
    listView.setAdapter( adapter );
    listView.setOnItemClickListener( new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            SharedPreferences pref = getSharedPreferences( WatchFaceActivity.SHARED_PREFERENCE, Context.MODE_PRIVATE );
            SharedPreferences.Editor editor = pref.edit();
            editor.putString( SHARED_PREFERENCE_SCHOOL, schools.schoolCodeList.get( position ) );
            editor.commit();
            finish();
        }
    });
}

List of schools for customizing the watch face
    The final part to this application is the actual watch face activity. In onCreate() we can take a reference to the containing view, the TextClock object and the ImageView where the school logo will be stored. When onResume() is called we to see if SharedPreferences contains a key for a selected school and then set the color of the TextClock, ImageView drawable and background color for the watch face. If the user has just installed the application and has not selected a watch face, we set a default face.

@Override
protected void onResume() {
    super.onResume();
    SharedPreferences pref = getSharedPreferences( SHARED_PREFERENCE, Context.MODE_PRIVATE );

    if( pref.contains( SettingsActivity.SHARED_PREFERENCE_SCHOOL ) ) {
        String schoolCode = pref.getString( SettingsActivity.SHARED_PREFERENCE_SCHOOL, "" );
        loadSchoolWatchFace( schoolCode );
    } else {
        mBackground.setImageResource( R.drawable.bulldog_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
        mContainer.setBackgroundColor(getResources().getColor( android.R.color.white ) );
    }
}

    It is important that we set these properties every time that the activity resumes, as we must set the watch face to a "blank state" when the watch is put into a power conserving off state and onPause() is called

@Override
protected void onPause() {
    super.onPause();
    mBackground.setImageDrawable( null );
    mClock.setTextColor( getResources().getColor( android.R.color.white ) );
    mContainer.setBackgroundColor( getResources().getColor( android.R.color.black ) );
}

Watch Blank State
    The loadSchoolWatchFace( schoolCode ) method simply checks the school code in a set of if/else statements to set the proper image and colors, just like the default else portion of onResume()

private void loadSchoolWatchFace( String schoolCode ) {
    if( "cuboulder".equals( schoolCode ) ) {
        mBackground.setImageResource( R.drawable.cuboulder_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_orange_light));
    } else if( "fsu".equals( schoolCode ) ) {
        mBackground.setImageResource( R.drawable.floridastate_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
    } else if( "ucsc".equals( schoolCode ) ) {
        mBackground.setImageResource( R.drawable.bananaslugs_logo );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_orange_light));
    } else if( "berkeley".equals( schoolCode ) ) {
        mBackground.setImageResource( R.drawable.berkeley_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_orange_light ) );
    } else {
        //Default to Fresno
        mBackground.setImageResource( R.drawable.bulldog_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
    }

    mContainer.setBackgroundColor(getResources().getColor(android.R.color.white));
}
Florida State University watch face
UC Santa Cruz Banana Slugs watch face

    And that's all there is to it. The important parts to add are the manifest intent-filter and metadata items, and onResume()/onPause() for the watch face, then the rest adds functionality to the app. This can also work with an analog watch face and other views, so there's a lot more possibilities for developers to add some great features.


Sunday, June 29, 2014

Introduction to Official Android Wear Development Tutorial

    In this tutorial, I will go over writing a program for Android Wear physical devices. This app will allow the user to select a time interval on the Wear, and then set off a vibration on the user's wrist when the timer goes off before repeating. The goal of this app is to hopefully assist drivers on long road trips to stay alert (an idea that I had while driving back along I80 from San Francisco to Denver) and has been tested on the Samsung Gear Live physical device (which is where all of the screen shots have come from). The source code for this app is available on GitHub.

    For those interested, the app is available on the Play Store for free and working now that Play Services 5.0 is available.

    Also, before I begin, I should also throw out a thank you to ReadWrite, as I ended up getting to I/O last minute with a registration code through a contest on their awesome site.


    The first step is to download and install the latest Android Studio with SDK packages, then follow this documentation for putting together the broiler plate code for the mobile and wear version of the app. For this tutorial I named the main activity for the Wear portion 'IterationActivity', as this will be the activity screen that is displayed when the user opens the app on their Wear device. In the broiler plate code as of the current release, this activity extends WatchActivity, which is not available in the SDK, so we can safely change this to a regular Activity class. Next we can create an IntentService called TimerService that handles all of the background operations. We'll get back to filling this out once the infrastructure is ready to go.

    In the Wear and Mobile (it's important to have it in both, or the wear portion will not unpackage onto the user's device. I learned this the hard way with a lot of frustration :) ) sections, open AndroidManifest.xml and add the vibrate permission:

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

    Then add the service tag for our TimerService.class in the Wear manifest:

<service android:name=".Services.TimerService" />

    Next, under the Mobile section in build.gradle, add the following line to dependencies in order to package the Wear portion of the app with the Mobile:

wearApp project(':wear')

    Now that this app should compile and run directly on a Wear device, it's time to make it actually do something. The layout xml file used by IterativeActivity is a simple list view with some text above it, and should look like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Select an interval"
    />

    <ListView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />

</LinearLayout>

    Under rect_activity_iteration and round_activity_iteration layout files, we can replace the text view with a GridViewPager for containing our view:

<GridViewPager
    android:id="@+id/view_pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

    Back to the IterationActivity file, we can start adding content to onCreate(). The following block is added when the activity is started. If the SharedPreference for a selected duration is set, then the timer is started with that value, otherwise we continue with the block.

if( ( getIntent().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK ) == Intent.FLAG_ACTIVITY_NEW_TASK ) {
    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences( this );
    long duration = pref.getLong( SAVED_STATE_SELECTED_DURATION, 0 );
    if( duration != 0 ) {
        setupTimer( duration );
        finish();
        return;
    }
}

    If the activity hasn't been started before and there is no preselected interval time, then we can set up the array of times that the user can select from:

private void setupIterationArray() {
    int[] minutes = getResources().getIntArray( R.array.interation_minutes );
    for( int i = 0; i < minutes.length; i++ ) {
        IterationListItem item = new IterationListItem( getResources().getQuantityString( R.plurals.label_minutes, minutes[i], minutes[i] ),
                minutes[i]  * 60 * 1000 );
        mIterationTimes.add( item );
    }
}

    Next we set the content view to activity_iteration.xml:

setContentView(R.layout.activity_iteration);

    After the view is set, we can create the list of labels and times that the user can select from:

private void setupIterationArray() {
    int[] minutes = getResources().getIntArray( R.array.interation_minutes );
    for( int i = 0; i < minutes.length; i++ ) {
        IterationListItem item = new IterationListItem( getResources().getQuantityString( R.plurals.label_minutes, minutes[i], minutes[i] ),
                minutes[i]  * 60 * 1000 );
        mIterationTimes.add( item );
    }
}

    Finally under onCreate() we can initialize the list view and GoogleAPIClient. This will provide the activity for selecting durations in Wear:

private void initList() {
    mListView = (ListView) findViewById( R.id.list_view );
    mListView.setAdapter( new ArrayAdapter<IterationListItem>( this,
            android.R.layout.simple_list_item_1, mIterationTimes ) );
    mListView.setOnItemClickListener( this );
}

private void initGoogleApiClient() {
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addApi( Wearable.API )
            .addConnectionCallbacks( this )
            .addOnConnectionFailedListener( this )
            .build();
}


    When one of these list items is clicked, the value is saved in a shared preference and the duration is sent to the setupTimer method:

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences( this );
    SharedPreferences.Editor editor = pref.edit();
    editor.putLong( SAVED_STATE_SELECTED_DURATION, mIterationTimes.get( position ).getDuration() );
    editor.commit();
    setupTimer( mIterationTimes.get( position ).getDuration() );
}

    setupTimer accepts a duration in milliseconds and cancels any notifications that this app may have created before, then builds and launches a new timer notification and registers an alarm before closing the list activity:

private void setupTimer( long duration ) {
    NotificationManagerCompat notificationManager = NotificationManagerCompat.from( this );
    notificationManager.cancel( 1 );
    notificationManager.notify( 1, buildNotification( duration ) );
    registerAlarmManager( duration );
    finish();
}

    The buildNotification method is pretty standard and similar to the three other notification posts I wrote about when going over the developers preview. The important part to note here is the PendingIntent that goes to TimerService and closes down the timer:

private Notification buildNotification( long duration ) {
    Intent removeIntent = new Intent( ACTION_REMOVE_TIMER, null, this, TimerService.class );
    PendingIntent pendingRemoveIntent = PendingIntent.getService( this, 0, removeIntent, PendingIntent.FLAG_UPDATE_CURRENT );

    return new NotificationCompat.Builder( this )
            .setSmallIcon( R.drawable.ic_launcher )
            .setContentTitle( "Stay Awake" )
            .setContentText(TimeUtil.getTimeString( duration ) )
            .setUsesChronometer( true )
            .setLargeIcon( BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher ) )
            .setWhen( System.currentTimeMillis() + duration )
            .addAction( R.drawable.ic_launcher, "Remove Timer", pendingRemoveIntent )
            .setDeleteIntent( pendingRemoveIntent )
            .setLocalOnly( true )
            .build();
}


    The final thing to do in IterationActivity is register an alarm that fires off an intent to TimerService once the duration has finished:

private void registerAlarmManager( long duration ) {
    AlarmManager alarmManager = (AlarmManager) getSystemService( Context.ALARM_SERVICE );
    Intent intent = new Intent( ACTION_SHOW_ALARM, null, this, TimerService.class );
    PendingIntent pendingIntent = PendingIntent.getService( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT );

    long time = System.currentTimeMillis() + duration;
    alarmManager.setExact( AlarmManager.RTC_WAKEUP, time, pendingIntent );
}

    Now that IterationActivity is set, we can focus on TimerService.class. HandleIntent listens for two types of actions: starting an alarm and removing an alarm.

@Override
protected void onHandleIntent(Intent intent) {
    String action = intent.getAction();

    if( IterationActivity.ACTION_SHOW_ALARM.equals( action ) ) {
        showAlarm();
    } else if( IterationActivity.ACTION_REMOVE_TIMER.equals( action ) ) {
        removeAlarm();
    }
}

    showAlarm() is only called after the first timer has ended, so it simply vibrates the Wear on call and fires an intent back to IterationActivity with a flag stating that the next notification timer should be posted.  It should be noted that after a lot of time and frustration, I switched to using a pattern here rather than simply passing in the duration, as Wear won't stop the vibration on the watch unless the screen is active if a pattern isn't used.

private void showAlarm() {
    final Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
    long[] pattern = { 0, getResources().getInteger( R.integer.vibration_duration ) };
    v.vibrate( pattern, -1 );
    Intent intent = new Intent( this, IterationActivity.class );
    intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
    startActivity( intent );
}


    removeAlarm() cancels any notification timers that may be visible, any running alarm manager for our specified PendingIntent and sets the selected duration shared preference to 0.

private void removeAlarm() {
    NotificationManagerCompat notificationManager = NotificationManagerCompat.from( this );
    notificationManager.cancel( 1 );

    AlarmManager alarmManager = (AlarmManager) getSystemService( Context.ALARM_SERVICE );

    Intent intent = new Intent( IterationActivity.ACTION_SHOW_ALARM, null, this, TimerService.class );
    PendingIntent pendingIntent = PendingIntent.getService( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT );

    alarmManager.cancel( pendingIntent );

    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
    SharedPreferences.Editor editor = pref.edit();
    editor.putLong( IterationActivity.SAVED_STATE_SELECTED_DURATION, 0 );
    editor.apply();
}

    And with that we should have a fully functioning basic Android Wear application. Devices go on sale on the 7th, and I can honestly say from my few days using one it has been useful and will only get more useful as developers make additional apps.