Implementing Text Classification Implementation using Android 9

Posted By : Sunidhi Sharma | 31-Jan-2019

Overview

Text Classification is a mechanism by which the system can identify a specific type of text and add appropriate actions when the user selects that text. Example of a few classification types are email addresses, URLs, or phone numbers which can trigger actions like launching a web browser application, a system dialer, or Email a client. All this can be achieved by using the default TextClassification Service built in to Android.

You can also build your own custom classifier which will take action on the basis of action defined by you.

Before jumping to the code part, let us discuss the origin of Text Classification in Android. Text classification was introduced in Android 8.1 for techniques to help the developers classify text. The latest version i.e.; Android 9 has enhanced this functionality using the Text Classifier Service.

The Android Pie(9) provides a default Text Classifier service implementation which is used for Text Classification everytime, that is, unless you have defined your custom Text Classifier.

 

Requirements

1. Android device with Android P OS.

2. Device with Fingerprint scan support.

3. Android Gradle version 3 or above.

4. A new android project with Kotlin Support

Example
Step 1: Getting the default TextClassificationManager

The first step involves getting the default TextClassificationManager by retrieving the appropriate system service. Write the code defined below in the onCreate method of your Activity.

textClassificationManager = getSystemService(Context.TEXT_CLASSIFICATION_SERVICE) as TextClassificationManager

Step 2: Making an Async call to TextClassificationManager

When you perforn Text Classification, it should be noted that Text Classification on its own is asn expensive operation, computationally as the system’s default Text Classifier uses a Machine Learning (ML) model to  perform this computation.

So, in order for it to perform uninterruptibly, you can wrap the calls to the TextClassifcation inside a coroutine executing asynchronously. The code for the same is shown below:

class MainActivity : AppCompatActivity() {

    private val emailAddress = "[email protected]"

    private val urlString = "https://www.oodlestechnologies.com"

    private lateinit var textClassificationManager: TextClassificationManager


    override fun onCreate(savedInstanceState: Bundle?) {

       super.onCreate(savedInstanceState)

       setContentView(R.layout.activity_main)


       classifier()

    }


    private fun classifier() = async(CommonPool) { //CommonPool is kept as context

       textClassificationManager = getSystemService(Context.TEXT_CLASSIFICATION_SERVICE) as TextClassificationManager

       ...

    }

}

Step 3: Performing the TextClassification

In order to perform the classification, we first obtain a TextClassifier instance from TextClassificationManager and call its classifyText() method. The classifyText() takes three parameters:

1. The text which you want to identify (Character Sequence)

2. The start index from which the text classification should take place (int value)

3. The ending index on which the text classification should take place (int value)

4. Locale List is an ordered list of locale preferences. In case, no locale prefernces are set, this can be set to null or empty locale list.

To perform text classification on email, type the lines mentioned below:

val text = textClassificationManager.textClassifier

val email = text.classifyText(emailAddress, 0, emailText.length, LocaleList.getDefault())

println(emailClassification)

The output generated will look something like this:

TextClassification {[email protected], entities={email=1.0}, actions=[android.app.RemoteAction@4e67771, android.app.RemoteAction@eb7956], id=androidtc|en_v6|754483982}

Entity points out the type of text identified and the confidence level on a scale of 0.0-1.0. As the confidence level is 1.0, hence it is a certain match.

You can perform the text identification of phone-number or URL in similar fashion.

Step4: Building Your Own Custom Text Identifier.

You can also develop your custom text identifier in order to take an action on selection of a particular text. One can code her own classifier by overriding the required methods of the TextClassifier interface.

In this text classifier, the suggestSelection() and classifyText() will have two forms, the first one will take individual arguments, the second one will take the request instance which contains all these arguments. The other method is a wrapper which will construct the Request instance from individual arguments.

class MyAndroidTextClassifier(
        private val context: Context,
        private val fallback: TextClassifier,
        private val factory: TextClassifierFactory = FrameworkFactory()
) : TextClassifier by fallback {

    private val yourtext = "your name"
    private val contentDescription = "logo"
    private val youruri = "https://www.oodlestechnologies.com"
    private val regex = Regex("your\\s?name", RegexOption.IGNORE_CASE)

    override fun suggestSelection(request: TextSelection.Request): TextSelection {
        return findRangeOfMatch(request)
                ?: fallback.suggestSelection(request)
    }

    private fun findRangeOfMatch(request: TextSelection.Request): TextSelection? {
        return regex.findAll(request.text)
                .firstOrNull { it.range.contains(request.startIndex until request.endIndex) }
                ?.range
                ?.let {
                    factory.buildTextSelection(it.start, it.endInclusive + 1, TextClassifier.TYPE_URL, 1.0f)
                }
    }

    private fun <T : Comparable<T>> ClosedRange<T>.contains(range: ClosedRange<T>) =
            contains(range.start) && contains(range.endInclusive)

    override fun classifyText(request: TextClassification.Request): TextClassification {
        return if (regex.matches(request.subSequence())) {
            factory.buildTextClassification(
                    request.subSequence().toString(),
                    listOf(TextClassifier.TYPE_URL to 1.0f),
                    listOf(factory.buildRemoteAction(
                            context,
                            R.drawable.ic_launcher,
                            yourtext,
                            contentDescription,
                            youruri
                    ))
            )
        } else {
            fallback.classifyText(request)
        }
    }

    private fun TextClassification.Request.subSequence() =
            text.subSequence(startIndex, endIndex)
}

When you make an isntance of TextSelection, make sure you provide a start and an end index of range, and the concrete type that we have already identified (TYPE_URL constant, inthis case). Also make sure you give a confidence score of 1.0f as we’re certain that this is a positive match. The factory method implementation written below makes use of textSelection.Builder to create a TextSelection instance.

interface TextClassifierFactory {

    fun buildTextSelection(startIndex: Int, endIndex: Int, entityType: String, confidenceScore: Float): TextSelection

    fun buildTextClassification(
            text: String,
            entityTypes: List<Pair<String, Float>>,
            actions: List<RemoteAction>
    ): TextClassification

    fun buildRemoteAction(
            context: Context,
            @DrawableRes drawableId: Int,
            title: String,
            contentDescription: String,
            uri: String
    ): RemoteAction
}

class FrameworkFactory : TextClassifierFactory {

    override fun buildRemoteAction(
            context: Context,
            drawableId: Int,
            title: String,
            contentDescription: String,
            uri: String
    ): RemoteAction {
        return RemoteAction(
                Icon.createWithResource(context, drawableId),
                title,
                contentDescription,
                PendingIntent.getActivity(
                        context,
                        0,
                        Intent(Intent.ACTION_VIEW, Uri.parse(uri)),
                        0
                )
        )
    }

    override fun buildTextClassification(
            text: String,
            entityTypes: List<Pair<String, Float>>,
            actions: List<RemoteAction>
    ): TextClassification {
        return TextClassification.Builder()
                .run {
                    setText(text)
                    entityTypes.forEach { setEntityType(it.first, it.second) }
                    actions.forEach { addAction(it) }
                    build()
                }
    }

    override fun buildTextSelection(
            startIndex: Int,
            endIndex: Int,
            entityType: String,
            confidenceScore: Float
    ): TextSelection {
        return TextSelection.Builder(startIndex, endIndex)
                .setEntityType(entityType, confidenceScore)
                .build()
    }
}
Conclusion

This blog discussed the use of the default Text Classifier and building a custom text classifier. Text Classification is a simple functionality for getting the available options after long pressing the text available in TextView or WebView and can make the life of developers a lot easier.

 

 

 

 

About Author

Author Image
Sunidhi Sharma

An ardent computer science enthusiast working in the field of Android application development.

Request for Proposal

Name is required

Comment is required

Sending message..