Direct Share to an Android app

1. Introduction

Direct Share is a feature that allows apps to show app-specific options directly in the system Intent chooser dialog. Users can then jump directly into your app when sharing content from another app. For example, a messaging app using Direct Share could enable the user to share content direct to a contact, which appears in the chooser dialog. Direct Share makes sharing content quicker and easier.

4272b0cacbab9093.png

In the image, you can see how the user can quickly select Tereasa or Chang to share the text "Hello!" directly to an app using Direct Share, without having to browse through the list of contacts.

Direct Share works with the concept of Sharing Shortcuts. The app can publish sharing targets in advance, allowing the system Intent chooser dialog to show them when required. To publish share targets, we will use the ShortcutManager API. Any published sharing shortcuts are persisted by the system until the app updates them, or the app is uninstalled.

When shown to the user, the system ranks any applicable shortcuts using a prediction service, which shows shortcuts which are more likely to be used.

This codelab will walk you through implementing Direct Share in your app, including making it backwards compatible with older Android versions.

What you will build

In this codelab, you will work with a messaging app that can receive Intents which contain plain text. When a user shares some text from some other app (or the one we're building), this app will be listed as an option. By using the Direct Share feature, this app also publishes some contacts shown in the system Intent chooser dialog.

You can see the sample app below:

1a2f836312a50861.gif

All classes and functionality have already been created for you. You will implement Direct Share specific logic to:

  • Publish sharing shortcuts with a specific category
  • Make Direct Share backwards compatible with older Android versions
  • Add a title and thumbnail to the content preview

What you will learn

  • How to implement Direct Share in your app
  • How to make Direct Share backwards compatible with older Android versions
  • How to show content preview in shared content

Prerequisites

  • Basic Kotlin knowledge (this codelab is in Kotlin)
  • Android Studio 3.3 or higher
  • Emulator or device running API 21+

If you run into any issues (code bugs, grammatical errors, unclear wording, etc.) as you work through this codelab, please report the issue via the Report a mistake link in the lower left corner of the codelab.

2. Getting Started

Get the Code

Get the Direct Share codelab from GitHub:

$ git clone https://github.com/android/codelab-android-direct-share

Alternatively you can download the repository as a Zip file:

Get Android Studio 3.3 or higher

Make sure you are using Android Studio 3.3 or higher.

If you need to download a recent version of Android Studio, you can do so here.

Project set up

The preferred way to implement the Direct Share feature is using the Android Q API's capabilities. This is why your compileSdkVersion needs to be at least "29".

To follow the codelab, open the root folder with Android Studio. It will contain two sub-projects inside it:

  • direct-share-start codelab's starting point.
  • direct-share-done solution to the codelab.

3. Overview of the sample app

This messaging app is able to:

  • Share plain text via ACTION_SEND Intents
  • Listen for plain text Intents to send messages to contacts
  • If there's no contact selected, the user can select a contact

Running the sample app

If you run direct-share-start-app, the Share screen (MainActivity.kt) will launch: Type anything you want on the EditText and click Share to share that text to any app.

a591d10e2db09026.png

Tapping on Share will trigger an Intent to the system and a system Intent dialog chooser will appear.

f8f107984daca635.png

Select contact screen (SelectContactActivity.kt): When the user selects the "Direct Share" messaging app to process the shared text, the user can choose who to share the message with. Notice that the user has to select the app, not a specific contact.

1b36a20eb107b020.png

Send message screen (SendMessageActivity.kt): Click send to send a message to a contact. The app will display a Toast as a signal that the message was sent.

14e2b1d9aa12bbb1.png

4. Declaring Sharing Shortcuts

Direct Share uses the ShortcutManager API to publish sharing shortcuts. It's similar to the App Shortcuts feature.

Let's start working on the app and implement Direct Share. Start with the direct-share-start sub-project.

There are two main steps to follow:

  1. Declare share-target elements in the application's shortcuts xml resource.
  2. Publish dynamic shortcuts with matching categories to the declared share-targets with the ShortcutManager API.

Share targets declaration

A share target defines the metadata of a sharing shortcut, including:

  • Information about the shared data type
  • Categories that can be associated with the sharing shortcut
  • Activity that will handle the share Intent

Share targets must be declared in the application's resource file where static shortcuts are also defined. In our example, you can find it in app/src/main/res/xml/shortcuts.xml.

You don't have to do anything at the moment, this file is already available in the project. You can open and see it.

shortcuts.xml

<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <share-target android:targetClass="com.example.android.directshare.SendMessageActivity">
        <data android:mimeType="text/plain" />
        <category android:name="com.example.android.directshare.category.TEXT_SHARE_TARGET" />
    </share-target>
</shortcuts>

Share target definitions are added inside the <shortcuts> root element in the resource file along with other possible static shortcut definitions.

Data element in share-target is similar to the data specification in an intent filter.

Each share-target can have multiple associated categories, which will be solely used to match published shortcuts of an app with its share target definitions. Categories can be any arbitrary string. Internally, the framework will match the category of the sharing shortcut with the category given in the xml resource of the app. The target class will handle the share Intent.

The shortcuts.xml file needs to be declared in an activity whose intent filters are set to the android.intent.action.MAIN action and the android.intent.category.LAUNCHER category in the AndroidManifest.xml file.

Update the AndroidManifest.xml file to declare share targets

Update the code to define share targets in the app.

  1. Open the AndroidManifest.xml file
  2. Uncomment Step 4 to add a <meta-data> element to the MainActivity that references the resource file where the app's share targets are defined

AndroidManifest.xml

<activity
    android:name=".MainActivity"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <!-- Reference resource file where the app's shortcuts are defined -->
    <meta-data
        android:name="android.app.shortcuts"
        android:resource="@xml/shortcuts" />
</activity>

5. Using the ShortcutManager API

Once the share target elements are defined, we need to publish dynamic shortcuts that match those definitions with the ShortcutManager API. The sharing shortcuts are persisted in the system until they are updated by the same application or the application is uninstalled. You will need to manually update the list of shortcuts every time you consider it appropriate (e.g. you coud update them either when the user opens the app or the most recent conversations change in a messaging app). The API offers methods to update, remove or add shortcuts.

In the codelab, we use ShortcutManagerCompat available in AndroidX to publish shortcuts since it provides backwards compatibility down to Android M. Use it in your project by adding the androidx:core dependency to your project.

See the dependency already added in the direct-share-start-app/app/build.gradle or build.gradle (Module: direct-share-start-app) file as follows:

build.gradle

implementation "androidx.core:core:${versions.androidxCore}"

Introducing SharingShortcutsManager

The class in charge of interacting with the ShortcutManager in the app is the SharingShortcutsManager. Every time that the user opens the app (i.e. when MainActivity gets launched), we push our sharing shortcuts.

You can see how we interact with it in the MainActivity class:

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    ...
    sharingShortcutsManager = SharingShortcutsManager().also {
        it.pushDirectShareTargets(this)
    }
}

Create categories that match your share-target definition

One of the parameters that we can configure is the list of categories associated to a shortcut. All the shortcuts that we create in this codelab are of the same type. And the type has to match with what we defined previously in the shortcuts.xml file. In this case we defined that SendMessageActivity was associated with category TEXT_SHARE_TARGET in shortcuts.xml.

If you open SharingShortcutsManager.kt, you can see the category defined in a constant variable.

SharingShortcutsManager.kt

/**
 * Category name defined in res/xml/shortcuts.xml that accepts data of type text/plain
 * and will trigger [SendMessageActivity]
 */
private val categoryTextShareTarget = "com.example.android.directshare.category.TEXT_SHARE_TARGET"

Go to the pushDirectShareTargets method now and uncomment the Step 5 code that creates a set of categories that the shortcuts are associated with:

SharingShortcutsManager.kt

fun pushDirectShareTargets(context: Context) {
    ...
    // Category that our sharing shortcuts will be assigned to
    val contactCategories = setOf(categoryTextShareTarget)
    ...
}

6. Anatomy of a Shortcut

Let's explore what we can configure in a shortcut.

Uncomment the code in Step 6 that creates shortcuts and adds them to the list of shortcuts that will get published by the ShortcutManagerCompat. We will go through the code in more detail in this section.

For the simplicity of the sample, we always add the same first four contacts that are defined in the Contact.kt file. If you open it, you can see we create an array with ten contacts and methods to retrieve them.

In the uncommented code, we create a shortcut using the ShortcutInfoCompat.Builder. This makes use of the traditional Builder pattern. It takes a Context object as a parameter which in this case is the Activity that calls this method and a String id.

The id parameter is important since it will identify this shortcut when the target activity receives the sharing intent. The activity will get the id with the EXTRA_SHORTCUT_ID Intent extra. In our case, the id will be the Contact Id which is represented by the position it occupies in the array.

ShortcutInfoCompat.Builder(context, Integer.toString(id))

Next, we configure the short label and the icon that will get displayed in the system Intent chooser dialog.

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        .setShortLabel(contact.name)
        // Icon that will be displayed in the share target
        .setIcon(IconCompat.createWithResource(context, contact.icon))

You can see in the image below how setting these attributes modify the UI displayed in the system Intent dialog chooser.

2f68efb77e7a8317.png

The intent defined in the ShortcutInfoCompat builder is triggered only if the shortcut is opened as a static shortcut. This is important to understand to avoid confusion in the future.

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        ...
        .setIntent(staticLauncherShortcutIntent)

To differentiate that Intent with the one received as a sharing shortcut in this example, we are defining it with an ACTION_DEFAULT action.

// Item that will be sent if the shortcut is opened as a static launcher shortcut
val staticLauncherShortcutIntent = Intent(Intent.ACTION_DEFAULT)

If a shortcut is long-lived, it can be cached by various system services and can appear as a sharing target even if it has been unpublished or removed by the app.

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        // Make this sharing shortcut cached by the system
        // Even if it is unpublished, it can still appear on the sharesheet
        .setLongLived(true)

Categories will be used to filter shortcuts which can handle various share intents or actions. This field is required for shortcuts that are intended to be used as share targets. To assign the category we created before, use the following method:

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        .setCategories(contactCategories)

You can also assign people to shortcuts. This is used for better understanding of user behavior across different apps, and help potential prediction services in the Android framework to provide better suggestions in the chooser dialog. Adding Person info to a shortcut is optional, but strongly recommended. Note that not all share targets can be associated with a person (e.g. share to cloud).

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        // Person objects are used to give better suggestions
        .setPerson(
                Person.Builder()
                        .setName(contact.name)
                        .build()
        )

With all this configuration, our shortcut is ready and we can build it.

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        .build()

The code in Step 6 has created four sharing shortcuts that can already be published.

7. Publishing sharing shortcuts

Now that we have four sharing targets in the array, we can publish them using the ShortcutManagerCompat. Uncomment Step 7 from the SharingShortcutsManager.kt file:

SharingShortcutsManager.kt

ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)

Triggering the Intent

If the user selects one of our app's Direct Share contacts in the system Intent dialog chooser that matches the share-target that we defined above. The following intent will be triggered:

Action: Intent.ACTION_SEND
ComponentName: { com.example.android.directshare /
                  com.example.android.directshare.SendMessageActivity}
Data: Uri to the shared content
EXTRA_SHORTCUT_ID: <ID of the selected shortcut>

Receiving the Intent

SendMessageActivity will receive the Intent as it was defined in the shortcuts.xml file. Apart from that, the activity also needs to define that is handling that type of Intents in the AndroidManifest.xml file. That is specified in the <intent-filter> tag inside the SendMessageActivity manifest declaration as you can see in the following code:

AndroidManifest.xml

<activity
    android:name=".SendMessageActivity"
    android:label="@string/app_name"
    android:theme="@style/DirectShareDialogTheme">
    <!-- This activity can respond to Intents of ACTION_SEND and with text/plain data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>

Open the SendMessageActivity.kt class, let's go through the code. When it starts, it checks the information received in the Intent. If there's no information about the contact, then it launches the SelectContactActivity to select a contact. It will receive the contact selected information in the onActivityResult method.

Running the App

At this point, we can run the app with Steps 1, 2 and 3 uncommented. Since we haven't made it backwards compatible with older Android versions yet, we need to run it with an Android 10 device.

Choosing the app in the chooser dialog

As explained above, if we choose our app to receive the shared text, the user is prompted to select a contact. After selecting the contact, the user can send the shared message.

1c756facb7566057.gif

Choosing a Direct Share Contact in the chooser dialog

If we choose a Contact that was published by the Direct Share app, SendMessageActivity can get the ID of the contact with Intent.EXTRA_SHORTCUT_ID. Check the method handleIntent in SendMessageActivity to see how it is implemented.

SendMessageActivity.kt

val shortcutId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)

2bbc2b76794c6c75.gif

Launching a Direct Share launcher shortcut

If we open one of the sharing shortcuts we published as a launcher shortcut, the system will trigger the Intent.ACTION_DEFAULT Intent that we defined in the ShortcutInfoCompat.Builder.

cb975dc2d172b6a.gif

8. Making Direct Share backwards compatible

Direct Share was first introduced in Android M where you had to implement a service extending ChooserTargetService to provide direct share targets on demand. The way to do that changed in Android Q where we provide direct share targets in advance with the ShortcutManager API.

If we run the app on a device with API level 23-28, we can see that the app is listed as an option but there are no direct targets:

7ef6f7bafb63cdb.png

We have already mentioned backwards compatibility in previous sections when introducing the ShortcutManagerCompat available in AndroidX. There's still more work to be done though:

  1. Open the direct-share-start-app/app/build.gradle (or build.gradle (Module: direct-share-start-app)file. There you can see some AndroidX dependencies related to Direct Share. As before, the core dependency includes ShortcutManagerCompat, and the sharetarget dependency will implement the ChooserTargetServiceCompat service which enables it to work on older Android versions. If you are implementing this in your project, you need these dependencies (or newer).

app/build.gradle

implementation 'androidx.core:core:1.2.0-beta01'
implementation 'androidx.sharetarget:sharetarget:1.0.0-beta01'
  1. Open the AndroidManifest.xml file and go to the SendMessageActivity declaration. Since it receives Intents coming from sharing shortcuts (Direct Share), it needs to provide a <meta-data> tag with the name: android.service.chooser.chooser_target_service.
  2. Uncomment Step 8 to include the service for the backwards compatibility feature.

AndroidManifest.xml

<activity
    android:name=".SendMessageActivity">
    ...
    <meta-data
        android:name="android.service.chooser.chooser_target_service"
        android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
</activity>

Both android.service.chooser.chooser_target_service and androidx.sharetarget.ChooserTargetServiceCompat values are static and always the same. If in your app you handle sharing intents with multiple activities, you will have to add this <meta-data> to all of them.

With those steps, Direct Share is now backwards compatible.

Running the App

If we implement the steps above then Direct Share works correctly on devices with API level 23-28:

62a9b6a5751c9802.png

If we run the App on an Android L device, that doesn't have Direct Share, the app will still be listed as an option.

c779d94e0ffbd6fa.png

9. Content Preview

When an app shares content, you can show an optional preview of it in Android Q+. The preview can have a title, image, or both.

When sharing images

When using both ACTION_SEND and ACTION_SEND_MULTIPLE along with URIs being shared via EXTRA_STREAM, the system will inspect the mime type, and attempt to render image/file previews in the preview area.

No additional fields are needed. To get proper image preview, please also provide the contentUri in the clipData, as the Intent.ACTION_SEND reference doc specifies.

When sharing text

The previous way of adding a title to the shared content is deprecated. You need to pass the title as an Intent extra using Intent.EXTRA_TITLE.

  1. Open MainActivity.kt and find the share method. Uncomment Step 9.1 to add a title to the shared content.

MainActivity.kt

sharingIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.send_intent_title))

If you run the app, you will see that Send message is the title of the shared content Hello!.

99404bc94b7198c9.png

To add a thumbnail to the title, create a content URI with the image you want to display. Set the Uri with the Intent.setClipData(contentUri) method and set the Intent.FLAG_GRANT_READ_URI_PERMISSION flag in the Intent.

  1. In the still in the share() method, uncomment Step 9.2 to add a thumbnail to the shared content.

MainActivity.kt

val thumbnail = getClipDataThumbnail()
thumbnail?.let {
    sharingIntent.clipData = it
    sharingIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}

Create content Uri

Take a look at the getClipDataThumbnail() method in MainActivity.kt, we create the content Uri as follows:

MainActivity.kt

val contentUri = saveThumbnailImage()
ClipData.newUri(contentResolver, null, contentUri)
...

contentUri is the Uri of the launcher image that we save to cache (in the images folder) in the saveThumbnailImage() method. The contentResolver needs access to the folder that we just declared. To do that:

  1. Open the AndroidManifest.xml file.
  2. Inside the <application> tag, uncomment Step 9.3. This will define a FileProvider what will be able to create and share the content Uri safely.

AndroidManifest.xml

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.android.directshare.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

If you run the app on an Android 10 device, you can see a thumbnail image next to the title. The thumbnail will appear as long as the shared content has a title. It won't appear without the title.

df966d69090d6be3.png

10. [Optional] Try Direct Share on your own

There are some improvements that you can make to your code. This is an optional step that you can try on your own:

  1. Modify the intent that gets triggered when the user taps on a launcher shortcut so that it opens the SendMessageActivity with a contact already populated. Tips: The intent needs to be of type Intent.ACTION_SEND and the contact Id needs to be sent with it.
  2. Modify SendMessageActivity to process the new Intent.

11. Congratulations!

You're now familiar with the concepts behind Direct Share and you are ready to implement it in your apps. In this codelab you learned about:

  • How to implement Direct Share in your app
  • How to make Direct Share backwards compatible with older Android versions
  • How to show content preview in shared content