Providing Custom Text Selection Actions in Android
Tags: android, kotlin
Published: 2020-09-26 17:19:24

I recently came across the app [Text Infinity](https://play.google.com/store/apps/details?id=com.mannan.textinfinity). It offers additional actions for selected text in Android. So I asked myself how can I create an app that provides additional actions.
![Text Infinity](https://dev-to-uploads.s3.amazonaws.com/i/poayboioysprfwlna79i.jpg)(source: [Google Play](https://play.google.com/store/apps/details?id=com.mannan.textinfinity))

## Table of Contents

- [How it works](#how-it-works)
- [Further cases](#further-cases)
  - [Multiple actions](#multiple-actions)
  - [Starting a service](#starting-a-service)
  - [Listening to certain types of text](#listening-to-certain-types-of-text)
  - [Watch out: launchMode singleTop](#watch-out-launchMode-singleTop)
- [References](#references)

## How it works

Since Android 6.0 a floating text selection toolbar will appear when the text is selected. By default, the common actions like cut, copy and paste are displayed. The best part is that any app can provide custom actions for the text selection toolbar.

Basically, your activity must listen to the implicit intent `ACTION_PROCESS_TEXT`. For that you have to add an [intent filter](https://developer.android.com/guide/components/intents-filters#Receiving) inside an activity in the manifest as shown below:

```xml
<activity
    android:name=".UppercaseActivity"
    android:label="@string/action_uppercase"
    >
    <intent-filter>
        <action android:name="android.intent.action.PROCESS_TEXT" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>
```

The `EXTRA_PROCESS_TEXT` parameter contains the highlighted text:

```kotlin
val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)
```

There is one more parameter `EXTRA_PROCESS_TEXT_READONLY`. It can be used to check if the action was triggered from an editable field like `EditText`. So you can return a modified version of text when it is **not readonly**.

```kotlin
val readonly = intent.getBooleanExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)
```

Usually my mind understands a concept better when I see the complete code. Therefore, here is a potential implementation how it could look like:

```kotlin
class UppercaseActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)
        val readonly = intent.getBooleanExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)

        if(!readonly && text != null){

            val outgoingIntent = Intent()
            outgoingIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, text.toString().toUpperCase())
            setResult(Activity.RESULT_OK, outgoingIntent)

        } else {
            Toast.makeText(this, "Text cannot be modified", Toast.LENGTH_SHORT).show()
        }

        finish()
    }
}
```

So let's have look at our example:
![working example](https://dev-to-uploads.s3.amazonaws.com/i/b0cunglt6tgixn38ijyc.gif)

## Further cases

### Multiple actions

In case you want to deploy multiple actions with a single app, each text selection action requires a separate activity.

```xml
<activity
    android:name=".UppercaseActivity"
    android:label="@string/action_uppercase"
    >
    <intent-filter>
        <action android:name="android.intent.action.PROCESS_TEXT" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>
<activity
    android:name=".LowercaseActivity"
    android:label="@string/action_lowercase"
    >
    <intent-filter>
        <action android:name="android.intent.action.PROCESS_TEXT" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>
<activity
    android:name=".ReverseActivity"
    android:label="@string/action_reverse"
    >
    <intent-filter>
        <action android:name="android.intent.action.PROCESS_TEXT" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>
```

### Starting a service

For the intent `ACTION_PROCESS_TEXT` , the Android system looks only for activities. In other words, a service cannot be directly started by this intent. However, your activity could simply start a service subsequently.

### Listening to certain types of text

Maybe you have wondered whether your action can only be triggered for certain types of text such as phone numbers, links and etc. Unfortunately there is no way to do that within the Android system. In other words, your action will always appear. So you need to validate the user input before processing it.

### Watch out: launchMode singleTop

If your activity is configured as `launchMode=”singleTop”`, then you must also handle the text selection action within the `onNewIntent()` method. (For more information about launchMode, see [Tasks](https://developer.android.com/guide/components/activities/tasks-and-back-stack#ManifestForTasks))

## References

- [Creating custom Text Selection actions with ACTION_PROCESS_TEXT](https://medium.com/androiddevelopers/custom-text-selection-actions-with-action-process-text-191f792d2999) by Ian Lake
- [Understand Tasks and Back Stack](https://developer.android.com/guide/components/activities/tasks-and-back-stack) by Android
- [Intents and Intent Filters](https://developer.android.com/guide/components/intents-filters) by Android
- Cover image by [Estée Janssens](https://unsplash.com/@esteejanssens?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) from [Unsplash](https://unsplash.com/s/photos/highlight?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)