In this codelab, you'll learn how to register a custom tile in the Quick Settings of an Android device.

What you'll learn

What you'll need

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would you rate your experience with building Android apps?

Novice Intermediate Proficient

You can either download all the sample code to your computer...

Download Zip

...or clone the GitHub repository from the command line.

$ git clone https://github.com/googlecodelabs/android-n-quick-settings.git

First, let's see what the finished sample app looks like. With the code downloaded, the following instructions describe how to open the completed sample app in Android Studio.

  1. Download the project ZIP file and decompress, if you haven't already.
  2. Open Android Studio. At the welcoming page, select

    Open an existing Android Studio project
    .
  3. Navigate to where you extracted the file android-n-quick-settings-master/ and click Open.
  4. Click the Gradle sync button.
  5. Enable USB debugging on your Android device.
  6. Switch the project view to Project.
  7. Select the app/ folder and then click Build > Select Build Variant.
  8. In the Build Variants panel, select startDebug.
  9. Plug in your Android device and click the Run button. You should see the Quick Settings Demo home screen appear after a few seconds.
  10. Swipe down from the top of the device screen to open the notification shade.
  11. Expand the shade and click Edit at the bottom right-hand corner.
  12. From the bottom of the Edit shade, drag the Quick Settings Sample tile to the top part of the shade.
  13. In Android Studio, open the Android Monitor panel. In the logcat output, you should see a message like the following.
05-12 22:25:44.207 12757-12757/com.google.android_quick_settings.start D/QS: Start listening

Try moving the Quick Settings Demo tile from the active set of tiles to the inactive set of tiles on the notification shade and opening / closing the notification shade. You should see the output in the logcat change as the tile state changes.

Notice when the tile state changes to 'start listening' and 'stop listening'. These events correspond to when the tile can accept input - that is, once it has been added to the shade and the notification shade has opened.

Frequently Asked Questions

Before we go to much farther, let's take a look at the definition for the TileService in the Android manifest. In the Project pane, open app/src/main/AndroidManifest.xml.

AndroidManifest.xml

<!-- TileService for "Update the Tile UI" section -->

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

To register a Quick Settings tile and corresponding service with the Android system, your manifest must include the service with a reference to the class that handles the tile. It must also include the android.permission.BIND_QUICK_SETTINGS_TILE permission and the android.service.quicksettings.action.QS_TILE intent filter.

Next, let's take a look at how we can interact with the tile from the TileService at runtime. The following instructions

  1. Make sure your device is still setup for USB debugging as we saw in the previous step.
  2. In the app/src/start/java/com.google.android_quick_settings/ directory, open QuickSettingsService.java.

In the class definition for QuickSettingsService, replace the override for the onClick method with the following code.

QuickSettingsService.java

@Override
public void onClick() {
    Log.d("QS", "Tile tapped");
    updateTile();
}


Next, we'll add some code that updates the appearance of the tile. It gets a reference to the tile at runtime, changes the icon and the label, and the state of the tile based upon persisted data.

QuickSettingsService.java

private static final String SERVICE_STATUS_FLAG = "serviceStatus";
private static final String PREFERENCES_KEY = 
    "com.google.android_quick_settings";

// Other class members ...

// Changes the appearance of the tile.
private void updateTile() {

    Tile tile = this.getQsTile();
    boolean isActive = getServiceStatus();

    Icon newIcon;
    String newLabel;
    int newState;

    // Change the tile to match the service status.
    if (isActive) {

        newLabel = String.format(Locale.US,
                       "%s %s",
                       getString(R.string.tile_label),
                       getString(R.string.service_active));

        newIcon = Icon.createWithResource(getApplicationContext(),
                      R.drawable.ic_android_black_24dp);

        newState = Tile.STATE_ACTIVE;

    } else {
        newLabel = String.format(Locale.US,
                "%s %s",
                getString(R.string.tile_label),
                getString(R.string.service_inactive));

        newIcon =
                Icon.createWithResource(getApplicationContext(),
                        android.R.drawable.ic_dialog_alert);

        newState = Tile.STATE_INACTIVE;
    }

    // Change the UI of the tile.
    tile.setLabel(newLabel);
    tile.setIcon(newIcon);
    tile.setState(newState);

    // Need to call updateTile for the tile to pick up changes.
    tile.updateTile();
}

Now we need to access and change the persisted data for the tile service. In the following code snippet, the app gets data from SharedPreferences, updates the setting, and then writes the updated setting back to SharedPreferences.

QuickSettingsService.java

// Access storage to see how many times the tile
// has been tapped.
private boolean getServiceStatus() {

    SharedPreferences prefs =
            getApplicationContext()                        
                .getSharedPreferences(PREFERENCES_KEY, MODE_PRIVATE);

    boolean isActive = prefs.getBoolean(SERVICE_STATUS_FLAG, false);
    isActive = !isActive;

    prefs.edit().putBoolean(SERVICE_STATUS_FLAG, isActive).apply();

    return isActive;
}

Finally, run the code again to see how this code works.

  1. Plug in your Android device and click the Run button. You should see the Quick Settings Demo home screen appear after a few seconds.
  2. Swipe down from the top of the device screen to open the notification shade.
  3. Tap the Quick Settings Demo tile repeatedly. You should see the tile icon, state, and label change as you tap.

As you can see, a custom Quick Settings service can access context and resources associated with your app. Your Quick Settings service can access the shared preferences for the application, allowing your app to retain state in response to the user interaction.

Also notice how much you can customize the Quick Settings tile itself at runtime. Through the android.service.quicksettings.Tile class, you can get and set the label, icon, content message, and state (make the tile "light up" or "grayed out") of the tile.

Frequently Asked Questions

Next you're going to create your own Quick Settings service that launches a dialog in response to a tile being tapped. The setting in the Android manifest file has already been added for you in the sample app.

First, let's add the java file that contains the code for the new TileService class.

  1. In the Project panel, select the app/src/start/java/com.google.android_quick_settings/ folder.
  2. Click File > New > Java Class. In the Create New Class dialog box, enter 'QSDialogService'.
  3. In the new Java file, replace the contents of the file with the following code.

    QSDialogService.java
package com.google.android_quick_settings;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.DialogFragment;
import android.os.Build;
import android.os.Bundle;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.util.Log;


@SuppressLint("Override")
@TargetApi(Build.VERSION_CODES.N)
public class QSDialogService
   extends TileService {
    
    // Class definition

}

As you see, a TileService that corresponds to a Quick Settings tile must inherit from the android.service.quicksettings.TileService class.

From the Quick Settings TileService class, you can launch a dialog box easily in response to a click event. Within the code for the TileService.onClick() method, you only need to create a new dialog and pass the dialog object to the TileService.showDialog() method. Your TileService class needs to apply listeners to the dialog object to react when the user selects an option in the dialog.

The dialog itself for a Quick Settings tile requires more work. A dialog launched from a Quick Settings tile does not have a host activity. Thus, when it creates the dialog, the system doesn't call the DialogFragment.onAttach method - where you would normally add event handlers. Also, although you can use a layout file to define the UI of the dialog, you can't use Activity.findViewById()method to access a View within the layout.

Next we'll add the implementation to the QSDialogService that we created earlier. We need to respond when the user clicks the tile by launching a dialog box. Before we show the dialog box, we need to get some data about the current state of the Quick Settings tile and apply event listeners to the dialog. In the class definition for the QSDialogService, add the following code.

QSDialogService.java

private boolean isTileActive;

@Override
public void onClick(){

   // Get the tile's current state.
   Tile tile = getQsTile();
   isTileActive = (tile.getState() == Tile.STATE_ACTIVE);

   QSDialog.Builder dialogBuilder =
           new QSDialog.Builder(getApplicationContext());

   QSDialog dialog = dialogBuilder
           .setClickListener(new QSDialog.QSDialogListener() {

               @Override
               public void onDialogPositiveClick(DialogFragment dialog) {
                   Log.d("QS", "Positive registed");

                   // The user wants to change the tile state.
                   isTileActive = !isTileActive;
                   updateTile();
               }

               @Override
               public void onDialogNegativeClick(DialogFragment dialog) {
                   Log.d("QS", "Negative registered");

                   // The user is cancelled the dialog box.
                   // We can't do anything to the dialog box here,
                   // but we can do any cleanup work.
               }
           })
           .create();

   // Pass the tile's current state to the dialog.
   Bundle args = new Bundle();
   args.putBoolean(QSDialog.TILE_STATE_KEY, isTileActive);

   this.showDialog(dialog.onCreateDialog(args));
}

After the user has made a selection in the dialog box, we need to respond to their choice. The following code updates the Quick Settings tile based upon the user's input.
QSDialogService.java

private void updateTile() {
    Tile tile = super.getQsTile();
    int activeState = isTileActive ?
        Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;

    tile.setState(activeState);
    tile.updateTile();
}

Next, run the code again to see how this code works.

  1. Plug in your Android device and click the Run button. You should see the Quick Settings Demo home screen appear after a few seconds.
  2. Swipe down from the top of the device screen to open the notification shade.
  3. (First time) Add the QS Dialog Launcher tile to the notification shade.
  4. Tap the QS Dialog Launcher tile.

    A dialog should launch that prompts you whether you want to change the QS Dialog Launcher tile state.

Frequently Asked Questions

In this final section, you'll create another Quick Settings TileService. This TileService class launches an activity in response to the user tapping the tile. You might use a pattern like this when you need to take the user directly to your app from the Quick Settings tile.

First, let's add the java file that contains the code for this TileService class.

  1. In the Project panel, select the app/src/start/java/com.google.android_quick_settings/ folder.
  2. Click File > New > Java Class. In the Create New Class dialog box, enter 'QSIntentService'.
  3. In the new Java file, replace the class declaration with the following code.

    QSIntentService.java
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;


@SuppressLint("Override")
@TargetApi(Build.VERSION_CODES.N)
public class QSIntentService
   extends TileService {
    
    @Override
    public void onClick() {

        // Implementation details.

    }
}

Before launching an intent, you needs to determine if the device lock is set. If it has set, the service cannot launch the intent without the user unlocking the device.

To check the lock state of the device, the TileService class provides an isLocked() method. Like the name implies, the method returns a simple boolean depending on whether the device lock has set.

After determining the lock state of the device, you can build an intent to launch your activity and start the activity. When the activity launches, you want the notifications shade to collapse so that the user has their attention on the activity UI. To launch the activity and dismiss the notification shade, you use the TileService.startActivityAndCollapse() method.

To do this, paste the following code into the implementation of the onClick() event that you added to the QSIntentService class, as shown in the following code sample. The code sample already includes a ResultActivity that you can use.

QSIntentService.java

// Check to see if the device is currently locked.
boolean isCurrentlyLocked = this.isLocked();

if (!isCurrentlyLocked) {

    Resources resources = getApplication().getResources();

    Tile tile = getQsTile();
    String tileLabel = tile.getLabel().toString();
    String tileState = (tile.getState() == Tile.STATE_ACTIVE) ?
                    resources.getString(R.string.service_active) :
                    resources.getString(R.string.service_inactive);

    Intent intent = new Intent(getApplicationContext(),
        ResultActivity.class);

    intent.putExtra(ResultActivity.RESULT_ACTIVITY_NAME_KEY,
        tileLabel);
    intent.putExtra(ResultActivity.RESULT_ACTIVITY_INFO_KEY,
        tileState);
    intent.addFlag.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    startActivityAndCollapse(intent);
}

Next, run the code again to see how this code works.

  1. Plug in your Android device and click the Run button. You should see the Quick Settings Demo home screen appear after a few seconds.
  2. Swipe down from the top of the device screen to open the notification shade.
  3. (First time) Add the QS Intent Launcher tile to the notification shade.
  4. Tap the QS Intent Launcher tile. The notification shade should disappear and the Quick Settings Result Activity appear in its place..

Frequently Asked Questions

Your app can now register a Quick Settings tile that launches a service, a dialog, or even an activity within your application.

What we've covered