Nougat has now been released in to the wild and there are a number of new features which developers should be considering. In this loosely-related series of articles we’re going to be looking at various aspects of these new features to see how we can best make use of them. In this article we’re going to look at how we can surface our app configuration in to the Quick Settings panel.
In the previous article we set up a dummy messaging Service which would, at random intervals, post messages. With long running services such as this, which consume battery and data ,it is often a good idea to provide the user with a mechanism to disable the Service and often this can be done with a toggle switch within the app. However, with Nougat, a new API has been introduced which enables us to surface simple settings from our apps in to the Quick Settings panel and we’ll use it to surface a mechanism to enable and disable our Service to the Quick Settings panel.
Before we start it is worth considering the use cases where a quick settings option is relevant to the user. It should really be something which could affect the device as a whole – in our example app a real MessengerService implementation (rather than our dummy one) may use network resources and consume battery which could use both user up the users data allowance and / or drain the batter faster. In this case surfacing a toggle to turn this Service on and off is appropriate. However for something which is intrinsic to your app, such as changing the visual theme of your app, this would still be better off being controlled from within the app itself.
Furthermore, the user has to manually add your Quick Settings Tile to their Quick Settings panel. There is no way for us to automatically enable this. That makes discoverability of our toggle a little bit harder. For that reason we should provide the same toggle from within the app itself. That serves two purposes – it maintains backwards compatibility to pre-Nougat where the Quick Settings Tile API does not exist; also, providing multiple ways of achieving the same thing will increase the chances of the user finding one of them.
Before we dive in to the code, let’s take a quick look at how this behaves from the user perspective:
As we have already discussed: for the tile to actually become active, the user must physically drag it to the quick settings panel. But once it’s there, tapping it will toggle the service enablement, and also the icon will change to indicate whether the service is running or not.
So let’s dive in to the code. At the heart of things is a Service which subclasses TileService:
@TargetApi(Build.VERSION_CODES.N) public class MessengerTileService extends TileService { private ServiceScheduler serviceScheduler; @Override public void onCreate() { super.onCreate(); serviceScheduler = ServiceScheduler.newInstance(this); } @Override public void onStartListening() { super.onStartListening(); if (serviceScheduler.isEnabled()) { updateTileState(Tile.STATE_ACTIVE); } else { updateTileState(Tile.STATE_INACTIVE); } } private void updateTileState(int state) { Tile tile = getQsTile(); if (tile != null) { tile.setState(state); Icon icon = tile.getIcon(); switch (state) { case Tile.STATE_ACTIVE: icon.setTint(Color.WHITE); break; case Tile.STATE_INACTIVE: case Tile.STATE_UNAVAILABLE: default: icon.setTint(Color.GRAY); break; } tile.updateTile(); } } @Override public void onClick() { super.onClick(); Tile tile = getQsTile(); switch (tile.getState()) { case Tile.STATE_INACTIVE: serviceScheduler.startService(); updateTileState(Tile.STATE_ACTIVE); break; case Tile.STATE_ACTIVE: serviceScheduler.stopService(); default: updateTileState(Tile.STATE_INACTIVE); break; } } @Override public void onDestroy() { serviceScheduler = null; super.onDestroy(); } }
We need to specify @TargetApi(Build.VERSION_CODES.N)
because this is a Nougat and later API, but this simply won’t get invoked on older devices because the IntentFilter (which we’ll look at shortly) simply won’t match anything on older devices.
There are a number of callbacks here which get invoked at different points within the Tile’s lifecycle. onCreate()
and onDestroy()
are the standard Service lifecycle calls. onStartListening()
is called whenever the quick settings panel is displayed, and this Tile in question has been added – there’s a symmetrical lifecycle method named onStopListening()
which is called when the quick settings panel is hidden again. In our code we initialise the icon state according to whether the service is currently enabled.
onClick()
will be called when the user taps on our tile, and we toggle the service state, and update the icon state accordingly.
There are also two further lifecycle callbacks onTileAdded()
and onTileRemoved()
which get called when the user adds or removes the Tile from the quick settings panel. There is also a method named startActivityAndCollapse()
which will launch an arbitrary Activity and close the notification drop down. I have yet to find a case where I feel this would be an appropriate thing to do – it’s essentially putting a shortcut in to the quick settings panel feel wrong.
The only other method here is updateTileState()
which is an internal method that we use to update appearance of the icon according to the state of the service. We simply apply different tint colours to the icon to give a visual indication of whether the service is enabled or not. It is possible to show different icons to represent different states by calling setStatusIcon()
but just applying different tints is more than adequate for this case.
Once again, because this is a Service, we need to add it to our manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.stylingandroid.nougat"> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> . . . <service android:name=".MessengerTileService" android:icon="@drawable/ic_message" android:label="@string/toggle_messages" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> <intent-filter> <action android:name="android.service.quicksettings.action.QS_TILE" /> </intent-filter> </service> </application> </manifest>
We must specify the BIND_QUICK_SETTINGS_TILE
permission and the QS_TILE IntentFilter action. Remember what we said earlier about this on pre-Nougat devices? This action will be undefined on older devices, so the MessengerTileService will never be run on pre-Nougat devices.
Aside from adding the icon image and some label text we’re done. That’s all there is to it. However, remember to use this sparingly. If all apps surface lots of settings to the quick status panel then there will be far too many for the user to find which ones may be important to him or her. We should only be using this for settings which may affect the device as a whole, and not as a replacement for properly implemented settings within the app itself.
In the next article we’ll turn our attention to how we should behave following a reboot.
The source code for this article is available here.
© 2016, Mark Allison. All rights reserved.
Copyright © 2016 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.