4

I want to implement a dialer-feature in my app. Actually, it's done, but it works the way I don't want to. When button is pressed, native dialer opens and waiting for pressing a button. Is it possible to call directly without double pressing? Here's my code:

Button {
        id: callButton
        anchors.centerIn: parent
        text: 'Make a call'
        onClicked: Qt.openUrlExternally('tel:+77051085322')
    }
pushandpop
  • 455
  • 1
  • 10
  • 22

2 Answers2

6

Whereas in iOS the call can be issued directly, the same does not apply to Android. To overcome the problem you can define a C++ class Wrapper which handles the call, depending on the current OS. An instance of this class is registered as a context property and directly used in QML.

Inside the class you can exploit Android native APIs which provide the automatic dialing feature via the Intent action ACTION_CALL (but remember that there are some restrictions in using it). Typically in Android you write:

Intent callIntent = new callIntent(Intent.ACTION_CALL);
callIntent.setPackage("com.android.phone");          // force native dialer  (Android < 5)
callIntent.setPackage("com.android.server.telecom"); // force native dialer  (Android >= 5)
callIntent.setData(Uri.parse("tel:" + number));
callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(callIntent);

By setting the package we can force the usage of the native dialer. Without it the user would be prompt to choose among available dialers (i.e. Skype, Viber, etc...) clearly if other are installed on that device. The system dialer package changed between Lollipop and the previous releases so that it is necessary to check the SDK at runtime to set the correct one.


To call these APIs in C++ you need the Qt Android Extras and in particular QAndroidJniObject but also the related permissions in your custom Android manifest. Just add to your .pro file:

android: QT += androidextras  #included only in Android builds

and the following row to your manifest:

<uses-permission android:name="android.permission.CALL_PHONE"/>

If you did not define a custom manifest just add one. As of Qt Creator 3.3 just go to Projects > Build > Build Android APK > Create Templates to generate the custom manifest.


The header of our class looks like the following - constructor/deconstructor missing:

#ifndef WRAPPER_H
#define WRAPPER_H
#include <QObject>
#include <QString>
#include <QDebug>
#if defined(Q_OS_IOS)
#include <QUrl>
#include <QDesktopServices>
#elif defined(Q_OS_ANDROID)
#include <QtAndroid>
#include <QAndroidJniObject>
#endif

#include <QDesktopServices>
#include <QUrl>

class Wrapper: public QObject
{
    Q_OBJECT
public:
    Q_INVOKABLE void directCall(QString number);
};

#endif // WRAPPER_H

The corresponding source file looks like the following - again constructor/deconstructor missing:

#include "wrapper.h"

void Wrapper::directCall(QString number)
{
#if defined(Q_OS_IOS)
    QDesktopServices::openUrl(QUrl(QString("tel://%1").arg(number)));
#elif defined(Q_OS_ANDROID)
    // get the Qt android activity
    QAndroidJniObject activity =  QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
    //
    if (activity.isValid()){
    // real Java code to C++ code
    // Intent callIntent = new callIntent(Intent.ACTION_CALL);
    QAndroidJniObject callConstant = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Intent", "ACTION_CALL");
    QAndroidJniObject callIntent("android/content/Intent",  "(Ljava/lang/String;)V", callConstant.object());
    // callIntent.setPackage("com.android.phone"); (<= 4.4w)  intent.setPackage("com.android.server.telecom");  (>= 5)
    QAndroidJniObject package;
    if(QtAndroid::androidSdkVersion() >= 21)
        package = QAndroidJniObject::fromString("com.android.server.telecom");
    else
        package = QAndroidJniObject::fromString("com.android.phone");
    callIntent.callObjectMethod("setPackage", "(Ljava/lang/String;)Landroid/content/Intent;", package.object<jstring>());
    // callIntent.setData(Uri.parse("tel:" + number));
    QAndroidJniObject jNumber = QAndroidJniObject::fromString(QString("tel:%1").arg(number));
    QAndroidJniObject uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri","parse","(Ljava/lang/String;)Landroid/net/Uri;", jNumber.object());
    callIntent.callObjectMethod("setData", "(Landroid/net/Uri;)Landroid/content/Intent;", uri.object<jobject>());
    // callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    jint flag = QAndroidJniObject::getStaticField<jint>("android/content/Intent", "FLAG_ACTIVITY_NEW_TASK");
    callIntent.callObjectMethod("setFlags", "(I)Landroid/content/Intent;", flag);
    //startActivity(callIntent);
    activity.callMethod<void>("startActivity","(Landroid/content/Intent;)V", callIntent.object<jobject>());
}
    else
        qDebug() << "Something wrong with Qt activity...";
#else
    qDebug() << "Does nothing here...";
#endif
}

As discussed at the beginning, you can include an instance of this class as a context property. The main for this purpose looks like the following:

#include <QApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "wrapper.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QQmlApplicationEngine engine;
    Wrapper jw;
    engine.rootContext()->setContextProperty("caller", &jw);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));      

    return app.exec();
}

Finally in QML you can simply write:

Button {
    id: callButton
    anchors.centerIn: parent
    text: 'Make a call'
    onClicked: caller.directCall("+0123456789")
}

The code can be easily extended to support also WinPhone while maintaining the same QML interface (probably via the inclusion of a dedicated header/source pair). Finally, the usage of conditional inclusions guarantees that the code correctly compiles even if the used kit is changed on the fly.

As a final note, I would add that Google Play policies are not as strict as Apple App Store policies. Hence, an app rejection due to the usage of ACTION_CALL is not likely to happen.

Community
  • 1
  • 1
BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
  • Awesome! I've discovered many cool things from your answer during of research and got initial idea of operations like this one. Frankly, it doesn't work. ```qDebug()``` writes nothing to console, i.e. ```activity``` is **valid**, but nothing happens. I'm gonna find where is the problem. Anyway, thank you for such a great explanation ;) – pushandpop Mar 30 '15 at 15:57
  • Well, I've tested the code while writing the answer, even if only in my cellphone. I'm not used to share not working code. :) What is the logger output? Does any of the native calls fail? On which Android version did you tried it? Just share the logger data and we can try to tackle the problem. We can solve your error and improve my answer. :) – BaCaRoZzo Mar 30 '15 at 17:35
  • I'm using **Android Lollipop 5.0.1**. Here's **Application output**: http://pastebin.com/UdvD3Q2L . I'm using ```qDebug()``` as logging tool. Actually, I've tried to use ```QsLog``` framework, but it was telling me that ```QIODevice::write : device not open```. If there is anything else I should post here or provide more information just let me know. – pushandpop Mar 31 '15 at 08:18
  • Ok, it was a bug in Darvik which made it more permissive than ART. Fixing the last call made the trick on Lollipop. However, I've changed the code a little bit: the previous one would prompt the user to choose a dialer if others were available on the device (e.g. Skype, Viber). The new one ensures to call the native dialer without *any* user interaction. Tested on both 5.0.1 and 4.4.2 devices. Tell me if anything is missing/wrong. :) – BaCaRoZzo Mar 31 '15 at 10:34
  • Hmm, it gives me much more errors. Here they are http://pastebin.com/xjmhXzSd . You said that you've tested it, so maybe there are problems only with my machine? – pushandpop Mar 31 '15 at 11:28
  • Did you changed all the code, both header and source? Yup, I've tested with both a rooted and a stock 5.0.1! It seems to me an ART error due to a wrong call. Quite strange. Well, it should work. `Security exception`...is it the manifest ok? – BaCaRoZzo Mar 31 '15 at 11:31
  • 1
    Wow, I've found the problem. I was editing the wrong ```AndroidManifest``` file(there was two). You're my hero today, thank you ;) – pushandpop Mar 31 '15 at 12:11
  • I'm glad you solved. Double win: we solved your issue and really improved my answer! ;) – BaCaRoZzo Mar 31 '15 at 14:43
1

you will need the permission

<uses-permission android:name="android.permission.CALL_PHONE" />

in your AndroidManifest.xml

in java you would then do:

Intent dialIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:+1123123"));
startActivity(dialIntent);

the equivalent Qt code is something like:

QAndroidJniObject action    = QAndroidJniObject::fromString("android.intent.action.CALL");
QAndroidJniObject uriString = QAndroidJniObject::fromString("tel:+1123123");
QAndroidJniObject uri       = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String)V", uriString);


QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String, Landroid/net/Uri)V", action, uri);


QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
activity.callObjectMethod("startActivity","(Landroid/content/Intent;)V",intent.object<jobject>());

however, note that using ACTION_CALL may get you reject from the appstore, and google advises to use ACTION_DIAL, which opens the dialer instead of doing a direct call.

aep
  • 776
  • 5
  • 26