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')
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With