Google TV

Migrating Existing Applications

Any Android application that runs with Android 3.0 (Honeycomb) or lower should run on Google TV, as long as it doesn't depend on features that are not supported on Google TV. This topic provides some guidelines for optimizing an existing mobile or tablet version of Android app for Google TV.

  1. Example: Panoramio
  2. Handling Navigation and Focus
  3. Making controls, fonts larger
  4. Using Left Navigation bar
  5. Providing High resolution Images
  6. Using Fragments
  7. Controlling slideshow with Media Keys
  8. Removing or working around unsupported features

Example: Panoramio

This example describes the steps for migrating Panoramio for Android app to Google TV. Panoramio for Android is a simple mobile client for Google's Panoramio service. It allows users to view pictures that were taken near their location, using both Google Maps and the radar view.

On a mobile device, the first screen of the app looks something like the screenshot on the left below. The main UI element is a MapView, a View that displays a Google Map using the Android APIs for Google Maps. A round icon marks the user's current location. Below the MapView is a Button. When the user clicks the button, the application searches the online Panoramio service for pictures near the current location. The results of the search are displayed in a new layout similar to the screenshot on the right below:

First view of the Panoramio app for Android List of images retrieved by the Panoramio app

The thumbnail results are displayed in a ListView. To see the full screen image, the user clicks on the thumbnail.

Google TV modifications

For Google TV, the first screen showing the MapView with the user's current location is not appropriate because Google TV devices are stationary. A better opening screen for "Panoramio for Google TV" is a display of pictures for the user's current location (set during the initial device setup). This screen also includes controls for browsing the pictures from cool places in the world and for searching for pictures by location.

A Google TV display has landscape orientation, so it has more horizontal screen space than vertical space. For this orientation, a better layout than ListView is GridView. While the ListView layout is good for a small, portrait-oriented screens, the GridView layout is better for displaying multiple columns of images across the display's width. GridView also offers flexible column sizes to fit different TV screen sizes.

The resulting layout is something like the layout on the left below.

When the user selects a thumbnail in the GridView, the application displays the image in a full-screen format. Given that we have more space on the Google TV screen, we will also include a carousel on the bottom to browse through the images. The resulting layout is something like the layout on the right below:

How the location search box appears in the app A grid view of pictures downloaded from Panoramio

Enlarging controls and fonts

Because users view a Google TV from a distance, an application's icons, controls, and text fonts for the Google TV version have to be larger than that for the mobile version. For the Panoramio app, these large versions of layout files are in the res/layout-large-notouch folder. The high resolution icon and background drawables are in the res/drawable-xhdpi folder.

Using the left navigation bar

As shown in the screenshot above, the main screen has a vertically-scrolling GridView. To make it easier for users to navigate to other controls using the D-pad, the application displays a left navigation bar (to learn more about the left navigation bar, see Using Left Navigation Bar).

Providing high resolution images

The "view image" screen of the app displays a full screen image that the user selected. This must be a high resolution image; otherwise, it will look grainy on a large TV screen. We use the tips in this section, to download and display high-resolution full-screen images.

Using fragments


The app screenshot above is for the "Cool Places" Activity UI. This UI uses fragments to create two ListView widgets displayed side by side. For Google TV, this style of multi-pane UI makes good use of the large screen space.

Using keyboard media keys

The following code snippet demonstrates how to control an animated image slideshow with keyboard media control keys.
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        switch (keyCode) {
            case KeyEvent.KEYCODE_MEDIA_PLAY:{
                if (!mPlaying) {
                    startSlideShow();
                }
                mPlaying = true;
                break;
            }
            case KeyEvent.KEYCODE_MEDIA_PAUSE:{
                mPlaying = false;
                showStatusToast(R.string.slideshow_paused);
            }
        }
        return super.onKeyDown(keyCode, event);
    }

Removing or working around unsupported features

The mobile version of the Panoramio app uses GPS, but because this feature is not supported in Google TV, it is not requested in the manifest for the "Panoramio for Google TV" app. Instead, the "Panoramio for Google TV" app uses the "static" location provider to get a location from the zip code configured during device setup. The following code snippet demonstrates this:
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

Location location = locationManager.getLastKnownLocation("static");

Geocoder geocoder = new Geocoder(this);

Address address = null;

try {

  address = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1).get(0);

  Log.d("Zip code", address.getPostalCode());


} catch (IOException e) {

  Log.e(TAG, "Geocoder error", e);

}  

The app uses the Google Maps API Geocoder to convert the location into a latitude and longitude:
/** This class provides Geocoding services through the Google Maps APIs **/
public class GeoCoderTask extends AsyncTask {
    // URL prefix to the geocoder
    private static final String GEOCODER_REQUEST_PREFIX_FOR_JSON =
            "//maps.googleapis.com/maps/api/geocode/json";


    public GeoResponse geocode(String address)
            throws IOException, URISyntaxException, JSONException {

        // prepare a URL to the geocoder
        String url = GEOCODER_REQUEST_PREFIX_FOR_JSON + "?address="
                + URLEncoder.encode(address, "UTF-8") + "&sensor=false";

        // prepare an HTTP connection to the geocoder
        URI uri = new URI("http", url, null);
        HttpGet get = new HttpGet(uri);

        HttpClient client = new DefaultHttpClient();
        HttpResponse response = client.execute(get);
	// The json response from Maps Geocoder.
        HttpEntity entity = response.getEntity();
        String str = Utilities.convertStreamToString(entity.getContent());
        JSONObject json = new JSONObject(str);
        return parse(json);

    }

    private GeoResponse parse(JSONObject json) {
        GeoResponse geoResponse = null;
        try {
            JSONArray array = json.getJSONArray("results");
            if (array.length() > 0) {

                JSONObject obj = array.getJSONObject(0);
                JSONObject viewport = obj
                        .getJSONObject("geometry").getJSONObject("viewport");
                double minLat = viewport.getJSONObject("southwest").getDouble(
                        "lat");
                double minLng = viewport.getJSONObject("southwest").getDouble(
                        "lng");
                double maxLat = viewport.getJSONObject("northeast").getDouble(
                        "lat");
                double maxLng = viewport.getJSONObject("northeast").getDouble(
                        "lng");
                geoResponse = new GeoResponse(minLat, minLng, maxLat, maxLng);
            }
        } catch (JSONException e) {
            Log.e(TAG, e.toString());
        }
        return geoResponse;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers it
     * the parameters given to AsyncTask.execute()
     */
    protected GeoResponse doInBackground(String... address) {
        try {
            return geocode(address[0]);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.