Tag Archives: Android TV

Alternative input methods for Android TV

Posted by Benjamin Baxter, Developer Advocate and Bacon Connoisseur

Hero image displaying phones and tvs communicating to each other

All TVs have the same problem with keyboard input: It is very cumbersome to hunt and peck for each letter using a D-pad with a remote. And if you make a mistake, trying to correct it compounds the problem.

APIs like Smart Lock and Autofill, can ease user's frustrations, but for certain types of input, like login, you need to collect complex input that is difficult for users using the on-screen keyboard.

With the Nearby Connections API, you can use a second screen to gather input from the user with less friction.

How Nearby Connections works

From the documentation:

"Nearby Connections is an offline peer-to-peer socket model for communication based on advertising and discovering devices in proximity.

Usage of the API falls into two phases: pre-connection, and post-connection.

In the pre-connection phase, Advertisers advertise themselves, while Discoverers discover nearby Advertisers and send connection requests. A connection request from a Discoverer to an Advertiser initiates a symmetric authentication flow that results in both sides independently accepting (or rejecting) the connection request.

After a connection request is accepted by both sides, the connection is established and the devices enter the post-connection phase, during which both sides can exchange data."

In most cases the TV is the advertiser and the phone is the discoverer. In the example below, the assumed second device is a phone. The API and patterns described in this article are not limited to a phone. For example, a tablet could also be the second screen device.

The TV is the Advertiser and the phone is the Discoverer.

Login Example

There are many times when keyboard input is required. Authenticating users and collecting billing information (like zip codes and name on card) are common cases. This example handles a login flow that uses a second screen to see how Nearby Connections can help reduce friction.

1. The user opens your app on her TV and needs to login. You can show a screen of options similar to the setup flow for a new TV.

Android TV setup with prompt to continue on the user's phone.

2. After the user chooses to login with their phone, the TV should start advertising and send the user to the associated login app on their phone, which should start discovering.

There are a variety of solutions to open the app on the phone. As an example, Android TV's setup flow has the user open the corresponding app on their mobile device. Initiating the hand-off is a more a UX concern than a technology concern.

Animation showing setup hand off from TV to phone.

3. The phone app should display the advertising TV and prompt the user to initiate the connection. After the (encrypted -- see Security Considerations below for more on this) connection is established the TV can stop advertising and the phone can stop discovering.

"Advertising/Discovery using Nearby Connections for hours on end can affect a device's battery. While this is not usually an issue for a plugged-in TV, it can be for mobile devices, so be conscious about stopping advertising and discovery once they're no longer needed."

4. Next, the phone can start collecting the user's input. Once the user enters their login information, the phone should send it to the TV in a BYTES payload over the secure connection.

5. When the TV receives the message it should send an ACK (using a BYTES payload) back to the phone to confirm delivery.

6. When the phone receives the ACK, it can safely close the connection.

The following diagram summarizes the sequence of events:

Sequence diagram of order of events to setup a connect and send a message.

UX considerations

Nearby Connections needs location permissions to be able to discover nearby devices. Be transparent with your users. Tell them why they need to grant the location permission on their phone.

Since the TV is advertising, it does not need location permissions.

Start advertising: The TV code

After the user chooses to login on the phone, the TV should start advertising. This is a very simple process with the Nearby API.

override fun onGuidedActionClicked(action: GuidedAction?) {
    super.onGuidedActionClicked(action)
    if( action == loginAction ) {
        // Update the UI so the user knows to check their phone
        navigationFlowCallback.navigateToConnectionDialog()
        doStartAdvertising(requireContext()) { payload ->
            handlePayload(payload)
        }
    }
}

When the user clicks a button, update the UI to tell them to look at their phone to continue. Be sure to offer a way to cancel the remote login and try manually with the cumbersome onscreen keyboard.

This example uses a GuidedStepFragment but the same UX pattern applies to whatever design you choose.

Advertising is straightforward. You need to supply a name, a service id (typically the package name), and a `ConnectionLifeCycleCallback`.

You also need to choose a strategy that both the TV and the phone use. Since it is possible that the users has multiple TVs (living room, bedroom, etc) the best strategy to use is P2P_CLUSTER.

Then start advertising. The onSuccessListener and onFailureListener tell you whether or not the device was able to start advertising, they do not indicate a device has been discovered.

fun doStartAdvertising(context: Context) {
    Nearby.getConnectionsClient(context).startAdvertising(
        context.getString(R.string.tv_name),
        context.packageName,
        connectionLifecycleCallback,
        AdvertisingOptions.Builder().setStrategy(Strategy.P2P_CLUSTER).build()
    )
    .addOnSuccessListener {
        Log.d(LoginStepFragment.TAG, "We are advertising!")
    }
    .addOnFailureListener {
        Log.d(LoginStepFragment.TAG, "We cannot start advertising.")
        Toast.makeText(
            context, "We cannot start advertising.", Toast.LENGTH_LONG)
                .show()
    }
}

The real magic happens in the `connectionLifecycleCallback` that is triggered when devices start to initiate a connection. The TV should accept the handshake from the phone (after performing the necessary authentication -- see Security Considerations below for more) and supply a payload listener.

val connectionLifecycleCallback = object : ConnectionLifecycleCallback() {

    override fun onConnectionInitiated(
            endpointId: String, 
            connectionInfo: ConnectionInfo
    ) {
        Log.d(TAG, "Connection initialized to endpoint: $endpointId")
        // Make sure to authenticate using `connectionInfo.authenticationToken` 
        // before accepting
        Nearby.getConnectionsClient(context)
            .acceptConnection(endpointId, payloadCallback)
    }

    override fun onConnectionResult(
        endpointId: String, 
        connectionResolution: ConnectionResolution
    ) {
        Log.d(TAG, "Received result from connection: ${connectionResolution.status.statusCode}")
        doStopAdvertising()
        when (connectionResolution.status.statusCode) {
            ConnectionsStatusCodes.STATUS_OK -> {
                Log.d(TAG, "Connected to endpoint: $endpointId")
                otherDeviceEndpointId = endpointId
            }
            else -> {
                otherDeviceEndpointId = null
            }
        }
    }

    override fun onDisconnected(endpointId: String) {
        Log.d(TAG, "Disconnected from endpoint: $endpointId")
        otherDeviceEndpointId = null
    }
}

The payloadCallback listens for the phone to send the login information needed. After receiving the login information, the connection is no longer needed. We go into more detail later in the Ending the Conversation section.

Discovering the big screen: The phone code

Nearby Connections does not require the user's consent. However, the location permission must be granted in order for discovery with Nearby Connections to work its magic. (It uses BLE scanning under the covers.)

After opening the app on the phone, start by prompting the user for location permission if not already granted on devices running Marshmallow and higher.

Once the permission is granted, start discovering, confirm the connection, collect the credentials, and send a message to the TV app.

Discovering is as simple as advertising. You need a service id (typically the package name -- this should be the same on the Discoverer and Advertiser for them to see each other), a name, and a `EndpointDiscoveryCallback`. Similar to the TV code, the flow is triggered by callbacks based on the connection status.

Nearby.getConnectionsClient(context).startDiscovery(
        context.packageName,
        mobileEndpointDiscoveryCallback,
        DiscoveryOptions.Builder().setStrategy(Strategy.P2P_CLUSTER).build()
        )
        .addOnSuccessListener {
            // We're discovering!
            Log.d(TAG, "We are discovering!")
        }
         .addOnFailureListener {
            // We were unable to start discovering.
            Log.d(TAG, "We cannot start discovering!")
        }

The Discoverer's listeners are similar to the Advertiser's success and failure listeners; they signal if the request to start discovery was successful or not.

Once you discover an advertiser, the `EndpointDiscoveryCallback` is triggered. You need to keep track of the other endpoint to know who to send the payload, e.g.: the user's credentials, to later.

val mobileEndpointDiscoveryCallback = object : EndpointDiscoveryCallback() {
    override fun onEndpointFound(
        endpointId: String, 
        discoveredEndpointInfo: DiscoveredEndpointInfo
    ) {
        // An endpoint was found!
        Log.d(TAG, "An endpoint was found, ${discoveredEndpointInfo.endpointName}")
        Nearby.getConnectionsClient(context)
            .requestConnection(
                context.getString(R.string.phone_name), 
                endpointId, 
                connectionLifecycleCallback)
    }

    override fun onEndpointLost(endpointId: String) {
        // A previously discovered endpoint has gone away.
        Log.d(TAG, "An endpoint was lost, $endpointId")
    }
}

One of the devices must initiate the connection. Since the Discoverer has a callback for endpoint discovery, it makes sense for the phone to request the connection to the TV.

The phone asks for a connection supplying a `connectionLifecycleCallback` which is symmetric to the callback in the TV code.

val connectionLifecycleCallback = object : ConnectionLifecycleCallback() {
    override fun onConnectionInitiated(
        endpointId: String,
        connectionInfo: ConnectionInfo
    ) {
        Log.d(TAG, "Connection initialized to endpoint: $endpointId")
        // Make sure to authenticate using `connectionInfo.authenticationToken` before accepting
        Nearby.getConnectionsClient(context)
                .acceptConnection(endpointId, payloadCallback)
    }

    override fun onConnectionResult(
        endpointId: String,
        connectionResolution: ConnectionResolution
    ) {
        Log.d(TAG, "Connection result from endpoint: $endpointId")
        when (connectionResolution.status.statusCode) {
            ConnectionsStatusCodes.STATUS_OK -> {
                Log.d(TAG, "Connected to endpoint: $endpointId")
                otherDeviceEndpointId = endpointId
                waitingIndicator.visibility = View.GONE
                emailInput.editText?.isEnabled = true
                passwordInput.editText?.isEnabled = true

                Nearby.getConnectionsClient(this).stopDiscovery()
            }
            else -> {
                otherDeviceEndpointId = null
            }
        }
    }

    override fun onDisconnected(endpointId: String) {
        Log.d(TAG, "Disconnected from endpoint: $endpointId")
        otherDeviceEndpointId = null
    }
}

Once the connection is established, stop discovery to avoid keeping this battery-intensive operation running longer than needed. The example stops discovery after the connection is established, but it is possible for a user to leave the activity before that happens. Be sure to stop the discovery/advertising in onStop() on both the TV and phone.


override fun onStop() {
    super.onStop()
    Nearby.getConnectionsClient(this).stopDiscovery()
}


Just like a TV app, when you accept the connection you supply a payload callback. The callback listens for messages from the TV app such as the ACK described above to clean up the connection.

After the devices are connected, the user can use the keyboard and send their authentication information to the TV by calling `sendPayload()`.

fun sendCreditials() {

    val email = emailInput.editText?.text.toString()
    val password = passwordInput.editText?.text.toString()

    val creds = "$email:$password"
    val payload = Payload.fromBytes(creds.toByteArray())
    Log.d(TAG, "sending payload: $creds")
    if (otherDeviceEndpointId != null) {
        Nearby.getConnectionsClient(this)
                .sendPayload(otherDeviceEndpointId, payload)
    }
}

Ending the conversation

After the phone sends the payload to the TV (and the login is successful), there is no reason for the devices to remain connected. The TV can initiate the disconnection with a simple shutdown protocol.

The TV should send an ACK to the phone after it receives the credential payload.

val payloadCallback = object : PayloadCallback() {
    override fun onPayloadReceived(endpointId: String, payload: Payload) {
        if (payload.type == Payload.Type.BYTES) {
            payload.asBytes()?.let {
                val body = String(it)
                Log.d(TAG, "A payload was received: $body")
                // Validate that this payload contains the login credentials, and process them.

                val ack = Payload.fromBytes(ACK_PAYLOAD.toByteArray())
                Nearby.getConnectionsClient(context).sendPayload(endpointId, ack)
            }
        }
    }

    override fun onPayloadTransferUpdate(
        endpointId: String,
        update: PayloadTransferUpdate
    ) {    }
}

The phone should have a `PayloadCallback` that initiates a disconnection in response to the ACK. This is also a good time to reset the UI to show an authenticated state.

private val payloadCallback = object : PayloadCallback() {
    override fun onPayloadReceived(endpointId: String, payload: Payload) {
        if (payload.type == Payload.Type.BYTES) {
            payload.asBytes()?.let {
                val body = String(it)
                Log.d(TAG, "A payload was received: $body")

                if (body == ACK_PAYLOAD) {
                    waitingIndicator.visibility = View.VISIBLE
                    waitingIndicator.text = getString(R.string.login_successful)
                    emailInput.editText?.isEnabled = false
                    passwordInput.editText?.isEnabled = false
                    loginButton.isEnabled = false

                    Nearby.getConnectionsClient(this@MainActivity)
                        .disconnectFromEndpoint(endpointId)
                }
            }
        }
    }

    override fun onPayloadTransferUpdate(
        endpointId: String,
        update: PayloadTransferUpdate
    ) {    }
}

Security considerations

For security (especially since we're sending over sensitive information like login credentials), it's strongly recommended that you authenticate the connection by showing a code and having the user confirm that the two devices being connected are the intended ones -- without this, the connection established by Nearby Connection is encrypted but not authenticated, and that's susceptible to Man-In-The-Middle attacks. The documentation goes into greater detail on how to authenticate a connection.

Let the user accept the connection by displaying a confirmation code on both devices.

Does your app offer a second screen experience?

There are many times when a user needs to supply input to a TV app. The Nearby API provides a way to offload the hardships of an onscreen-dpad-driven keyboard to an easy and familiar phone keyboard.

What use cases do you have where a second screen would simplify your user's life? Leave a comment or send me (@benjamintravels) or Varun (@varunkapoor, Team Lead for Nearby Connections) a tweet to continue the discussion.

Phasing out legacy recommendations on Android TV

Posted by Bejamin Baxter, Developer Programs Engineer

At Google I/O 2017, we announced a redesign of the Android TV's home screen. We expanded the recommendation row concept so that each app can have its own row (or multiple rows) of content on the home screen. Since the release of the new home screen, we have seen increased adoption of the new recommendation channels for Android Oreo in a wide variety of apps.

With more and more apps surfacing high-quality recommendations using the new channels, the legacy recommendation row in the new home screen on Android O will be phased out over the next year.

Currently, when an app creates recommendations with the legacy notification based API the content is added to a channel for that app. The channel may already exist if there was recommended content for it when you upgraded from Android N (or below). If the there is no channel for the app, it will be automatically generated for you. In either case, the user can't add or remove programs from the channel, but they can move, hide, and show the channel. When an app starts to use the new API to add its own channels, the system removes the auto-generated channel and the app takes over control of the display of their content.

Over the next year, we will phase out the automatic generation of channels. Instead of generating multiple channels, one for each app's legacy recommendations, we will insert one channel for all legacy recommendations. This channel will appear at the bottom of the channel list. Users can move or remove it. When a user upgrades to Android O, the previous recommendation row from Android N (and below) becomes a channel at the bottom of the home screen.

If you have not updated your app to post content to the new channels on the home screen, take a look at our documentation, codelab, and sample to get started.

We look forward to more and more apps taking advantage of the new changes in the home screen. We love to hear your feedback, so please visit the Android TV Developer Community on G+ to share your thoughts and ideas.

Working with Multiple JobServices

Posted by Isai Damier, Software Engineer, Android DA

Working with Multiple JobServices

In its continuous effort to improve user experience, the Android platform has introduced strict limitations on background services starting in API level 26. Basically, unless your app is running in the foreground, the system will stop all of your app's background services within minutes.

As a result of these restrictions on background services, JobScheduler jobs have become the de facto solution for performing background tasks. For people familiar with services, JobScheduler is generally straightforward to use: except in a few cases, one of which we shall explore presently.

Imagine you are building an Android TV app. Since channels are very important to TV Apps, your app should be able to perform at least five different background operations on channels: publish a channel, add programs to a channel, send logs about a channel to your remote server, update a channel's metadata, and delete a channel. Prior to Android 8.0 (Oreo) each of these five operations could be implemented within background services. Starting in API 26, however, you must be judicious in deciding which should be plain old background Services and which should be JobServices.

In the case of a TV app, of the five operations mentioned above, only channel publication can be a plain old background service. For some context, channel publication involves three steps: first the user clicks on a button to start the process; second the app starts a background operation to create and submit the publication; and third, the user gets a UI to confirm subscription. So as you can see, publishing channels requires user interactions and therefore a visible Activity. Hence, ChannelPublisherService could be an IntentService that handles the background portion. The reason you should not use a JobService here is because JobService will introduce a delay in execution, whereas user interaction usually requires immediate response from your app.

For the other four operations, however, you should use JobServices; that's because all of them may execute while your app is in the background. So respectively, you should have ChannelProgramsJobService, ChannelLoggerJobService, ChannelMetadataJobService, and ChannelDeletionJobService.

Avoiding JobId Collisions

Since all the four JobServices above deal with Channel objects, it should be convenient to use the channelId as the jobId for each one of them. But because of the way JobServices are designed in the Android Framework, you can't. The following is the official description of jobId

Application-provided id for this job. Subsequent calls to cancel, 
or jobs created with the same jobId, will update the pre-existing 
job with the same id. This ID must be unique across all clients 
of the same uid (not just the same package). You will want to 
make sure this is a stable id across app updates, so probably not 
based on a resource ID.

What the description is telling you is that even though you are using 4 different Java objects (i.e. -JobServices), you still cannot use the same channelId as their jobIds. You don't get credit for class-level namespace.

This indeed is a real problem. You need a stable and scalable way to relate a channelId to its set of jobIds. The last thing you want is to have different channels overwriting each other's operations because of jobId collisions. Were jobId of type String instead of Integer, the solution would be easy: jobId= "ChannelPrograms" + channelId for ChannelProgramsJobService, jobId= "ChannelLogs" + channelId for ChannelLoggerJobService, etc. But since jobId is an Integer and not a String, you have to devise a clever system for generating reusable jobIds for your jobs. And for that, you can use something like the following JobIdManager.

JobIdManager is a class that you tweak according to your app's needs. For this present TV app, the basic idea is to use a single channelId over all jobs dealing with Channels. To expedite clarification: let's first look at the code for this sample JobIdManager class, and then we'll discuss.

public class JobIdManager {

   public static final int JOB_TYPE_CHANNEL_PROGRAMS = 1;
   public static final int JOB_TYPE_CHANNEL_METADATA = 2;
   public static final int JOB_TYPE_CHANNEL_DELETION = 3;
   public static final int JOB_TYPE_CHANNEL_LOGGER = 4;

   public static final int JOB_TYPE_USER_PREFS = 11;
   public static final int JOB_TYPE_USER_BEHAVIOR = 21;

   @IntDef(value = {
           JOB_TYPE_CHANNEL_PROGRAMS,
           JOB_TYPE_CHANNEL_METADATA,
           JOB_TYPE_CHANNEL_DELETION,
           JOB_TYPE_CHANNEL_LOGGER,
           JOB_TYPE_USER_PREFS,
           JOB_TYPE_USER_BEHAVIOR
   })
   @Retention(RetentionPolicy.SOURCE)
   public @interface JobType {
   }

   //16-1 for short. Adjust per your needs
   private static final int JOB_TYPE_SHIFTS = 15;

   public static int getJobId(@JobType int jobType, int objectId) {
       if ( 0 < objectId && objectId < (1<< JOB_TYPE_SHIFTS) ) {
           return (jobType << JOB_TYPE_SHIFTS) + objectId;
       } else {
           String err = String.format("objectId %s must be between %s and %s",
                   objectId,0,(1<<JOB_TYPE_SHIFTS));
           throw new IllegalArgumentException(err);
       }
   }
}

As you can see, JobIdManager simply combines a prefix with a channelId to get a jobId. This elegant simplicity, however, is just the tip of the iceberg. Let's consider the assumptions and caveats beneath.

First insight: you must be able to coerce channelId into a Short, so that when you combine channelId with a prefix you still end up with a valid Java Integer. Now of course, strictly speaking, it does not have to be a Short. As long as your prefix and channelId combine into a non-overflowing Integer, it will work. But margin is essential to sound engineering. So unless you truly have no choice, go with a Short coercion. One way you can do this in practice, for objects with large IDs on your remote server, is to define a key in your local database or content provider and use that key to generate your jobIds.

Second insight: your entire app ought to have only one JobIdManager class. That class should generate jobIds for all your app's jobs: whether those jobs have to do with Channels, Users, or Cats and Dogs. The sample JobIdManager class points this out: not all JOB_TYPEs have to do with Channel operations. One job type has to do with user prefs and one with user behavior. The JobIdManager accounts for them all by assigning a different prefix to each job type.

Third insight: for each -JobService in your app, you must have a unique and final JOB_TYPE_ prefix. Again, this must be an exhaustive one-to-one relationship.

Using JobIdManager

The following code snippet from ChannelProgramsJobService demonstrates how to use a JobIdManager in your project. Whenever you need to schedule a new job, you generate the jobId using JobIdManager.getJobId(...).

import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.PersistableBundle;

public class ChannelProgramsJobService extends JobService {
  
   private static final String CHANNEL_ID = "channelId";
   . . .

   public static void schedulePeriodicJob(Context context,
                                      final int channelId,
                                      String channelName,
                                      long intervalMillis,
                                      long flexMillis)
{
   JobInfo.Builder builder = scheduleJob(context, channelId);
   builder.setPeriodic(intervalMillis, flexMillis);

   JobScheduler scheduler = 
            (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
   if (JobScheduler.RESULT_SUCCESS != scheduler.schedule(builder.build())) {
       //todo what? log to server as analytics maybe?
       Log.d(TAG, "could not schedule program updates for channel " + channelName);
   }
}

private static JobInfo.Builder scheduleJob(Context context,final int channelId){
   ComponentName componentName =
           new ComponentName(context, ChannelProgramsJobService.class);
   final int jobId = JobIdManager
             .getJobId(JobIdManager.JOB_TYPE_CHANNEL_PROGRAMS, channelId);
   PersistableBundle bundle = new PersistableBundle();
   bundle.putInt(CHANNEL_ID, channelId);
   JobInfo.Builder builder = new JobInfo.Builder(jobId, componentName);
   builder.setPersisted(true);
   builder.setExtras(bundle);
   builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
   return builder;
}

   ...
}

Footnote: Thanks to Christopher Tate and Trevor Johns for their invaluable feedback

Video Playback with the Google Assistant on Android TV

Posted by Benjamin Baxter, Developer Programs Engineer

How to integrate the Google Assistant in a TV app

Earlier this year, we announced that the Google Assistant will be coming to Android TV and it has arrived. The Google Assistant on Android TV will allow users to discover, launch and control media content, control smart devices like light bulbs, and much more. Your Assistant also understands that you're interacting on a TV, so you'll get the best experience possible while watching your favorite movies and TV shows.

The Google Assistant has a built-in capability to understand commands like "Watch The Incredibles", and media controls, like pause, fast forward, etc. This article will walk through how to integrate the Google Assistant into your application.

There are no new APIs needed to integrate with the Google Assistant. You just need to follow the pattern that the Google Assistant expects from your app. If you want to experiment and play with the APIs and the Assistant, you can download this sample from github.

Discovery

The Google Assistant has made some changes to improve finding information on Android TV.

There are a few ways to expose your content to users through the Google Assisant.

Server side integration. (Requires registration and onboarding)

You need to provide your content catalog to Google. This data is ingested and available to the Google Assistant outside of your app.

This is not specific for Google Assistant. It will also enable other Google services such as search and discovery on Google Search, Google Play, Google Home App, and Android TV.

Client side integration. (Available to all apps)

If your app is already searchable, then you only need to handle the EXTRA_START_PLAYBACK flag, which we go into more detail later. Content will auto-play if the app name is explicitly specified in the search results or if the user is already in your app.

Once your app is searchable, you can test by asking the Assistant or, if you are in a loud area, test quietly by running the following adb command:

adb shell am start -a "android.search.action.GLOBAL_SEARCH" --es query \"The Incredibles\" 

Each app that responds to the search query will have a row displaying their search results. Notice that YouTube and the sample app, Assistant Playback, each receive their own rows for content that match the search query.

For specific searches such as "Play Big Buck Bunny", the Assistant will present a card with a button for each app that exactly matched the search query. In the screenshot below, you can see the sample app, Assistant Playback, shows up as an option to watch Big Buck Bunny.

There are times when the Google Assistant will launch an app directly to start playing content. An example of when this occurs is when content is exclusive to the app; "Play the Netflix original House of Cards".

Launching

When the user selects a video from search results, an intent is sent to your app. The priority order for the intent actions are as follows:

  1. Intent specified in the cursor returned from the search (SUGGEST_COLUMN_INTENT_ACTION).
  2. Intent specific in the searchable.xml file with the searchSuggestIntentAction value.
  3. Defaults to ACTION_VIEW.

In addition, the Assistant will also pass an extra to signal if playback should begin immediately. You app should be able to handle the intent and expect a boolean extra called EXTRA_START_PLAYBACK.

import static android.support.v4.content.IntentCompat.EXTRA_START_PLAYBACK;

public class SearchableActivity extends Activity {

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       if (getIntent() != null) {
           // Retrieve video from getIntent().getData().

           boolean startPlayback = getIntent().getBooleanExtra(EXTRA_START_PLAYBACK, false);
           Log.d(TAG, "Should start playback? " + (startPlayback ? "yes" : "no"));

           if (startPlayback) {
               // Start playback.
               startActivity(...);
           } else {
               // Show details for movie.
               startActivity(...);
           }
       }
       finish();
   }
}

You can test this by modifying and running the following adb command. If your app has a custom action, then replace android.intent.action.VIEW with the custom action. Replace the value of the -d argument with the URI you return from the Assistant's query.

adb shell 'am start -a android.intent.action.VIEW --ez
android.intent.extra.START_PLAYBACK true -d <URI> -f 0x14000000'

The -f argument is the logical OR value from FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP. This will force your activity to be freshly launched.

For example, in the sample app, you can run the following command to launch playback of "Big Buck Bunny" as if the assistant had launched it.

adb shell 'am start -a android.intent.action.VIEW --ez
android.intent.extra.START_PLAYBACK true -d 
content://com.example.android.assistantplayback/video/2 -n
com.example.android.assistantplayback/.SearchableActivity -f 0x14000000'

The URI above is defined by the value of android:searchSuggestIntentData in searchable.xml (content://com.example.android.assistantplayback/video/) in addition to the value of SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID (2) returned from the query.

Note that intents may be cached by the Google Assistant up to 7 days. Your app could receive a request to play content that is no longer available. The intent handler should be designed to be stateless and not rely on any previously knowledge to handle the deep link. Your app should gracefully handle this situation. One solution would be to show an error message and let the user land on your main activity or another relevant activity.

Playback

If your app implements MediaSession correctly, then your app should work right away with no changes.

The Google Assistant assumes that your app handles transport controls. The Assistant uses the TransportControls to send media commands to your app's MediaSession. Video apps must support the following controls wherever possible:

  • Play/Pause/Stop
  • Previous/Next
  • Rewind/Fast Forward (implemented with seekTo())

You can easily get a hook for these controls by implementing a MediaSession.Callback. If you play videos using PlaybackTransportControlGlue, then all your callback needs to do it sync the glue and the MediaSession. Otherwise use this callback to sync your player.

public class MyMediaSessionCallback extends MediaSessionCompat.Callback {

   private final PlaybackTransportControlGlue<?> mGlue;

   public MediaSessionCallback(PlaybackTransportControlGlue<?> glue) {
       mGlue = glue;
   }

   @Override
   public void onPlay() {
       Log.d(TAG, "MediaSessionCallback: onPlay()");
       mGlue.play();
       updateMediaSessionState(...);
   }

   @Override
   public void onPause() {
       Log.d(TAG, "MediaSessionCallback: onPause()");
       mGlue.pause();
       updateMediaSessionState(...);
   }

   @Override
   public void onSeekTo(long position) {
       Log.d(TAG, "MediaSessionCallback: onSeekTo()");
       mGlue.seekTo(position);
       updateMediaSessionState(...);
   }

   @Override
   public void onStop() {
       Log.d(TAG, "MediaSessionCallback: onStop()");
       // Handle differently based on your use case.
   }

   @Override
   public void onSkipToNext() {
       Log.d(TAG, "MediaSessionCallback: onSkipToNext()");
       playAndUpdateMediaSession(...);
   }

   @Override
   public void onSkipToPrevious() {
       Log.d(TAG, "MediaSessionCallback: onSkipToPrevious()");
       playAndUpdateMediaSession(...);
   }
}

Continue learning

Check out the following articles and training documents to continue learning about MediaSession and Video apps.

To play around with the Google Assistant on Android TV, download the sample app and run it on Nvidia Shield running Android M or above.

If you would like to continue the discussion, leave a response or talk to me on Twitter.

Welcome to your New Home on Android TV

Posted by Paul Saxman, Android Devices and Media Developer Relations Lead

Android TV brings rich app experiences and entertainment to the biggest screen in your house, and with Android O, we’re making it even easier for users to access content from their favorite apps. We’ve built a new, content-centric home screen experience for Android TV, and we're bringing the Google Assistant to the platform as well. These features put content that users want to access a few clicks, or spoken words, away.

The New Android TV Home Screen

The new Android TV home screen organizes video content into channels and programs in a way that’s familiar to TV viewers. Each Android TV app can publish multiple channels, which are represented as rows of programs on the home screen. Apps add relevant programs on each channel, and update these programs and channels as users access content or when new content is available. To help engage users, programs can include a video preview, which is automatically played when a user focuses on a program. Users can configure which channels they wish to see on the home screen, and the ordering of channels, so the themes and shows they’re interested in are quick and easy to access.

In addition to channels for you app, the top of the new Android TV home screen includes a quick launch bar for users' favorite apps, and a special Watch Next channel. This channel contains programs based on the viewing habits of the user.

The APIs for creating and maintaining channels and programs are part of the TvProvider APIs, which are distributed as an Android Support Library module with Android O. To get started using these APIs, visit the Android O Developer Preview site for an overview, and try out the Android TV Channels and Programs codelab for a first-hand experience building an Android TV app for Android O.

Later this year, Nexus Players will receive the new Android TV home experience as an OTA update. If you wish build and test apps for the new interface today, however, you can use the Android TV emulator or Nexus Player device images that are part of the latest Android O Developer Preview.

The Google Assistant on Android TV

The Google Assistant on Android TV, coming later this year, will allow users to quickly find and access content using their voice. Because the Assistant is context-aware, it can help users narrow down what content to play. Users will also be able access the Assistant to control playback, even while a video or music is playing. And since the Assistant can control compatible smart home devices, a simple voice request can dim the lights to create an ideal movie viewing environment. When the Google Assistant comes to Android TV, it will launch in the US on Android devices running M, N, and O.

We're looking forward to seeing how developers take advantage of the new Android TV home screen. We welcome feedback, so please visit the Android TV Developer Community on G+ to share you thoughts and ideas!

What’s New in Android: O Developer Preview 2 & More

Posted by: Dave Burke, VP of Engineering

android-o-logo.png
With billions of Android devices around the world, Android has surpassed our wildest expectations. Today at Google I/O, we showcased a number of ways we’re pushing Android forward, with the O Release, new tools for developers to help create more performant apps, and an early preview of a project we call Android Go -- a new experience that we’re building for entry-level devices.
Fluid experiences in Android O
It's pretty incredible what you can do on mobile devices today, and how easy it is to rely on them as computers in our pockets. In the O release we've focused on creating fluid experiences that make Android even more powerful and easy to use, and today we highlighted some of those:
  • Picture-in-picture: lets users manage two tasks simultaneously, whether it’s video calling your friend while checking your calendar, or reading a new recipe while watching a video on a specific cooking technique. We’ve designed PIP to provide seamless multitasking on any size screen, and it’s easy for apps to support it.
  • Notification dots extend the reach of notifications, a new way for developers to surface activity in their app, driving engagement. Built on our unique and highly regarded notification system, dots work with zero effort for most apps - we even extract the color of the dot from your icon. 
  • Autofill with Google simplifies setting up a new device and synchronizing passwords by bringing Chrome's Autofill feature to Android. Once a user opts-in, Autofill will work out-of-the-box for most apps. Developers can optimize their apps for Autofill by providing hints about the type of data expected or add support in custom views. 
  • A new homescreen for Android TV makes it easy for users to find, preview, and watch content provided by apps. Apps can publish one or more channels, and users can control the channels that appear on the homescreen. You’ll be able to get started with creating channels using the new TvProvider support library APIs
  • Smart Text Selection: In Android O, we’re applying on-device machine learning to copy/paste, to let Android recognize entities like addresses, URLs, telephone numbers, and email addresses. This makes the copy/paste experience better by selecting the entire entity and surfacing the right apps to carry out an action based on the type of entity.
  • TensorFlow Lite: As Android continues to take advantage of machine learning to improve the user experience, we want our developer partners to be able to do the same. Today we shared an early look at TensorFlow Lite, an upcoming project based on TensorFlow, Google’s open source machine learning library. TensorFlow Lite is specifically designed to be fast and lightweight for embedded use cases. Since many on-device scenarios require real-time performance, we’re also working on a new Neural Network API that TensorFlow can take advantage of to accelerate computation. We plan to make both of these available to developers in a maintenance update to O later this year, so stay tuned!  
(L) Android O: Picture-in-picture, (R) Android O: Notification dots

Working on the Vitals in Android
We think Android’s foundations are critical, so we’re investing in Android Vitals, a project focused on optimizing battery life, startup time, graphic rendering time, and stability. Today we showcased some of the work we’ve done so far, and introduced new tools to help developers understand power, performance, and reliability issues in their apps:
  • System optimizations: in Android O, we’ve done a lot of work across the system to make apps run faster and smoother. For example we made extensive changes in our runtime - including new optimizations like concurrent compacting garbage collection, code locality, and more. 
  • Background limits: up to now it’s been fairly easy for apps to unintentionally overuse resources while they’re in the background, and this can adversely affect the performance of the system. So in O, we've introduced new limits on background location and wi-fi scans, and changes in the way apps run in the background. These boundaries prevent overuse -- they’re about increasing battery life and freeing up memory.
  • New Android Vitals Dashboards in the Play Console: today we launched six Play Console dashboards to help you pinpoint common issues in your apps - excessive crash rate, ANR rate, frozen frames, slow rendering, excessive wakeups, and stuck wake locks, including how many users are affected, with guidance on the best way to address the issues. You can visit the Play Console today to see your app's data, then learn how to address any issues.
Android Go
Part of Android’s mission is to bring computing to everyone. We’re excited about seeing more users come online for the first time as the price of entry level smart phones drop, and we want to help manufacturers continue to offer lower-cost devices that provide a great experience for these users. Today we gave a sneak peek of a new experience that we’re building specifically for Android devices that have 1GB or less of memory -- Internally we call it “Android Go,” and it’s designed around three things
  • OS: We’re optimizing Android O to run smoothly and efficiently on entry-level devices
  • Apps: We’re also designing Google apps to use less memory, storage space, and mobile data, including apps such as YouTube Go, Chrome, and Gboard. 
  • Play: On entry-level devices, Play store will promote a better user experience by highlighting apps that are specifically designed for these devices -- such as apps that use less memory, storage space, and mobile data -- while still giving users access to the entire app catalog.
The Android Go experience will ship in 2018 for all Android devices that have 1GB or less of memory. We recommend getting your apps ready for these devices soon -- take a look at the Building for Billions to learn about the importance of offering a useful offline state, reducing APK size, and minimizing battery and memory use.

O Developer Preview 2, Now in Public Beta
Today’s release of O Developer Preview 2 is our first beta-quality candidate, available to test on your primary phone or tablet. We’re inviting those who want to try the beta release of Android O to enroll now at android.com/beta -- it’s an incredibly convenient way to preview Android O on your Nexus 5X, 6P, and Player, as well as Pixel, Pixel XL, or Pixel C device.

With more users starting to get Android O on their devices through the Android Beta program, now is the time to test your apps for compatibility, resolve any issues, and publish an update as soon as possible. See the migration guide for steps and a recommended timeline.

Later today you’ll be able to download the updated tools for developing on Android O, including the latest canaries of Android Studio, SDK, and tools, Android O system images, and emulators. Along with those, you’ll be able to download support library 26.0.0 beta and other libraries from our new Maven repo. The change to Maven from SDK Manager means a slight change to your build configuration, but gives you much more flexibility in how you integrate library updates with your CI systems.

When you’re ready to get started developing with Android O, visit the O Developer Preview site for details on all of the features you can use in your apps, including notification channels and dots, picture-in-picture, autofill, and others. APIs have changed since the first developer preview, so take a look at the diff report to see where your code might be affected.

Thanks for the feedback you’ve given us so far. Please keep it coming, about Android O features, APIs, issues, or requests -- see the Feedback and Bugs page for details on where to report feedback.

Adding TV Channels to Your App with the TIF Companion Library

Posted by Nick Felker and Sachit Mishra, Developer Programs Engineers

The TV Input Framework (TIF) on Android TV makes it easy for third-party app developers to create their own TV channels with any type of linear media. It introduces a new way for apps to engage with users with a high-quality channel surfing experience, and it gives users a single interface to browse and watch all of their channels.

To help developers get started with building TV channels, we have created the TV Input Framework Companion Library, which includes a number of helper methods and classes to make the development process as easy as possible.

This library provides standard classes to set up a background task that updates the program guide and an interface that helps integrate your media player with the playback controller, as well as supports the new TV Recording APIs that are available in Android Nougat. It includes everything you need to start showing your content on your Android TV's live TV app.

(Note: source from android-tv-sample-inputs sample)

To get started, take a look at the sample app and documentation. The sample demonstrates how to extend this library to create custom channels and manage video playback. Developers can immediately get started with the sample app by updating the XMLTV file with their own content or dynamically creating channels in the SampleJobService.

You can include this library in your app by copying the library directory from the sample into your project root directory. Then, add the following to your project's settings.gradle file:

include ':library'

In your app's build.gradle file, add the following to your dependencies:

compile project(':library')

Android TV continues to grow, and whether your app has on-demand or live media, TIF is a great way to keep users engaged with your content. One partner for example, Haystack TV, recently integrated TIF into their app and it now accounts for 16% of watch time for new users on Android TV.

Check out our TV developer site to learn more about Android TV, and join our developer community on Google+ at g.co/androidtvdev to discuss this library and other topics with TV developers.

Building TV Channels

Posted by Josh Gordon, Developer Advocate

Channel surfing is a popular way of watching TV. You pick up the remote, lean back, and flip through channels to see what’s on. On Android TV, app developers can create their own channel-like experiences using the TV Input Framework.

To the user, the channels you create look and feel just like regular TV channel. But behind the scenes, they stream video over the internet. For example, you can create a channel from a video playlist.

Watch this DevByte for an overview of how to build to a channel, and see the sample app and developer training for more info. The sample shows how to work with a variety of media formats, including HLS, MPEG-Dash, and HTTP Progressive.



If you already have an app that streams video, consider also making your content available as a channel. It’s a great opportunity to increase engagement. We’re excited to see what you develop, and look forward to seeing your content on the big screen!

Telltale Games share their tips for success on Android TV

Lily Sheringham, Developer Marketing at Google Play

Editor’s note: This is another post in our series featuring tips from developers finding success on Google Play. This week, we’re sharing advice from Telltale Games on how to create a successful game on Android TV. -Ed.

With new Android hardware being released from the likes of Sony, Sharp, and Philips amongst others, Android TV and Google Play can help you bring your game to users right in their living rooms through a big screen experience.

The recent Marshmallow update for Android TV means makes it easier than ever to extend your new or existing games and apps for TV. It's important to understand how your game is presented in the user interface and how it can help users get to the content they want quickly.

Telltale Games is a US-founded game developer and publisher, based in San Francisco, California. They’re well known for the popular series ‘The Walking Dead’ and ‘Game of Thrones‘ which was created in partnership with HBO.

Zac Litton, VP of Technology at Telltale Games, shares his tips for creating and launching your games with Android TV.

Tips for launching successful games on Android TV

  1. Determine the Device for Android TV: Determine what device your game is running on by using the UiModeManager.getCurrentModeType() method. If the device is running in television mode, you can declare what to display as the launch point of the game on the Android TV itself (Configuration). Add the LEANBACK_LAUNCHER filter category to one of your intent-filters to identify your game as being enabled for TV. This is required for your game to be considered a TV app in Google Play.
  2. Touchscreen vs TV: TVs don’t have touch screens so make sure you set the touchscreen required flag to false in the manifest as touch is implicitly true by default on Android. This will help avoid your game getting filtered from the TV Play store right out of the gate. Also, check your permissions, as some imply hardware requirements which you may need to override explicitly.
  3. Use Hardware APIs: Use the package manager which has System Feature API to enable your game to reason about what capabilities it can and should expose. For example, whether to show the user touch screen controls or game controller controls. You can also make your app location aware using the location APIs available in Google Play services to add location awareness with automated location tracking, geofencing, and activity recognition.
  4. Use appropriate controllers: To reach the most users, your app should support a simplified input scheme that doesn’t require a directional pad (D-pad controller). The player needs to be able to use a D-Pad in all aspects of the game—not just controlling core gameplay, but also navigating menus and ads, therefore your Android TV game shouldn’t refer to a touch interface specifically. For example, an Android TV game should not tell a player to "Tap here to continue."
  5. Appear in the right place: Make sure you add an android:isGame attribute to the application element of the manifest and set it to true in order to enable the installed game to show up on the correct launcher row, games.
  6. Provide home screen banners: Provide a home screen banner for each localization supported, especially if you are an international developer. The banner (320 x 180) is the game launch point that appears on the TV home screen on the games row.
  7. Use a TV image for your Store Listing: Be sure you provide at least one TV screen shot on your Store Listing page. Then include a high res icon, feature graphic, promo graphic and TV banner.
  8. Improve visibility through ‘search’ and ‘recommendations’: Android TV uses the Android search interface to retrieve content data from installed apps and games, and deliver search results to the user. Implement a ContentProvider to show instant suggestions to the user, and a SearchManager to deep link your game’s content.
  9. Set appropriate pricing and distribution: Check “Distribute to Android TV” in the relevant section in the Developer Console. This will trigger a review by Google to ensure your game meets the minimum requirements for TV.
  10. Guide the user: Use a tutorial to guide the player into the game mechanics and provide an input reference to the user based on the input control they are using.

With the recently released Android TV codelab and online class from Udacity, you can learn how to convert your existing mobile game into Android TV in just four hours. Find out more about how to build games for Android TV and how you to publish them using familiar tools and processes in Google Play.

New permissions requirements for Android TV

Posted by Anirudh Dewani, Developer Advocate

Android 6.0 introduces a new runtime permission model that gives users more granular control over granting permissions requested from their apps and leads to faster app installs. Users can also revoke these permissions from Settings at any point of time. If an app running on the M Preview supports the new permissions model, the user does not have to grant any permissions when they install or upgrade the app. Developers should check for permissions that require runtime grant from users, and request them if the app doesn’t already have them.

To list all permissions that require runtime grant from users on Android 6.0 -

adb shell pm list permissions -g -d 

RECORD_AUDIO

Apps should generally request as few permissions as possible. Voice search is an integral part of Android TV content discovery experience. When using the internal SpeechRecognizer to enable Voice Search, apps must declare RECORD_AUDIO permission in the manifest. RECORD_AUDIO requires explicit user grant during runtime in Android 6.0. When using the Android TV Leanback support library, apps can eliminate the need for requesting RECORD_AUDIO during runtime by using SpeechRecognitionCallback instead of SpeechRecognizer.

SearchActivity.java

Commit from Android TV Leanback Sample repository.


mFragment = (SearchFragment) getFragmentManager()
                .findFragmentById(R.id.search_fragment);

if (!USE_INTERNAL_SPEECH_RECOGNIZER) {
   
    mSpeechRecognitionCallback = new SpeechRecognitionCallback() {
        
        @Override
        public void recognizeSpeech() {
            if (DEBUG) Log.v(TAG, "recognizeSpeech");
        
            // ACTION_RECOGNIZE_SPEECH
            startActivityForResult(mFragment.getRecognizerIntent(), REQUEST_SPEECH);
        }
    };
    mFragment.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
}


When SpeechRecognitionCallback is set, Android Leanback support library will let the your activity process the voice search action instead of using the internal SpeechRecognizer. The app can then use RecognizerIntent to support speech recognition.

If you have an Android TV app targeting API Level 23, please update the app to use SpeechRecognitionCallback and remove RECORD_AUDIO permission from your manifest.