Exploring Tiles in Android N Preview Release


Some amazing cakeThere are some cool new features slated for N(I’m voting that N stands for Nutella, cause this cake is amazing).

In this post I will be covering my discoveries while using the new Tiles API.

Note the online android developer docs don’t yet contain info about the new N APIs, but you can download the docs here for offline reading. For directions about getting setup to use the preview directions are here.

What Are Tiles & Quick Settings

Screenshot_20160326-143349Quick Settings are a combination of Tiles that expose easy on/off or configuration settings. A Tile is simply one individual Quick Setting. In Android N there is a new API that allows us to make our own tiles.

Easy Setup

The good news is there isn’t much to do to get a custom tile for your app. The API docs are pretty straight forward on what needs to be done. You really only need to declare a service in your manifest:

<service
     android:name=".MyTileService"
     android:label="@string/tile_label"
     android:icon="@drawable/disabled_icon"
     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
     <intent-filter>
         <action android:name="android.service.quicksettings.action.QS_TILE" />
     </intent-filter>
 </service>

And then implement the service in code. In my example below the tile only turns on and off.

public class MyTileService extends TileService {

   @Override
   public int onTileAdded() {
       setCurrentState(Tile.STATE_INACTIVE);
       return super.onTileAdded();
   }

   @Override
   public void onTileRemoved() {
       setCurrentState(Tile.STATE_INACTIVE);
       super.onTileRemoved();
   }

   @Override
   public void onClick() {
       super.onClick();

       Tile tile = getQsTile();

       // switch from active to passive based on tiles current state
       switch (tile.getState()) {
           case Tile.STATE_ACTIVE:
               setCurrentState(Tile.STATE_INACTIVE);
               break;
           case Tile.STATE_INACTIVE:
               setCurrentState(Tile.STATE_ACTIVE);
               break;
           case Tile.STATE_UNAVAILABLE:
               break;
       }
   }

   private void setCurrentState(int state){
       Tile tile = getQsTile();
       tile.setState(state);
       switch (state){
           case Tile.STATE_ACTIVE:
               Icon icon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_android_black_24dp);
               tile.setIcon(icon);
               showToast("Active");  // or code for turing on something e.g. starting another service
               break;
           case Tile.STATE_INACTIVE:
               tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.ic_not_interested_black_24dp));
               showToast("Inactive");// or code for turning off something e.g. another service
               break;
           case Tile.STATE_UNAVAILABLE:
               tile.setIcon(Icon.createWithResource(getApplicationContext(), R.drawable.ic_not_interested_black_24dp));
               showToast("Unavailable");
               break;
       }

       tile.updateTile();// <-- super important line
   }

   @Override
   public void onStartListening() {
       super.onStartListening();
       showToast("Started Listening");
   }

   @Override
   public void onStopListening() {
       super.onStopListening();
       showToast("Stopped Listening");
   }

   private void showToast(final String text) {
       Handler handler = new Handler(Looper.getMainLooper());

       handler.post(new Runnable() {
           @Override
           public void run() {
               Toast.makeText(MyTileService.this.getApplicationContext(), text, Toast.LENGTH_LONG).show();
           }
       });
   }
}

The onTileAdded is called when your tile is added to the primary Quick Settings tiles. Likewise when it is removed onTileRemoved is called. Straight forward enough.

Listening?

However, I was initially unsure of what onStartListening and onStopListening were for. From the docs for onStartListening:

Called when this tile moves into a listening state.
When this tile is in a listening state it is expected to keep the UI up to date. Any listeners or callbacks needed to keep this tile up to date should be registered here and unregistered in onStopListening()

Likewise onStop per the docs:

Called when this tile moves out of the listening state.

From my testing the onStartListening gets called when the tile becomes visible e.g. pulling down the system bar exposing the tile triggers this callback. Likewise dismissing the status bar pull down(hiding the tile) causes the onStopListening to be triggered(You can see this by running the above code).

Updating Tiles

There isn’t a whole lot to a tile, it has an Icon and a Label, contentDescription, and a state. However these properties don’t take affect until you call updateTile().

Active vs. Passive Tiles

At first I thought tiles were rather simple(E.g. essentially toggle buttons with callbacks for notifying us when a user clicked the tile(This is true, and is called a Passive Tile). However there is also an Active Tile which allows you to push updates to your tile. You push updates by using the requestListeningState method per the docs:

Requests that a tile be put in the listening state so it can send an update.

We can then update the state in our onStartListening callback. Essentially this allows us to actively push state to the Tile instead of just being notified when a user clicks it. If you want an active tile you must return TILE_MODE_ACTIVE from onTileAdded()(it returns TILE_MODE_PASSIVE by default).

Bugs

Icon doesn’t get updated between deving builds without going into the edit screen. Actually it’s worse you have remove the Tile first, and add it back in for it to update.

5th icon should be an android not a no symbol 5th icon should be an android not a no symbol Looks correct, but if you hit back it's wrong

Uninstalling an APP that has a Tile in the primary Quick Settings menu doesn’t go away until you go to edit the quick settings.
Screenshot_20160326-150836

Not including an icon in the service declaration in the manifest leads to what appears to be invisible tile(even the label is missing). It appears to be clickable(you can see the ripple effect) but even the label is missing. Below are screen shots of the tile being active, but not displaying on two of the screens and showing a ripple animation when clicking where they are expected to be.

Screenshot_20160326-144553 Screenshot_20160326-142621 Screenshot_20160326-142540

Nice to haves

It’d be nice to have state drawable support for the tiles that way you can just set the one drawable and it can react to active, inactive, and disabled. In my testing I didn’t find that to work(the docs didn’t mention that it should work).

Comments 1

Leave a Reply

Your email address will not be published. Required fields are marked *