QtAndroid details (5): JNI calls Android system functions (2), qtandroidjni
In "QtAndroid details (4): JNI calls Android system functions (1)", we provide some simple examples, demonstrate how to use the Qt JNI class library to access the network status, system resource directory, and current application information. This time, we provide some new examples, which may be more practical. Examples in this article include:
- Vibration
- Make the screen always bright
- Dynamically change the display direction of an application (landscape and landscape)
- Adjust screen brightness
- Set the ringtone Mode
Example
Figure 1
We can see how these functions are implemented one by one in the order on the interface.
Source code analysis
The code for building the interface is not mentioned in the Widget class constructor. In another way, let's not list all the code. One function and one function separate code, so the article looks shorter.
Vibration
When you click the "Vibrate" button in figure 1, the onVibrate () slot will be called and its code is as follows:
void Widget::onVibrate(){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); QAndroidJniObject name = QAndroidJniObject::getStaticObjectField( "android/content/Context", "VIBRATOR_SERVICE", "Ljava/lang/String;" ); CHECK_EXCEPTION(); QAndroidJniObject vibrateService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", name.object<jstring>()); CHECK_EXCEPTION(); jlong duration = 200; vibrateService.callMethod<void>("vibrate", "(J)V", duration); CHECK_EXCEPTION();}
Are you familiar with this? In fact, using QAndroidJniObject to call the Java class library seems similar to the code written ...... You can refer to "QtAndroid details (3): startActivity practices Android photos" and "QtAndroid details (4): JNI calls Android functions (1) "The Code in it will confirm this point more. In fact, this shows the general usage of QAndroidJniObject.
Many system services in Android have a name, which is defined in the Context class in the form of static member variables. I have seen it before. The Vibrator name is Context. VIBRATOR_SERVICE, and the corresponding class is android. OS. Vibrator. Vibrator's method "void vibrate (long MS)", which can produce vibrations in milliseconds.
The C ++ code first uses the QAndroidJniObject: getStaticObjectField () method from android. content. the Context class obtains the service name, calls the getSystemService method of Activity to obtain the Vibrator instance, and calls the vibrate (long) method of Vibrator to generate a vibration.
Change the display direction of the application on the screen
Qt Creator does not set Activity screenOrientation for Android apps by default. If your mobile phone is rotated, the app may also be displayed on a horizontal or vertical screen. When the application needs to be fixed to display in a certain direction, you need to modify the "android: screenOrientation" attribute of the <activity> tag in AndroidManifest. xml. In fact, the android. app. Activity Class also provides the "void setRequestedOrientation (int requestedOrientation)" method, allowing us to adjust the display direction of an Activity through code.
Simply put, an Android app may have multiple activities. Each Activity can have its own screen display direction, that is, the screen display direction, which is a feature of Activity.
When you click the "ScreenOrientation" button in figure 1, the onScreenOrientation () is called. The Code is as follows:
void Widget::onScreenOrientation(){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); jint orient = activity.callMethod<jint>( "getRequestedOrientation" ); CHECK_EXCEPTION(); if(orient == 1) { orient = 0; } else { orient = 1; } activity.callMethod<void>( "setRequestedOrientation", "(I)V", orient); CHECK_EXCEPTION();}
The androidActivity () method in the QtAndroid namespace can return the Activity object used by the Qt application, and then directly call the setRequestOrientation () method to change the screen direction of the current Activity.
Ringtone Mode
Phone call ringtones generally have three modes: normal (Bell), mute, and vibrate. corresponding to the Code, android is used. media. set the "void setRingerMode (int ringerMode)" in the AudioManager class. The name of AudioManager is AUDIO_SERVICE.
The integer parameter of setRingerMode has three values: RINGER_MODE_NORMAL, RINGER_MODE_SILENT, and RINGER_MODE_VIBRATE. These are defined in the form of static member variables in android. media. in the AudioManager class, we can use the QAndroidJniObject: getStaticField method in the C ++ Code. However, I stole a lazy code and used numbers directly. Macros corresponding to the ringtone mode:
#define RINGER_MODE_NORMAL 2#define RINGER_MODE_SILENT 0#define RINGER_MODE_VIBRATE 1
As shown in figure 1, the ringtone mode is selected through a set of radio buttons (QRadioButton. In the code, I use QButtonGroup to manage three single-choice buttons, and set the id of each button to the ringtone mode that it represents. In this way, when "buttonClicked (int id)" of QButtonGroup is triggered, our slot "void Widget: onRingerModeClicked (int mode)" is called, and the parameter is in the ring tone mode, very convenient.
Let's take a look at the code of the single-choice button initialization for this set of ringtone mode:
void Widget::initRingerMode(QVBoxLayout *layout){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); QAndroidJniObject name = QAndroidJniObject::getStaticObjectField( "android/content/Context", "AUDIO_SERVICE", "Ljava/lang/String;" ); CHECK_EXCEPTION(); QAndroidJniObject audioService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", name.object<jstring>()); CHECK_EXCEPTION(); int mode = audioService.callMethod<jint>( "getRingerMode", "()I" ); CHECK_EXCEPTION(); layout->addWidget(new QLabel("Ringer Mode:")); QHBoxLayout *rowLayout = new QHBoxLayout(); layout->addLayout(rowLayout); rowLayout->addSpacing(30); m_ringerModeGroup = new QButtonGroup(this); QRadioButton *normal = new QRadioButton("Normal"); m_ringerModeGroup->addButton(normal, RINGER_MODE_NORMAL); rowLayout->addWidget(normal); QRadioButton *silent = new QRadioButton("Silent"); m_ringerModeGroup->addButton(silent, RINGER_MODE_SILENT); rowLayout->addWidget(silent); QRadioButton *vibrate = new QRadioButton("Vibrate"); m_ringerModeGroup->addButton(vibrate, RINGER_MODE_VIBRATE); rowLayout->addWidget(vibrate); switch(mode) { case RINGER_MODE_NORMAL: normal->setChecked(true); break; case RINGER_MODE_SILENT: silent->setChecked(true); break; case RINGER_MODE_VIBRATE: vibrate->setChecked(true); break; } connect(m_ringerModeGroup, SIGNAL(buttonClicked(int)), this, SLOT(onRingerModeClicked(int)));}
In the above code, I also called the "int getRingerMode ()" method of android. media. AudioManager to get the current ringtone mode of the system, and then select the corresponding radio button.
The following is the onRingerModeClicked slot:
void Widget::onRingerModeClicked(int mode){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); QAndroidJniObject name = QAndroidJniObject::getStaticObjectField( "android/content/Context", "AUDIO_SERVICE", "Ljava/lang/String;" ); CHECK_EXCEPTION(); QAndroidJniObject audioService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", name.object<jstring>()); CHECK_EXCEPTION(); audioService.callMethod<void>( "setRingerMode", "(I)V", mode ); CHECK_EXCEPTION();}
The code is exactly the same as that of onVibrate.
Keep the screen always on
Some applications, such as video, do not want the Android phone to sleep or lock the screen during operation. In this case, you need to keep the screen on.
There are two ways to keep the screen on Android. One is to add the following code before calling setContentView () in the onCreate () method of the Activity:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
This method does not require special permissions. However, in Qt, we cannot use QAndroidJniObject. Because setContentView () has been called when the interface comes out ...... There are also thread problems. So I used the second method: PowerManager.
Android. OS. PowerManager can control whether the screen is always on. Its service name is Context. POWER_SERVICE. The PowerManager method "WakeLock newWakeLock (int flag, String tag)" can obtain a WakeLock instance. The flag can be of the following types:
- PARTIAL_WAKE_LOCK (constant value 1)
- SCREEN_DIM_WAKE_LOCK (Constant Value 6)
- SCREEN_BRIGHT_WAKE_LOCK (constant value 10)
- FULL_WAKE_LOCK (constant value 16)
In this example, I use SCREEN_BRIGHT_WAKE_LOCK. The constant value is 10. I directly use a constant instead of QAndroidJniObject: getStaticField () to obtain this constant.
After obtaining the WakeLock instance, you can call its "void acquire ()" method to complete the lock and call its "void release ()" method to release the lock. We should release the lock when appropriate, otherwise it will consume a lot of power.
In Figure 1, I used a check box (QCheckBox) to control whether the screen is always on. The corresponding slot is onScreenOnChecked. The Code is as follows:
void Widget::onScreenOnChecked(bool checked){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); if(m_lastChecked) { if(m_wakeLock.isValid()) { m_wakeLock.callMethod<void>("release"); CHECK_EXCEPTION(); } m_lastChecked = false; return; } QAndroidJniObject name = QAndroidJniObject::getStaticObjectField( "android/content/Context", "POWER_SERVICE", "Ljava/lang/String;" ); CHECK_EXCEPTION(); QAndroidJniObject powerService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", name.object<jstring>()); CHECK_EXCEPTION(); QAndroidJniObject tag = QAndroidJniObject::fromString("QtJniWakeLock"); m_wakeLock = powerService.callObjectMethod( "newWakeLock", "(ILjava/lang/String;)Landroid/os/PowerManager$WakeLock;", 10, //SCREEN_BRIGHT_WAKE_LOCK tag.object<jstring>() ); CHECK_EXCEPTION(); if(m_wakeLock.isValid()) { m_wakeLock.callMethod<void>("acquire"); CHECK_EXCEPTION(); } m_lastChecked = true;}
It should be noted that WakeLock is the internal class of android. OS. PowerManager. When we access the internal class through JNI, the signature is in the format of "Outer $ Inner", such as "PowerManager $ WakeLock ".
I added the WAKE_LOCK permission to the application.
Adjust screen brightness
You can adjust the screen brightness on the Settings page of the Android phone, which is achieved by modifying Settings. To adjust the Settings of an application, you must request the WRITE_SETTINGS permission.
The android. provider. Settings. System class provides the following two static methods:
- Static boolean putInt (ContentResolver, String key, int value );
- Static int getInt (ContentResolver, String key );
The putInt method is used to write a set (key-value Pair). The getInt method is used to obtain the value represented by the key.
System is the internal class of the Settings class. Pay attention to the JNI signature.
As shown in 1, I use a QSlider to adjust the screen brightness. The screen brightness ranges from 0 ~ 255. When you drag the QSlider, The onBrightnessChanged slot is triggered. The Code is as follows:
void Widget::onBrightnessChanged(int value){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); QAndroidJniObject contentResolver = activity.callObjectMethod( "getContentResolver", "()Landroid/content/ContentResolver;" ); CHECK_EXCEPTION(); //set brightness mode to MANUAL QAndroidJniObject brightnessTag = QAndroidJniObject::fromString("screen_brightness"); QAndroidJniObject brightnessModeTag = QAndroidJniObject::fromString("screen_brightness_mode"); bool ok = QAndroidJniObject::callStaticMethod<jboolean>( "android/provider/Settings$System", "putInt", "(Landroid/content/ContentResolver;Ljava/lang/String;I)Z", contentResolver.object<jobject>(), brightnessModeTag.object<jstring>(), 0 ); CHECK_EXCEPTION(); qDebug() << "set brightness mode to MANUAL - " << ok; //set brightness to value ok = QAndroidJniObject::callStaticMethod<jboolean>( "android/provider/Settings$System", "putInt", "(Landroid/content/ContentResolver;Ljava/lang/String;I)Z", contentResolver.object<jobject>(), brightnessTag.object<jstring>(), value ); CHECK_EXCEPTION(); qDebug() << "set brightness to " << value << " result - " << ok;}
You may notice that the first parameter of putInt and getInt is ContentResolver, and the fully qualified class is called android. content. contentResolver, the method "ContentResolver getContentResolver ()" of the Activity can get the ContentResolver instance. In this way, I get the ContentResolver instance at the beginning of the onBrightnessChanged slot.
The Settings. System class can modify various System Settings. It defines many static constants to identify configuration items. SCREEN_BRIGHTNESS and SCREEN_BRIGHTNESS_MODE are the two I used in the Code. The former identifies the screen brightness, and the latter identifies the screen brightness mode. They are all string constants. I am lazy and directly use the always-on string in the Android document.
Some users may set the screen brightness adjustment mode to automatic. In the automatic mode, it is invalid to modify the SCREEN_BRIGHTNESS in the System, so I roughly set the screen brightness mode to manual at the beginning. The static constant SCREEN_BRIGHTNESS_MODE_AUTOMATIC of the System class (value: 1) indicates the automatic adjustment mode, and SCREEN_BRIGHTNESS_MODE_MANUAL (value: 0) indicates the manual mode. I am also lazy, or I am using a numerical value directly.
The method I used here will affect the global settings of Android ...... Not necessarily what you want. If you only want to modify the screen brightness of the current application, you can set the properties of the Window corresponding to the Activity. The corresponding Java code is as follows:
WindowManager.LayoutParams lp = window.getAttributes(); lp.screenBrightness = brightness; window.setAttributes(lp);
If you want to implement the above Java code through JNI in Qt, it may cause errors, because the above Code must be called in the UI thread of Android Activity, while the Qt thread, the thread where the Activity is located is two different threads. For detailed analysis, see section 13th of Qt on Android core programming-"Qt on Android secrets ".
------------
I finally finished talking about it. It doesn't seem like talking about Qt C ++ code, but introducing the Android class library ...... It is undeniable that QAndroidJniObject is used for JNI development.
Download the project source code: Qt JNI calls the Android system function.
Review:
- QtAndroid (4): JNI calls Android system functions (1)
- Details on QtAndroid (3): startActivity for Android photography
- QtAndroid (2): startActivity and its friends
- QtAndroid (1): QAndroidJniObject
- Qt on Android Column