Getting started

This page shows you how to set up your environment and build Slices in your existing app.

Download and install the Slice Viewer

The Slice Viewer is a sample app that you can use to test your Slices without implementing the SliceView API.

Note: Slice Viewer requires Android 4.4 (API level 19) or later.
If ADB is not set up properly in your environment, see the ADB guide for more information.

To install, run the following command:

adb install -r -t slice-viewer.apk

Run the Slice Viewer

You can launch the Slice Viewer either from your Android Studio project or from the command line:

Launch Slice Viewer from your Android Studio project

  1. In your project, select Run > Edit Configurations…
  2. In the top-left corner, click the green plus sign
  3. Select Android App

  4. Enter slice in the name field
  5. Select your app module in the Module dropdown
  6. Under Launch Options, select URL from the Launch dropdown
  7. Enter slice-<your slice URI> in the URL field

    Example: slice-content://com.example.your.sliceuri

  8. Click OK

Note: You can use the configuration you've just created the next time you want to launch the Slice Viewer to view your Slice

Launch the Slice Viewer tool via ADB (command line)

Run your app from Android Studio:

adb install -t -r <yourapp>.apk

View your Slice by running the following command:

adb shell am start -a android.intent.action.VIEW -d slice-<your slice URI>


Slice Viewer showing a single WiFi Slice

View all of your Slices in one place

In addition to launching a single Slice, you can view a persistent list of your Slices.

  • Use the search bar to manually search for your Slices via URI (e.g. content://com.example.android.app/hello). Each time you search, the Slice is added to the list.
  • Any time you launch the Slice Viewer tool with a Slice URI, the Slice is added to the list
  • You can swipe a Slice to remove it from the list
  • Tap the URI of the Slice to see a page containing only that Slice. This has the same effect as launching Slice Viewer with a Slice URI.

Note: Slice scrolling is disabled while displayed in the list. Launch your Slice into the single Slice Viewer to test your scrollability.


Slice Viewer showing a list of Slices

View the Slice in different modes

An app that presents a Slice can modify the SliceView#mode at runtime, so you should make sure your Slice looks as expected in each mode. Select the menu icon on the top of the page to change the mode.


Single Slice viewer with mode set to "small"

Build your first Slice

To build a Slice, open your Android Studio project, right-click your src package, and select New… > Other > Slice Provider. This creates a class that extends SliceProvider and adds the required provider entry to your AndroidManifest.xml. An example is shown below:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.app">
    ...
    <application>
        ...
        <provider android:name="MySliceProvider"
            android:authorities="com.example.android.app"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.app.slice.category.SLICE" />
            </intent-filter>
        </provider>
        ...
    </application>

</manifest>

Note: You can safely export SliceProviders, as all permission checks are handled internally.

Each Slice has an associated URI. When a surface wants to display a Slice, it sends a binding request to your app with this URI. Your app then handles this request and dynamically builds the Slice via the onBindSlice method. The surface can then display the Slice when appropriate.

Below is an example of an onBindSlice method that checks for the /hello URI path and returns a Hello World Slice:

Kotlin

override fun onBindSlice(sliceUri: Uri): Slice? {
    return if (sliceUri.path == "/hello") {
        ListBuilder(context, sliceUri, ListBuilder.INFINITY)
                .addRow { it.setTitle("URI found.") }
                .build()
    } else {
        ListBuilder(context, sliceUri, ListBuilder.INFINITY)
                .addRow { it.setTitle("URI not found.") }
                .build()
    }
}

Java

@Override
public Slice onBindSlice(Uri sliceUri) {
    // Create parent ListBuilder.
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);

    // Create RowBuilder.
    ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder(listBuilder);

    if (sliceUri.getPath().equals("/hello")) {
        rowBuilder.setTitle("URI found.");
    } else {
        rowBuilder.setTitle("URI not found.");
    }

    // Add Row to List.
    listBuilder.addRow(rowBuilder);

    // Build List.
    return listBuilder.build();
}

Use the slice run configuration that you created in the Slice Viewer tutorial section above, passing in your Slice URI (e.g. slice-content://com.android.example.slicesample/hello) of the Hello World Slice to view it in the Slice Viewer.

Interactive Slices

Similar to notifications, you can handle clicks within your Slice by attaching PendingIntents that are triggered on user interaction. The example below starts an Activity that can receive and handle those intents:

Kotlin

fun createSlice(sliceUri: Uri): Slice {
    val activityAction = createActivityAction()
    return ListBuilder(context, sliceUri, INFINITY)
            .addRow {
                it.apply {
                    setTitle("Perform action in app")
                    setPrimaryAction(activityAction)
                }
            }.build()
}

fun createActivityAction(): SliceAction {
    val intent = Intent(context, MainActivity::class.java)
    return SliceAction(PendingIntent.getActivity(context, 0, intent, 0),
            IconCompat.createWithResource(context, R.drawable.ic_home),
            "Open MainActivity."
    )
}

Java

public Slice createSlice(Uri sliceUri) {
    SliceAction activityAction = createActivityAction();
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
    ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder(listBuilder)
            .setTitle("Perform action in app.")
            .setPrimaryAction(activityAction);
    listBuilder.addRow(rowBuilder);
    return listBuilder.build();
}

public SliceAction createActivityAction() {
    Intent intent = new Intent(getContext(), MainActivity.class);
    return new SliceAction(PendingIntent.getActivity(getContext(), 0, intent, 0),
            IconCompat.createWithResource(getContext(), R.drawable.ic_home),
            "Open MainActivity");
}

Slices also support other input types, such as toggles, that include state in the intent that is sent to the app.

Kotlin

fun createBrightnessSlice(sliceUri: Uri): Slice {
    val toggleAction =
            SliceAction(createToggleIntent(), "Toggle adaptive brightness", true)
    return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
            .addRow {
                it.apply {
                    setTitle("Adaptive brightness")
                    setSubtitle("Optimizes brightness for available light")
                    setPrimaryAction(toggleAction)
                }
            }.addInputRange {
                it.apply {
                    setInputAction(brightnessPendingIntent)
                    setMax(100)
                    setValue(45)
                }
            }.build()
}

fun createToggleIntent(): PendingIntent {
    val intent = Intent(context, MyBroadcastReceiver::class.java)
    return PendingIntent.getBroadcast(context, 0, intent, 0)
}

Java

public Slice createBrightnessSlice(Uri sliceUri) {
    SliceAction toggleAction = new SliceAction(createToggleIntent(), "Toggle adaptive brightness", true);
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
    ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder(listBuilder)
            .setTitle("Adaptive brightness")
            .setSubtitle("Optimizes brightness for available light.")
            .setPrimaryAction(toggleAction);

    listBuilder.addRow(rowBuilder);

    ListBuilder.InputRangeBuilder inputRangeBuilder = new ListBuilder.InputRangeBuilder(listBuilder)
            .setInputAction(brightnessPendingIntent)
            .setMax(100)
            .setValue(45);

    listBuilder.addInputRange(inputRangeBuilder);

    return listBuilder.build();
}

public PendingIntent createToggleIntent() {
    Intent intent = new Intent(getContext(), MyBroadcastReceiver.class);
    return PendingIntent.getBroadcast(getContext(), 0, intent, 0);
}

The receiver can then check the state that it receives:

Kotlin

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
            Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                    Slice.EXTRA_TOGGLE_STATE, false),
                    Toast.LENGTH_LONG).show()
        }
    }

    companion object {
        const val EXTRA_MESSAGE = "message"
    }
}

Java

public class MyBroadcastReceiver extends BroadcastReceiver {

    public static String EXTRA_MESSAGE = "message";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(EXTRA_TOGGLE_STATE)) {
            Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                    EXTRA_TOGGLE_STATE, false),
                    Toast.LENGTH_LONG).show();
        }
    }
}

Dynamic Slices

Slices can also contain dynamic content. In the example below, the Slice now includes the number of broadcasts received in its content:

Kotlin

fun createDynamicSlice(sliceUri: Uri): Slice {
    return when (sliceUri.path) {
        "/count" -> {
            val toastAndIncrementAction = SliceAction(createToastAndIncrementIntent("Item clicked"),
                    actionIcon, "Increment.")
            ListBuilder(context, sliceUri, ListBuilder.INFINITY)
                    .addRow {
                        it.apply {
                            setPrimaryAction(toastAndIncrementAction)
                            setTitle("Count: ${MyBroadcastReceiver.receivedCount}")
                            setSubtitle("Click me")
                        }
                    }
                    .build()
        }
        else -> {
            ListBuilder(context, sliceUri, ListBuilder.INFINITY)
                    .addRow { it.setTitle("URI not found.") }
                    .build()
        }
    }
}

fun createToastAndIncrementIntent(s: String): PendingIntent {
    return PendingIntent.getBroadcast(context, 0,
            Intent(context, MyBroadcastReceiver::class.java)
                    .putExtra(MyBroadcastReceiver.EXTRA_MESSAGE, s), 0)
}

Java

public Slice createDynamicSlice(Uri sliceUri) {
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
    ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder(listBuilder);
    switch (sliceUri.getPath()) {
        case "/count":
            SliceAction toastAndIncrementAction = new SliceAction(createToastAndIncrementIntent(
                    "Item clicked."), actionIcon, "Increment.");
            rowBuilder.setPrimaryAction(toastAndIncrementAction)
                    .setTitle("Count: " + MyBroadcastReceiver.sReceivedCount)
                    .setSubtitle("Click me");
            listBuilder.addRow(rowBuilder);
            break;
        default:
            rowBuilder.setTitle("URI not found.");
            listBuilder.addRow(rowBuilder);
            break;
    }
    return listBuilder.build();
}

public PendingIntent createToastAndIncrementIntent(String s) {
    Intent intent = new Intent(getContext(), MyBroadcastReceiver.class)
            .putExtra(MyBroadcastReceiver.EXTRA_MESSAGE, s);
    return PendingIntent.getBroadcast(getContext(), 0, intent, 0);
}

In this example, while the count is shown, it doesn’t update on its own. You can modify your broadcast receiver to notify the system that a change has occurred by using ContentResolver#notifyChange.

Kotlin

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
            Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                    Slice.EXTRA_TOGGLE_STATE, false),
                    Toast.LENGTH_LONG).show()
            receivedCount++;
            context.contentResolver.notifyChange(sliceUri, null)
        }
    }

    companion object {
        var receivedCount = 0
        val sliceUri = Uri.parse("content://com.android.example.slicesample/count")
        const val EXTRA_MESSAGE = "message"
    }
}

Java

public class MyBroadcastReceiver extends BroadcastReceiver {

    public static int sReceivedCount = 0;
    public static int receivedCount = 0;
    public static String EXTRA_MESSAGE = "message";

    private static Uri sliceUri = Uri.parse("content://com.android.example.slicesample/count");

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(EXTRA_TOGGLE_STATE)) {
            Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                    EXTRA_TOGGLE_STATE, false),
                    Toast.LENGTH_LONG).show();
            sReceivedCount++;
            context.getContentResolver().notifyChange(sliceUri, null);
        }
    }
}

Templates

Slices support a variety of templates. For more details on template options and behaviors, see Templates.