Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a call with Qt directly from the application?

Tags:

android

qt

qml

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')
    }
like image 605
pushandpop Avatar asked Mar 17 '23 14:03

pushandpop


2 Answers

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.

like image 111
BaCaRoZzo Avatar answered Mar 24 '23 09:03

BaCaRoZzo


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.

like image 24
aep Avatar answered Mar 24 '23 09:03

aep