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!


Saturday, November 8, 2014

Building an Android Google Cast Sender App

This post was originally written for BinPress.

    With millions of Chromecasts sold and the Android TV ready to burst into living rooms, knowing how to build an app that supports casting should be in every Android developer's toolkit. This post will cover making an Android sender app to cast videos to a receiver app for displaying content on a television. All code for this example can be found on GitHub.

    For this tutorial, we'll use an unstyled receiver application created through the Google Cast Developer Console. While there is a generic default receiver application that can be used, this will provide an option to style the default receiver, or build your own if needed. Once the generic receiver application has been created, make note of the application ID, as we'll use that later. If you haven't already, you'll also want to register your casting device in order to run debug applications through Google Cast.

    Now, let's get to work on our Android application. The first thing that should be done is adding the libraries that we'll need to our build.gradle file.

compile 'com.android.support:appcompat-v7:20.0.0'
compile 'com.android.support:mediarouter-v7:19.0.+'
compile 'com.google.android.gms:play-services:6.1.11'

    Once build.gradle is all set, we can add the Internet permission and play services to AndroidManifest.xml

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

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

    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />

 ...

    The next thing that we'll need to do is add the media routing button to our activity's menu file. This action bar button handles all of the state logic needed for hiding or displaying the casting button, as well as coloring it when the app has connected to a casting device.

<item android:id="@+id/media_route_menu_item"
    android:title="Chromecast"
    app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
    app:showAsAction="always"
    />

    For our last housekeeping item, we'll need to add some values to strings.xml, such as the video URL, receiver application ID and general UI strings. You'll need to replace app_id with your application ID, and video_url with a video that you would want to play. For this example I picked up a link from Archive.org for the George Romero classic Night of the Living Dead.

<string name="app_name">Chromecast</string>
<string name="video_url">xxxxx</string>
<string name="app_id">xxxxx</string>
<string name="play_video">Play Video</string>
<string name="pause_video">Pause Video</string>
<string name="resume_video">Resume Video</string>
<string name="video_title">Paul\'s Chromecast Video Stream</string>
<string name="content_type_mp4">video/mp4</string> 

    Now that all of the preparation work is done, we can move on to working with our casting activity - MainActivity.java. To start, I've added a set of declarations to the top of the class to keep track of our casting components and general state logic.

private Button mButton;

private MediaRouter mMediaRouter;
private MediaRouteSelector mMediaRouteSelector;
private MediaRouter.Callback mMediaRouterCallback;
private CastDevice mSelectedDevice;
private GoogleApiClient mApiClient;
private RemoteMediaPlayer mRemoteMediaPlayer;
private Cast.Listener mCastClientListener;
private boolean mWaitingForReconnect = false;
private boolean mApplicationStarted = false;
private boolean mVideoIsLoaded;
private boolean mIsPlaying;

    The UI for this activity consists of a single button that will start a video if the app is connected to a casting device, or can pause/resume a video once it has been started, and the media router button in the action bar. In onCreate we simply initialize the click listener for the main control button and the media router, selector and callbacks.

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

    mButton = (Button) findViewById( R.id.button );
    mButton.setOnClickListener( new OnClickListener() {
        @Override
        public void onClick( View v ) {
           if( !mVideoIsLoaded )
               startVideo();
           else
               controlVideo();
        }
    });

    initMediaRouter();
}

private void initMediaRouter() {
    // Configure Cast device discovery
    mMediaRouter = MediaRouter.getInstance( getApplicationContext() );
    mMediaRouteSelector = new MediaRouteSelector.Builder()
            .addControlCategory( 
                CastMediaControlIntent.categoryForCast( getString( R.string.app_id ) ) )
            .build();
    mMediaRouterCallback = new MediaRouterCallback();
}

    Since the media router button exists in a menu file, we need to also override onCreateOptionsMenu and associate our selector with the MediaRouteActionProvider for the media router menu item.

@Override
public boolean onCreateOptionsMenu( Menu menu ) {
    super.onCreateOptionsMenu( menu );
    getMenuInflater().inflate( R.menu.main, menu );
    MenuItem mediaRouteMenuItem = menu.findItem( R.id.media_route_menu_item );
    MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat.getActionProvider( mediaRouteMenuItem );
    mediaRouteActionProvider.setRouteSelector( mMediaRouteSelector );
    return true;
}

    We'll also want to associate the media router callbacks with our router button in onResume.

@Override
protected void onResume() {
    super.onResume();
    // Start media router discovery
    mMediaRouter.addCallback( mMediaRouteSelector, mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN );
}

    mMediaRouterCallback is an extension of MediaRouter.Callback, and provides methods for performing actions when a route is selected or unselected.

private class MediaRouterCallback extends MediaRouter.Callback {

    @Override
    public void onRouteSelected(MediaRouter router, RouteInfo info) {
        initCastClientListener();
        initRemoteMediaPlayer();

        mSelectedDevice = CastDevice.getFromBundle( info.getExtras() );

        launchReceiver();
    }

    @Override
    public void onRouteUnselected( MediaRouter router, RouteInfo info ) {
        teardown();
        mSelectedDevice = null;
        mButton.setText( getString( R.string.play_video ) );
        mVideoIsLoaded = false;
    }
}

    At this point your application should display the media router button in the action bar if you are on a network with a casting device (assuming you have stub methods in where necessary).


    It will also allow you to choose a casting device to interact with.


    As you probably noticed, MediaRouterCallback does a few different things in onRouteSelected. First it initializes mCastClientListener, which has methods for checking when an application status has changed, volume has changed or the client has disconnected from the receiver application (I'll go over the teardown method later).

private void initCastClientListener() {
    mCastClientListener = new Cast.Listener() {
        @Override
        public void onApplicationStatusChanged() {
        }

        @Override
        public void onVolumeChanged() {
        }

        @Override
        public void onApplicationDisconnected( int statusCode ) {
            teardown();
        }
    };
}

    Next the RemoteMediaPlayer is initialized. This object controls playback with the receiver, as well as provides a lot of useful state and status information.

    private void initRemoteMediaPlayer() {
        mRemoteMediaPlayer = new RemoteMediaPlayer();
        mRemoteMediaPlayer.setOnStatusUpdatedListener( new RemoteMediaPlayer.OnStatusUpdatedListener() {
            @Override
            public void onStatusUpdated() {
                MediaStatus mediaStatus = mRemoteMediaPlayer.getMediaStatus();
                mIsPlaying = mediaStatus.getPlayerState() == MediaStatus.PLAYER_STATE_PLAYING;
            }
        });

        mRemoteMediaPlayer.setOnMetadataUpdatedListener( new RemoteMediaPlayer.OnMetadataUpdatedListener() {
            @Override
            public void onMetadataUpdated() {
            }
        });
    }

    Next, the selected device is stored and the receiver application is launched by using Google Play Services

private void launchReceiver() {
    Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions
            .builder( mSelectedDevice, mCastClientListener );

    ConnectionCallbacks mConnectionCallbacks = new ConnectionCallbacks();
    ConnectionFailedListener mConnectionFailedListener = new ConnectionFailedListener();
    mApiClient = new GoogleApiClient.Builder( this )
            .addApi( Cast.API, apiOptionsBuilder.build() )
            .addConnectionCallbacks( mConnectionCallbacks )
            .addOnConnectionFailedListener( mConnectionFailedListener )
            .build();

    mApiClient.connect();
}

    ConnectionCallbacks is another inner class that extends GoogleApiClient.ConnectionCallbacks. When the API client has connected from launchReceiver, the Cast API is used to actually launch the receiver specified by the application ID that we received earlier from the Google Cast Developers Console

private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks {

@Override
public void onConnected( Bundle hint ) {
    if( mWaitingForReconnect ) {
        mWaitingForReconnect = false;
        reconnectChannels( hint );
    } else {
        try {
            Cast.CastApi.launchApplication( mApiClient, getString( R.string.app_id ), false )
                    .setResultCallback( 
                         new ResultCallback<Cast.ApplicationConnectionResult>() {
                        @Override
                        public void onResult(
                           Cast.ApplicationConnectionResult applicationConnectionResult) {
                            Status status = applicationConnectionResult.getStatus();
                            if( status.isSuccess() ) {
                                //Values that can be useful for storing/logic
                                ApplicationMetadata applicationMetadata = 
                                       applicationConnectionResult.getApplicationMetadata();
                                String sessionId = 
                                       applicationConnectionResult.getSessionId();
                                String applicationStatus = 
                                       applicationConnectionResult.getApplicationStatus();
                                boolean wasLaunched = 
                                       applicationConnectionResult.getWasLaunched();

                                mApplicationStarted = true;
                                reconnectChannels( null );
                            }
                        }
                    }
            );
        } catch ( Exception e ) {

        }
    }
}

@Override
public void onConnectionSuspended(int i) {
    mWaitingForReconnect = true;
}

    At this point the application will start up our generic receiver application on the television.


    In onCreate we added a method call to startVideo. As you may have guessed, this method is used to start video playback on the receiver if it is connected. This method is also where we could start a local player if the app is not connected to a receiver.

private void startVideo() {
    MediaMetadata mediaMetadata = new MediaMetadata( MediaMetadata.MEDIA_TYPE_MOVIE );
    mediaMetadata.putString( MediaMetadata.KEY_TITLE, getString( R.string.video_title ) );

    MediaInfo mediaInfo = new MediaInfo.Builder( getString( R.string.video_url ) )
            .setContentType( getString( R.string.content_type_mp4 ) )
            .setStreamType( MediaInfo.STREAM_TYPE_BUFFERED )
            .setMetadata( mediaMetadata )
            .build();
    try {
        mRemoteMediaPlayer.load( mApiClient, mediaInfo, true )
                .setResultCallback( new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
                    @Override
                    public void onResult( RemoteMediaPlayer.MediaChannelResult mediaChannelResult ) {
                        if( mediaChannelResult.getStatus().isSuccess() ) {
                            mVideoIsLoaded = true;
                            mButton.setText( getString( R.string.pause_video ) );
                        }
                    }
                } );
    } catch( Exception e ) {
    }
} 




    When the video is casting to the receiver, the main UI button switches functionality to simply pausing or resuming the video by sending messages over the RemoteMediaControl

private void controlVideo() {
    if( mRemoteMediaPlayer == null || !mVideoIsLoaded )
        return;

    if( mIsPlaying ) {
        mRemoteMediaPlayer.pause( mApiClient );
        mButton.setText( getString( R.string.resume_video ) );
    } else {
        mRemoteMediaPlayer.play( mApiClient );
        mButton.setText( getString( R.string.pause_video ) );
    }
}



    The last few methods to cover generally have to do with some cleanup to keep the app from crashing and to properly close a connection to the receiver. When the connection fails or the app is no longer running, we will call the teardown method.

private void reconnectChannels( Bundle hint ) {
    if( ( hint != null ) && hint.getBoolean( Cast.EXTRA_APP_NO_LONGER_RUNNING ) ) {
        //Log.e( TAG, "App is no longer running" );
        teardown();
    } else {
        try {
            Cast.CastApi.setMessageReceivedCallbacks( mApiClient, mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer );
        } catch( IOException e ) {
            //Log.e( TAG, "Exception while creating media channel ", e );
        } catch( NullPointerException e ) {
            //Log.e( TAG, "Something wasn't reinitialized for reconnectChannels" );
        }
    }
}

private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener {
    @Override
    public void onConnectionFailed( ConnectionResult connectionResult ) {
        teardown();
    }
}

    ...where teardown simply closes the casting connection and disconnects from Play Services.

private void teardown() {
    if( mApiClient != null ) {
        if( mApplicationStarted ) {
            try {
                Cast.CastApi.stopApplication( mApiClient );
                if( mRemoteMediaPlayer != null ) {
                    Cast.CastApi.removeMessageReceivedCallbacks( mApiClient, mRemoteMediaPlayer.getNamespace() );
                    mRemoteMediaPlayer = null;
                }
            } catch( IOException e ) {
                //Log.e( TAG, "Exception while removing application " + e );
            }
            mApplicationStarted = false;
        }
        if( mApiClient.isConnected() )
            mApiClient.disconnect();
        mApiClient = null;
    }
    mSelectedDevice = null;
    mVideoIsLoaded = false;
}


    We also have to remember to remove callbacks from the MediaRouter that we added during onResume.

@Override
protected void onPause() {
    if ( isFinishing() ) {
        // End media router discovery
        mMediaRouter.removeCallback( mMediaRouterCallback );
    }
    super.onPause();
}

    And with that, we have the base functionality for a Google Cast capable Android sender app. If the source code on GitHub doesn't make sense, or you have other questions, please comment below!

Wednesday, October 29, 2014

Using Media Style Notifications with Media Session Controls

     Android Lollipop has brought with it many new features, one of which is lock screen notifications. Up until this point, lock screen media controls had to be implemented through the use of a RemoteView, and media control notifications had to be built with custom views. In this tutorial I will go over using the new MediaStyle for notifications and interacting with a MediaSession for controlling media playback states. All code for this tutorial can be found on GitHub.


    The very first thing that we're going to need to do is set the MEDIA_CONTENT_CONTROL permission in AndroidManifest.xml. This will allow us to use our lock screen notification to control media.

    <permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />

    For this example we're going to use a background service to controls our media and build/interact with the new notifications. Our MainActivity is going to be very straight forward and simply start our service with an action telling the service to build a playing state notification

    Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
    intent.setAction( MediaPlayerService.ACTION_PLAY );
    startService( intent );

    Next we're going to want to start fleshing out MediaPlayerService. At the top of the class we're going to define a set of strings that we will use to implement notification actions, and also define the objects that we'll use throughout the class.

    public static final String ACTION_PLAY = "action_play";
    public static final String ACTION_PAUSE = "action_pause";
    public static final String ACTION_REWIND = "action_rewind";
    public static final String ACTION_FAST_FORWARD = "action_fast_foward";
    public static final String ACTION_NEXT = "action_next";
    public static final String ACTION_PREVIOUS = "action_previous";
    public static final String ACTION_STOP = "action_stop";

    private MediaPlayer mMediaPlayer;
    private MediaSessionManager mManager;
    private MediaSession mSession;
    private MediaController mController;

   When the service receives an intent, it'll immediately go through onStartCommand. This method only does two simple things: if our objects have not been initialized, it'll call initMediaSession to set them up, and then the intent will be passed to handleIntent.

    initMediaSession initializes the objects that we defined earlier. MediaPlayer handles media playback (not used in this example, but in an actual app it would be), the MediaSessionManager helps maintain the MediaSession, MediaSession is used for keeping track of media states, and the MediaController handles transitioning between media states and calling MediaPlayer methods.

    mMediaPlayer = new MediaPlayer();
    mManager = (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
    mSession = mManager.createSession("sample session");
    mController = MediaController.fromToken( mSession.getSessionToken() );

    The next thing initMediaSession does is add TransportControlCallbacks that we can call in order to control the MediaPlayer and display new notifications.

    mSession.addTransportControlsCallback( new MediaSession.TransportControlsCallback() {
        @Override
        public void onPlay() {
            super.onPlay();
            Log.e( "MediaPlayerService", "onPlay");
            buildNotification( generateAction( android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE ) );
        }

        @Override
        public void onPause() {
            super.onPause();
            Log.e( "MediaPlayerService", "onPause");
            buildNotification(generateAction(android.R.drawable.ic_media_play, "Play", ACTION_PLAY));
        }

        @Override
        public void onSkipToNext() {
            super.onSkipToNext();
            Log.e( "MediaPlayerService", "onSkipToNext");
            //Change media here
            buildNotification( generateAction( android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE ) );
        }

        @Override
        public void onSkipToPrevious() {
            super.onSkipToPrevious();
            Log.e( "MediaPlayerService", "onSkipToPrevious");
            //Change media here
            buildNotification( generateAction( android.R.drawable.ic_media_pause, "Pause", ACTION_PAUSE ) );
        }

        @Override
        public void onFastForward() {
            super.onFastForward();
            Log.e( "MediaPlayerService", "onFastForward");
            //Manipulate current media here
        }

        @Override
        public void onRewind() {
            super.onRewind();
            Log.e( "MediaPlayerService", "onRewind");
            //Manipulate current media here
        }

        @Override
        public void onStop() {
            super.onStop();
            Log.e( "MediaPlayerService", "onStop");
            //Stop media player here
            NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.cancel( 1 );
            Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
            stopService( intent );
        }

        @Override
        public void onSeekTo(long pos) {
            super.onSeekTo(pos);
        }

        @Override
        public void onSetRating(Rating rating) {
            super.onSetRating(rating);
        }
    });

    These control methods are called in handleIntent. When an intent is received by the service, handleIntent extracts the action associated with that intent to determine which transport control method should be called

    private void handleIntent( Intent intent ) {
        if( intent == null || intent.getAction() == null )
            return;

        String action = intent.getAction();

        if( action.equalsIgnoreCase( ACTION_PLAY ) ) {
            mController.getTransportControls().play();
        } else if( action.equalsIgnoreCase( ACTION_PAUSE ) ) {
            mController.getTransportControls().pause();
        } else if( action.equalsIgnoreCase( ACTION_FAST_FORWARD ) ) {
            mController.getTransportControls().fastForward();
        } else if( action.equalsIgnoreCase( ACTION_REWIND ) ) {
            mController.getTransportControls().rewind();
        } else if( action.equalsIgnoreCase( ACTION_PREVIOUS ) ) {
            mController.getTransportControls().skipToPrevious();
        } else if( action.equalsIgnoreCase( ACTION_NEXT ) ) {
            mController.getTransportControls().skipToNext();
        } else if( action.equalsIgnoreCase( ACTION_STOP ) ) {
            mController.getTransportControls().stop();
        }
    }

    As can be seen in the TransportControlCallbacks, we call buildNotification with another method called generateAction. Actions are used by the MediaStyle notification to populate the buttons at the bottom of the notification and launch intents when pressed. generateAction simply accepts the icon that the notification will use for that button, a title for the button and a string that will be used as the action identifier in handleIntent. With this information, generateAction is able to construct a pendingIntent before assigning it to an action that is then returned.

    private Notification.Action generateAction( int icon, String title, String intentAction ) {
        Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
        intent.setAction( intentAction );
        PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 1, intent, 0);
        return new Notification.Action.Builder( icon, title, pendingIntent ).build();
    }

    buildNotification is where we actually implement the MediaStyle notification. The first thing we do is create a new Notification.MediaStyle style, then start building the rest of the notification through using Notification.Builder. This notification simply contains a pendingIntent that would stop our media when the notification is dismissed, a title, content text, a small icon and the style.

private void buildNotification( Notification.Action action ) {
    Notification.MediaStyle style = new Notification.MediaStyle();

    Intent intent = new Intent( getApplicationContext(), MediaPlayerService.class );
    intent.setAction( ACTION_STOP );
    PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 1, intent, 0);
    Notification.Builder builder = new Notification.Builder( this )
            .setSmallIcon( R.drawable.ic_launcher )
            .setContentTitle( "Media Title" )
            .setContentText( "Media Artist" )
            .setDeleteIntent( pendingIntent )
            .setStyle( style );

    Next we can add our buttons to the notification through the use of Builder.addAction. It should be noted that MediaStyle notifications only support up to five different actions. Each of our actions will be generated through the generateAction method described above.

    builder.addAction( generateAction( android.R.drawable.ic_media_previous, "Previous", ACTION_PREVIOUS ) );
    builder.addAction( generateAction( android.R.drawable.ic_media_rew, "Rewind", ACTION_REWIND ) );
    builder.addAction( action );
    builder.addAction( generateAction( android.R.drawable.ic_media_ff, "Fast Foward", ACTION_FAST_FORWARD ) );
    builder.addAction( generateAction( android.R.drawable.ic_media_next, "Next", ACTION_NEXT ) );

    The last thing this method does is actually construct the notification and post it to the system

    NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
    notificationManager.notify( 1, builder.build() );

    Now that the notification and sessions are implementing and working, the final thing we need to take into account is releasing our MediaSession once the media player and service have been stopped.

    @Override
    public boolean onUnbind(Intent intent) {
        mSession.release();
        return super.onUnbind(intent);
    }

    And with that we now have a fully working MediaStyle notification on our lock screen and in the notification drawer that takes advantage of MediaSession for playback control. Enjoy!

Friday, October 17, 2014

A Guide to the Android Wear Message API

I originally wrote this post for the good folks over at Binpress.com

While Android Wear provides a lot of great features, such as notifications, out of the box, its real potential lies in the ability to create native apps that communicate with a paired smartphone. Luckily, Android Wear comes with a few new APIs to help make this communication a lot smoother for developers: the Message API, Node API and DataLayer API. Each of these has their own unique purpose; the Message API is designed for "fire and forget it" types of messages, the DataLayer API supports syncing data between a smartphone and a wearable, and the Node API handles events related to local and connected device nodes.

For this tutorial I'll cover using the Message API to send data from a smartphone to an Android Wear device. We'll start a watch activity by using a WearableListenerService and display text in a ListView through the MessageApi.MessageListener interface, though all three APIs are used in a similar way. All sample code for this tutorial can be found on GitHub.

To start, create a project in Android Studio that has both a mobile and wear module:


Once your project and modules are created, you can start building the mobile side of your application. The sample project contains simple ListView, EditText and Button views that allow the user to enter text and, when the Button is pressed, display it in the ListView while also sending that text to the wearable.


To start, open AndroidManifest.xml in the mobile module and add a meta-data tag for Google Play Services

<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />

Next, we can focus on the mobile MainActivity class. The first thing we need to do is connect the GoogleApiClient and implement the GoogleApiClient.ConnectionCallbacks interface:

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

    if( mApiClient != null && !( mApiClient.isConnected() || mApiClient.isConnecting() ) )
        mApiClient.connect();
}

When the GoogleApiClient has finished connecting, onConnected will be called. When it is, we'll send a message through the Message API to a WearableListenerService (implemented later in this tutorial) running on the Wear in order to start our watch activity.

@Override
public void onConnected(Bundle bundle) {
    sendMessage( START_ACTIVITY, "" );
}

...where START_ACTIVITY is a string value that must begin with /, denoting a message path.

private static final String START_ACTIVITY = "/start_activity";

Aside from our initial message, we also want a message to be sent when our 'send' button is pressed. This is done through a simple OnClickListener that uses a different path value of /message and sends the text present in the EditText at the bottom of the screen.

mSendButton.setOnClickListener( new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        String text = mEditText.getText().toString();
        if (!TextUtils.isEmpty(text)) {
            mAdapter.add(text);
            mAdapter.notifyDataSetChanged();

            sendMessage(WEAR_MESSAGE_PATH, text);
        }
    }
});

The sendMessage method is where we really start to see how we interact with the new Android Wear APIs. In order to send a message to the Wear, we must first use the Node API to get a list of nodes connected to the device. Once we have this list, we send a message to each node using MessageAPI with references to the GoogleApiClient, node ID, the path used to determine the type of message being sent, and the message payload as a byte array. A MessageApi.SendMessageResult is returned that can be used to determine if a message was successfully sent to the current node. Once the message is sent, we clear the EditText view to allow the user to enter more text.

private void sendMessage( final String path, final String text ) {
    new Thread( new Runnable() {
        @Override
        public void run() {
            NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes( mApiClient ).await();
            for(Node node : nodes.getNodes()) {
                MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(
                        mApiClient, node.getId(), path, text.getBytes() ).await();
            }

            runOnUiThread( new Runnable() {
                @Override
                public void run() {
                    mEditText.setText( "" );
                }
            });
        }
    }).start();
}


Finally in onDestroy, we need to disconnect from GoogleApiClient.

@Override
protected void onDestroy() {
    super.onDestroy();
    mApiClient.disconnect();
}


Now that the mobile MainActivity is ready, it's time to move into the wear module. First we'll want to create a class that extends from WearableListenerService, which is a service that implements the three Wear communication APIs. For our purposes we only need to override the onMessageReceived method. In this method we check the path that was sent over the MessageApi to determine the type of message, and then, if appropriate, fire off an intent to our wear MainActivity to bring our application to the forefront.

private static final String START_ACTIVITY = "/start_activity";

@Override
public void onMessageReceived(MessageEvent messageEvent) {
    if( messageEvent.getPath().equalsIgnoreCase( START_ACTIVITY ) ) {
        Intent intent = new Intent( this, MainActivity.class );
        intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
        startActivity( intent );
    } else {
        super.onMessageReceived(messageEvent);
    }
}

Once we're done implementing our service, we need to add it into the wear AndroidManifest.xml file.

<service android:name=".WearMessageListenerService">
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
    </intent-filter>
</service>

As mentioned earlier, there are two ways to receive a message on Android Wear. The next way is implementing MessageApi.MessageListener in our Wear MainActivity. To start, we need to connect GoogleApiClient in the same way as our mobile application. In the onConnected callback, we add our wearable listener to the api client

@Override
public void onConnected(Bundle bundle) {
    Wearable.MessageApi.addListener( mApiClient, this );
}


Next, we simply override the onMessageReceived method from the MessageListener interface, check the path value to determine if we want to take action, and then add the payload text to our adapter to display the text in our wear ListView

@Override
public void onMessageReceived( final MessageEvent messageEvent ) {
    runOnUiThread( new Runnable() {
        @Override
        public void run() {
            if( messageEvent.getPath().equalsIgnoreCase( WEAR_MESSAGE_PATH ) ) {
                mAdapter.add(new String(messageEvent.getData()));
                mAdapter.notifyDataSetChanged();
            }
        }
    });
}

This will display the text from our mobile app on the wearable, as seen below (the screen shot was taken from a Moto 360, hence the cut off portion at the bottom).

And with that we now have a working communication channel from a smartphone to a paired Android Wear device. I hope this tutorial helps you build your own applications. Good luck!

Wednesday, September 10, 2014

Getting Started with the Gamepad Controller for Android TV

I originally wrote this tutorial for the good folks over at Binpress.
With the release of Android TV, game developers will have a new platform to contend with. Luckily, there's only one major change that needs to be considered when building your game for Android TV: implementing the new controller.
In this post, I'll go over using the new controller in the context of a simple (i.e. it works, but isn't polished or something you'd spend hours playing) Asteroids-esque OpenGL game. All source code for this example can be found on GitHub.

The first thing to take note of when building a game for Android TV is that the application tag in AndroidManifest.xml has an attribute called 'isGame', which is set to true. This is what places your application into the Games section of the menu selection screen.
  1. <application
  2.     android:allowBackup="true"
  3.     android:icon="@drawable/ic_launcher"
  4.     android:label="@string/app_name"
  5.     android:isGame="true"
  6.     android:theme="@style/AppTheme" >

Next we need to handle input as it happens. We do this by overriding two methods in our main Activity class - dispatchGenericMotionEvent and dispatchKeyEvent. In this example I simply let our controller (MVC controller, not physical controller :)) know that an event has happened, and it handles interpreting what it is and doing something with it.
  1. MainActivity.java:
  2. @Override
  3. public boolean dispatchGenericMotionEvent(MotionEvent event) {
  4.     return mGame.handleMotionEvent(event);
  5. }
  6. @Override
  7. public boolean dispatchKeyEvent(KeyEvent event) {
  8.     return mGame.handleKeyEvent(event);
  9. }
  10. GameView.java:
  11. public boolean handleMotionEvent( MotionEvent motionEvent ) {
  12.     if ( mShip != null ) {
  13.         mShip.getController().setDeviceId( motionEvent.getDeviceId() );
  14.         mShip.getController().handleMotionEvent( motionEvent );
  15.         return true;
  16.     }
  17.     return false;
  18. }
  19. public boolean handleKeyEvent( KeyEvent keyEvent ) {
  20.     if ( mShip != null ) {
  21.         mShip.getController().setDeviceId( keyEvent.getDeviceId() );
  22.         mShip.getController().handleKeyEvent( keyEvent );
  23.         return true;
  24.     }
  25.     return false;
  26. }
As you can see in GameView.java, we check to see if our ship object has been initialized, and if it has we get the controller associated with that ship. (Since this is a single player game, there's no logic to assigning one of multiple controllers to a ship). Finally, the device ID and event are passed to the game controller utility class.
The GamepadController.java class is where things get a bit more interesting. When the controller is initialized, we create a pair of two-dimensional arrays for storing the state of the buttons and joystick positions on the controller. Each button in mButtonState[][] is associated with its own index and keeps track of state during the current and previous frame. Each joystick in mJoystickPositions[][] also has its own index, but the values stored are current positions on the X and Y axes.
  1. // The buttons on the game pad.
  2. public static final int BUTTON_A = 0;
  3. public static final int BUTTON_B = 1;
  4. public static final int BUTTON_X = 2;
  5. public static final int BUTTON_Y = 3;
  6. public static final int BUTTON_R1 = 4;
  7. public static final int BUTTON_R2 = 5;
  8. public static final int BUTTON_L1 = 6;
  9. public static final int BUTTON_L2 = 7;
  10. public static final int BUTTON_COUNT = 8;
  11. // The axes for joystick movement.
  12. public static final int AXIS_X = 0;
  13. public static final int AXIS_Y = 1;
  14. public static final int AXIS_COUNT = 2;
  15. // Game pads usually have 2 joysticks.
  16. public static final int JOYSTICK_1 = 0;
  17. public static final int JOYSTICK_2 = 1;
  18. public static final int JOYSTICK_COUNT = 2;
  19. // Keep track of button states for the current and previous frames.
  20. protected static final int FRAME_INDEX_CURRENT = 0;
  21. protected static final int FRAME_INDEX_PREVIOUS = 1;
  22. protected static final int FRAME_INDEX_COUNT = 2;
  23. // Positions of the two joysticks.
  24. private final float mJoystickPositions[][];
  25. // The button states for the current and previous frames.
  26. private final boolean mButtonState[][];
  27. public GamepadController() {
  28.     mButtonState = new boolean[BUTTON_COUNT][FRAME_INDEX_COUNT];
  29.     mJoystickPositions = new float[JOYSTICK_COUNT][AXIS_COUNT];
  30.     resetState();//initializes values
  31. }
With the controller arrays initialized, we can get back to handling our input.handleMotionEvent has two parts: getting the input from the first joystick on the controller and the second joystick (if there is any input).
  1. public void handleMotionEvent(MotionEvent motionEvent) {
  2.     //Joystick 1:
  3.     mJoystickPositions[JOYSTICK_1][AXIS_X] = motionEvent.getAxisValue(MotionEvent.AXIS_X);
  4.     mJoystickPositions[JOYSTICK_1][AXIS_Y] = motionEvent.getAxisValue(MotionEvent.AXIS_Y);
  5.     //Joystick 2:
  6.     mJoystickPositions[JOYSTICK_2][AXIS_X] = motionEvent.getAxisValue(MotionEvent.AXIS_Z);
  7.     mJoystickPositions[JOYSTICK_2][AXIS_Y] = motionEvent.getAxisValue(MotionEvent.AXIS_RZ);
  8. }
As you can see, the axes are mapped as X and Y for the left joystick, and Z and RZ for the second. The values from those axes are read and stored in our array for reading by our model classes.
We also save key pressed events by determining if the key action is "down," and saving that value in our button state array based on which button is being pressed.
  1. public void handleKeyEvent(KeyEvent keyEvent) {
  2.     boolean keyIsDown = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
  3.     if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_A) {
  4.         mButtonState[BUTTON_A][FRAME_INDEX_CURRENT] = keyIsDown;
  5.     } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_B) {
  6.         mButtonState[BUTTON_B][FRAME_INDEX_CURRENT] = keyIsDown;
  7.     } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_X) {
  8.         mButtonState[BUTTON_X][FRAME_INDEX_CURRENT] = keyIsDown;
  9.     } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_Y) {
  10.         mButtonState[BUTTON_Y][FRAME_INDEX_CURRENT] = keyIsDown;
  11.     } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_R1 ) {
  12.         mButtonState[BUTTON_R1][FRAME_INDEX_CURRENT] = keyIsDown;
  13.     } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_R2 ) {
  14.         mButtonState[BUTTON_R2][FRAME_INDEX_CURRENT] = keyIsDown;
  15.     } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_L1 ) {
  16.         mButtonState[BUTTON_L1][FRAME_INDEX_CURRENT] = keyIsDown;
  17.     } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BUTTON_L2 ) {
  18.         mButtonState[BUTTON_L2][FRAME_INDEX_CURRENT] = keyIsDown;
  19.     }
  20. }
Once the gamepad controller state is stored, we can access that from our object that is associated with that controller - in this case our unstoppable hero, the USS Triangle ship. The game calls the ship's update method every frame, so that's where we're going to handle updating the ship position by reading in joystick values, and wether or not the ship should fire a bullet.
  1. public void update(float delta ) {
  2.     if ( !updateStatus( delta ) ) {
  3.         return;
  4.     }
  5.     updateShipPosition( delta );
  6.     handleKeyInput( delta );
  7. }
updateStatus simply checks to see if the ship is spawned, and -- if it is -- it calls the methods to update position or handle key pressed events. In updateShipPosition, we grab the X and Y axis positions from the controller, and then use those to determine the magnitude of the joystick movement via the Pythagorean theorem (wrapped up in our Utils class methodvector2DLength).
  1. float newHeadingX = mController.getJoystickPosition(GamepadController.JOYSTICK_1,
  2.             GamepadController.AXIS_X);
  3. float newHeadingY = mController.getJoystickPosition(GamepadController.JOYSTICK_1,
  4.             GamepadController.AXIS_Y);
  5. float magnitude = Utils.vector2DLength(newHeadingX, newHeadingY);
Once we have our headings and magnitude, we can see if that magnitude is significant enough to be used (in this case, over 10 percent movement from the center), then store the new headings in our ship's values and set the velocity for our ship. If the magnitude for the joystick is somehow greater than a set max value, then we divide our velocities by the magnitude to provide a max velocity along each axis.
  1. if (magnitude > GamepadController.JOYSTICK_MOVEMENT_THRESHOLD) {
  2.         //Get the heading divided by how much the joystick is being used
  3.         mHeadingX = newHeadingX / magnitude;
  4.         mHeadingY = -newHeadingY / magnitude;
  5.         setVelocity( newHeadingX, -newHeadingY );
  6.         if (magnitude > 1.0f) {
  7.             //Sets a cap velocity
  8.             mVelocityX /= magnitude;
  9.             mVelocityY /= magnitude;
  10.         }
  11.     }
Handling button pressed events is a bit more straight forward in this example - we check to see if a cool down timer has run up for firing a bullet, and we do the following if it has. We check to see if the X button is currently in a down position, reset the cool down, calculate aim based on the direction the ship is facing and create a bullet object at the ship's position.
  1. private void handleKeyInput( float delta ) {
  2.     if( mFireTimer > 0 ) {
  3.         mFireTimer--;
  4.         return;
  5.     }
  6.     if ( mController.isButtonDown( GamepadController.BUTTON_X ) && mFireTimer == 0 ) {
  7.         mFireTimer = FIRE_REFRESH_TIMER;
  8.         calculateAimDirection();
  9.         fireGun();
  10.     }
  11. }
Aim can be calculated with joystick 2 in the same way our heading is calculated for the ship, or it can simply set the aiming value as the heading if the second joystick isn't used for aiming (which is how it's used in the sample project).
Other than handling the new key events, the rest of the game is built in a standard way. This example throws together a quick OpenGL project, but the same ideas can be applied however your game is built. Hopefully this'll help some of you get rolling on getting your games onto the new Android TV platform!