Fast transfer from instant messengers - QIWI Android Wallet

Hello!

My name is Alex, I'm a developer at QIWI.

TL; DR

How to transfer from the messenger directly to the payment form:

  1. In the manifest we put an empty Activity c of the intent-filter type ACTION_VIEWand ACTION_DIALwith the scheme “tel”.
  2. In activity, we transfer to the payment form through the existing deeplink, enriching it with data from the original intent- “tel:XXXXX”



Profit : by clicking on the highlighted phone number in the messenger, a person gets on the transfer form with the completed field of the transfer recipient.
Bonus : I’ll tell you how beautifully to enable this feature, without being able to change the intent-filter list in the manifest in runtime.

What for?


Suppose a person has the task of transferring money for a collective birthday present. The receiving party can choose convenient transfer options: transfer to a card, electronic wallet, direct transfer to an account or something else. Many companies providing translation services accept a phone number as a secondary or primary user identifier. In order to avoid erroneous transfers, the recipient can convey his phone number via text message: email, instant messenger or SMS. The message also indicates the necessary system for translation.

A typical user path is to copy the phone number from the messenger and paste it into the application for translation. This way involves switching between applications, possibly accessing the desktop and even searching among the installed applications of the translation service provider. Is it possible to shorten this path by reducing the number of steps? There is an option.

Theory


Many instant messengers in smartphones recognize phone numbers and highlight them in the form of a hyperlink. By default, there is an application for processing this kind of links on any mobile device with a GSM module. Usually it is called “Phone”. In Android, the ability to process a shared phone number is declared in AndroidManifest. Examples of applications: “Phone”, Viber, Skype. If you intercept these events, you can immediately provide the user with a list of possible actions with a phone number. In our case, this will be a transfer offer through QIWI Wallet.

We are interested in events with types Intent.ACTION_VIEW, Intent.ACTION_DIAL, Intent.ACTION_CALLwith the tel scheme. EventIntent.ACTION_CALLIt has restrictions on the Android security policy and its initialization requires permission from the user starting from the version of Marshmallow inclusive. According to our research messengers using more general Intent.ACTION_VIEW, Intent.ACTION_DIAL.

There are several similar types specifically for emergency phones, but this is clearly not our case. The intent body has the form “tel: 12345678”. This kind of intent cannot be processed android.content.UriMatcher, because host is missing here. The easiest way to handle this kind of Uri is to ask it schemeSpecificPart and check if it is a valid phone number.

The list of intent-filters must be specified in AndroidManifest, it is impossible to change it during the execution of the application. How to remotely disable this feature? The first option is to repulse the user’s request at the time the Activity was opened - with a message or just close the application. From our point of view, this is not the best UX, since we will interrupt the flow of the user in the middle. It is better if we do not let a person choose our application at all, if the functionality is disabled. It is easier to disable the component with the intent-filter list in runtime, this will not give the user any false promises in the system interface. When using the PackageManager, note that checking for component inclusion is not very reliable. It is preferable to enabled force a component’s flag without relying on its current state using valuesCOMPONENT_ENABLED_STATE_ENABLED and COMPONENT_ENABLED_STATE_DISABLED. Changes made through the PackageManager are saved until the application is uninstalled from the device.

Practice


In order to disable the feature, we need an Android component to disable it.
The easiest way is to use an empty Activity, which we will do. If you decide to use Service with a similar set of intent-filters, check out the Google guides, which specifically say “do not declare intent filters for your services” . Please note that by default the component is disabled:android:enabled="false"

  <activity
            android:name=".messengerP2P.view.MessengerP2PActivity"
            android:configChanges="orientation"
            android:label="@string/title_activity_messenger_p2_p"
            android:enabled="false"
            android:screenOrientation="portrait">
            <!-- Open shared telephone number as dial application -->
            <intent-filter

                android:label="@string/title_activity_messenger_p2_p">
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.DIAL" />

                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="tel" />
            </intent-filter>
        </activity>

The activity itself simply redirects to the form of payment through existing diplinks.

class MessengerP2PActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var phoneNumberFromDial: String? = intent?.data?.schemeSpecificPart
        phoneNumberFromDial?.let {
            if (ru.mw.utils.Utils
                    .isPhoneNumber(phoneNumberFromDial)) {
                val phoneNumberFromDialLink = PaymentActivity.getUriForProviderId(
                        resources.getInteger(R.integer.providerIdQiwiWallet).toLong(), null, null)
                    .buildUpon()
                    .appendQueryParameter(PaymentActivity.QUERY_PARAM_ACCOUNT, phoneNumberFromDial)
                startActivity(
                    Intent(Intent.ACTION_VIEW, phoneNumberFromDialLink.build()))
            }
        }
        finish()
    }
}

The android: enabled flag can be controlled by this helper class.

import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager

class MessengerP2PUtils {

    companion object {
        private const val componentName = "ru.mw.messengerP2P.view.MessengerP2PActivity"

        private fun switchMessengerP2P(enabled: Boolean = true, packageName: String, packageManager: PackageManager) {
            val compName = ComponentName(packageName, componentName)
            packageManager.setComponentEnabledSetting(
                compName,
                when (enabled) {
                    true -> PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                    else -> PackageManager.COMPONENT_ENABLED_STATE_DISABLED
                },
                PackageManager.DONT_KILL_APP)
        }

        fun enableMessengerP2P(applicationContext: Context) {
            val packageName = applicationContext.packageName
            val packageManager = applicationContext.packageManager
            switchMessengerP2P(enabled = true, packageName = packageName, packageManager = packageManager);
        }

        fun disableMessengerP2P(applicationContext: Context) {
            val packageName = applicationContext.packageName
            val packageManager = applicationContext.packageManager
            switchMessengerP2P(enabled = false, packageName = packageName, packageManager = packageManager);
        }

        fun isMessengerP2PEnabled(packageName: String, packageManager: PackageManager): Boolean {
            val state = packageManager.getComponentEnabledSetting(ComponentName(packageName, componentName))
            return state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
        }

    }
}

The “isMessengerP2PEnabled” method is not used here to check the current state of the component, since during testing it produced unreliable results. If you decide to use it, carefully check its operation in the conditions of asynchronous business logic and when downloading the application.

The time for changing the flag value is enabled not very important, we chose the moment when the feature flags configuration was fully loaded.

So that when you click on the phone number in the messengers there is a system dialogue of choice, you need to provide the user with instructions on how to throw off the default settings for intent-a “tel: XXXXX”. For example: “If you can only make a call when you click on a number, go to Smartphone settings → select Phone in the application list → reset the“ Open by default. ”


If you disable the component on the go, when the user is shown this system dialog, nothing bad will happen. The corresponding item will instantly disappear from the list, in our case “Transfer money”.

An interesting behavior will begin if the user processes the links through our application by default. To do this, it is enough to poke the item “Remember selection” or “Always”. If you disable the component, then the user will be given a choice of which application to use, and the first time there is no “Remember choice” item. After enabling features, the entire flow will be restored, the user will be transferred to the application directly.

Conclusion


The idea of ​​this feature came to us a long time ago, the original ticket was created in 2017. The idea has not lost its relevance even now, I could not find a banking application with similar functionality. We released “transfers from the messenger” on April 29, on the first day almost 8,000 unique users took advantage of them. If business indicators in the near future will satisfy us, we will develop this feature further.

What do you think, how many more interesting intent-s of Android are peacefully waiting for their business application?

All Articles