Skip to content

Commit 23a1ab4

Browse files
committed
Merge #331: Use node model state to update the android notification
b641df4 android: use node model state to update notification (johnny9) Pull request description: The AndroidNotifier class connects to the NodeModel's state signals and uses JNI to send callbacks to the Android service managing the foreground notification. [![Windows](https://img.shields.io/badge/OS-Windows-green)](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/win64/unsecure_win_gui.zip?branch=pull/331) [![Intel macOS](https://img.shields.io/badge/OS-Intel%20macOS-green)](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/macos/unsecure_mac_gui.zip?branch=pull/331) [![Apple Silicon macOS](https://img.shields.io/badge/OS-Apple%20Silicon%20macOS-green)](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/macos_arm64/unsecure_mac_arm64_gui.zip?branch=pull/331) [![ARM64 Android](https://img.shields.io/badge/OS-Android-green)](https://api.cirrus-ci.com/v1/artifact/github/bitcoin-core/gui-qml/android/unsecure_android_apk.zip?branch=pull/331) ACKs for top commit: jarolrod: ACK b641df4 Tree-SHA512: 1dfc95ade573f6620ba67871ff6f609e4bffe4cee877e03bd56c8917ac8cfb184198e5ca603ffda1962a3416da52921c0228eab5f0172011eca44de3d813e0d0
2 parents cfcfebc + b641df4 commit 23a1ab4

File tree

7 files changed

+232
-8
lines changed

7 files changed

+232
-8
lines changed

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,7 @@ AM_CONDITIONAL([TARGET_DARWIN], [test "$TARGET_OS" = "darwin"])
18691869
AM_CONDITIONAL([BUILD_DARWIN], [test "$BUILD_OS" = "darwin"])
18701870
AM_CONDITIONAL([TARGET_LINUX], [test "$TARGET_OS" = "linux"])
18711871
AM_CONDITIONAL([TARGET_WINDOWS], [test "$TARGET_OS" = "windows"])
1872+
AM_CONDITIONAL([TARGET_ANDROID], [test "$TARGET_OS" = "android"])
18721873
AM_CONDITIONAL([ENABLE_WALLET], [test "$enable_wallet" = "yes"])
18731874
AM_CONDITIONAL([USE_SQLITE], [test "$use_sqlite" = "yes"])
18741875
AM_CONDITIONAL([USE_BDB], [test "$use_bdb" = "yes"])

src/Makefile.qt.include

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,12 @@ QML_RES_QML = \
398398
qml/pages/settings/SettingsStorage.qml \
399399
qml/pages/settings/SettingsTheme.qml
400400

401+
if TARGET_ANDROID
402+
BITCOIN_QT_H += qml/androidnotifier.h
403+
BITCOIN_QML_BASE_CPP += qml/androidnotifier.cpp
404+
QT_MOC_CPP += qml/moc_androidnotifier.cpp
405+
endif
406+
401407
BITCOIN_QT_CPP = $(BITCOIN_QT_BASE_CPP)
402408
if TARGET_WINDOWS
403409
BITCOIN_QT_CPP += $(BITCOIN_QT_WINDOWS_CPP)

src/qml/androidnotifier.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright (c) 2023 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <qml/androidnotifier.h>
6+
7+
#include <jni.h>
8+
9+
extern "C" {
10+
JNIEXPORT jboolean JNICALL Java_org_bitcoincore_qt_BitcoinQtService_register(JNIEnv *env, jobject obj);
11+
}
12+
13+
static JavaVM * g_vm = nullptr;
14+
static jobject g_obj;
15+
16+
JNIEXPORT jboolean JNICALL Java_org_bitcoincore_qt_BitcoinQtService_register(JNIEnv * env, jobject obj)
17+
{
18+
env->GetJavaVM(&g_vm);
19+
g_obj = env->NewGlobalRef(obj);
20+
21+
return (jboolean) true;
22+
}
23+
24+
namespace {
25+
26+
JNIEnv* getJNIEnv(JavaVM* javaVM) {
27+
JNIEnv* env;
28+
jint result = javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
29+
30+
if (result == JNI_EDETACHED) {
31+
javaVM->AttachCurrentThread(&env, nullptr);
32+
} else if (result != JNI_OK) {
33+
// Error handling
34+
return nullptr;
35+
}
36+
37+
return env;
38+
}
39+
40+
}
41+
42+
AndroidNotifier::AndroidNotifier(const NodeModel & node_model,
43+
QObject * parent)
44+
: QObject(parent)
45+
, m_node_model(node_model)
46+
{
47+
QObject::connect(&node_model, &NodeModel::blockTipHeightChanged,
48+
this, &AndroidNotifier::onBlockTipHeightChanged);
49+
QObject::connect(&node_model, &NodeModel::numOutboundPeersChanged,
50+
this, &AndroidNotifier::onNumOutboundPeersChanged);
51+
QObject::connect(&node_model, &NodeModel::pauseChanged,
52+
this, &AndroidNotifier::onPausedChanged);
53+
QObject::connect(&node_model, &NodeModel::verificationProgressChanged,
54+
this, &AndroidNotifier::onVerificationProgressChanged);
55+
}
56+
57+
void AndroidNotifier::onBlockTipHeightChanged()
58+
{
59+
if (g_vm != nullptr) {
60+
JNIEnv * env = getJNIEnv(g_vm);
61+
if (env == nullptr) {
62+
return;
63+
}
64+
jclass clazz = env->GetObjectClass(g_obj);
65+
jmethodID mid = env->GetMethodID(clazz, "updateBlockTipHeight", "(I)V");
66+
env->CallVoidMethod(g_obj, mid, m_node_model.blockTipHeight());
67+
}
68+
}
69+
70+
void AndroidNotifier::onNumOutboundPeersChanged()
71+
{
72+
if (g_vm != nullptr) {
73+
JNIEnv * env = getJNIEnv(g_vm);
74+
if (env == nullptr) {
75+
return;
76+
}
77+
jclass clazz = env->GetObjectClass(g_obj);
78+
jmethodID mid = env->GetMethodID(clazz, "updateNumberOfPeers", "(I)V");
79+
env->CallVoidMethod(g_obj, mid, m_node_model.numOutboundPeers());
80+
}
81+
}
82+
83+
void AndroidNotifier::onVerificationProgressChanged()
84+
{
85+
if (g_vm != nullptr) {
86+
JNIEnv * env = getJNIEnv(g_vm);
87+
if (env == nullptr) {
88+
return;
89+
}
90+
jclass clazz = env->GetObjectClass(g_obj);
91+
jmethodID mid = env->GetMethodID(clazz, "updateVerificationProgress", "(D)V");
92+
env->CallVoidMethod(g_obj, mid, static_cast<jdouble>(m_node_model.verificationProgress()));
93+
}
94+
}
95+
96+
void AndroidNotifier::onPausedChanged()
97+
{
98+
if (g_vm != nullptr) {
99+
JNIEnv * env = getJNIEnv(g_vm);
100+
if (env == nullptr) {
101+
return;
102+
}
103+
jclass clazz = env->GetObjectClass(g_obj);
104+
jmethodID mid = env->GetMethodID(clazz, "updatePaused", "(Z)V");
105+
env->CallVoidMethod(g_obj, mid, static_cast<jboolean>(m_node_model.pause()));
106+
}
107+
}
108+

src/qml/androidnotifier.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#ifndef BITCOIN_QML_ANDROIDNOTIFIER_H
2+
#define BITCOIN_QML_ANDROIDNOTIFIER_H
3+
4+
#include <qml/models/nodemodel.h>
5+
6+
#include <QObject>
7+
#include <jni.h>
8+
9+
class AndroidNotifier : public QObject
10+
{
11+
Q_OBJECT
12+
13+
public:
14+
explicit AndroidNotifier(const NodeModel & node_model, QObject * parent = nullptr);
15+
16+
public Q_SLOTS:
17+
void onBlockTipHeightChanged();
18+
void onNumOutboundPeersChanged();
19+
void onVerificationProgressChanged();
20+
void onPausedChanged();
21+
22+
private:
23+
const NodeModel & m_node_model;
24+
};
25+
26+
#endif // BITCOIN_QML_ANDROIDNOTIFIER_H

src/qml/bitcoin.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include <node/interface_ui.h>
1414
#include <noui.h>
1515
#include <qml/appmode.h>
16+
#ifdef __ANDROID__
17+
#include <qml/androidnotifier.h>
18+
#endif
1619
#include <qml/components/blockclockdial.h>
1720
#include <qml/controls/linegraph.h>
1821
#include <qml/models/chainmodel.h>
@@ -233,6 +236,9 @@ int QmlGuiMain(int argc, char* argv[])
233236
// QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException);
234237

235238
NetworkTrafficTower network_traffic_tower{node_model};
239+
#ifdef __ANDROID__
240+
AndroidNotifier android_notifier{node_model};
241+
#endif
236242

237243
ChainModel chain_model{*chain};
238244
chain_model.setCurrentNetworkName(QString::fromStdString(gArgs.GetChainName()));

src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ public void onCreate(Bundle savedInstanceState)
2525

2626
Intent intent = new Intent(this, BitcoinQtService.class);
2727
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
28-
startForegroundService(intent);
28+
startForegroundService(intent);
2929
} else {
30-
startService(intent);
30+
startService(intent);
3131
}
3232

3333
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
3434
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
35-
| View.SYSTEM_UI_FLAG_IMMERSIVE);
35+
| View.SYSTEM_UI_FLAG_IMMERSIVE);
3636
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
3737
WindowManager.LayoutParams.FLAG_FULLSCREEN);
3838
super.onCreate(savedInstanceState);

src/qt/android/src/org/bitcoincore/qt/BitcoinQtService.java

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,26 @@
1717

1818
public class BitcoinQtService extends QtService
1919
{
20+
private static final String TAG = "BitcoinQtService";
21+
private static final int NOTIFICATION_ID = 21000000;
2022
private PowerManager.WakeLock wakeLock;
2123
private WifiManager.WifiLock wifiLock;
24+
private Notification.Builder notificationBuilder;
25+
private boolean connected = false;
26+
private boolean paused = false;
27+
private boolean synced = false;
28+
private int blockHeight = 0;
29+
private double verificationProgress = 0.0;
30+
2231

2332
@Override
2433
public void onCreate() {
2534
super.onCreate();
2635

2736
CharSequence name = "Bitcoin Core";
2837
String description = "Bitcoin Core App notifications";
29-
int importance = NotificationManager.IMPORTANCE_DEFAULT;
38+
// IMPORTANCE_LOW notifications won't make sound
39+
int importance = NotificationManager.IMPORTANCE_LOW;
3040
NotificationChannel channel = new NotificationChannel("bitcoin_channel_id", name, importance);
3141
channel.setDescription(description);
3242

@@ -36,14 +46,13 @@ public void onCreate() {
3646
Intent intent = new Intent(this, BitcoinQtActivity.class);
3747
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
3848

39-
Notification notification = new Notification.Builder(this, "bitcoin_channel_id")
49+
notificationBuilder = new Notification.Builder(this, "bitcoin_channel_id")
4050
.setSmallIcon(R.drawable.bitcoin)
4151
.setContentTitle("Running bitcoin")
4252
.setOngoing(true)
43-
.setContentIntent(pendingIntent)
44-
.build();
53+
.setContentIntent(pendingIntent);
4554

46-
startForeground(1, notification);
55+
startForeground(NOTIFICATION_ID, notificationBuilder.build());
4756
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
4857
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BitcoinCore::IBD");
4958

@@ -56,6 +65,11 @@ public int onStartCommand(Intent intent, int flags, int startId) {
5665
super.onStartCommand(intent, flags, startId);
5766
wakeLock.acquire();
5867
wifiLock.acquire();
68+
if (register()) {
69+
Log.d(TAG, "Registered JVM to native module");
70+
} else {
71+
Log.e(TAG, "Failed to register JVM to native module");
72+
}
5973
return START_NOT_STICKY;
6074
}
6175

@@ -70,4 +84,67 @@ public void onDestroy() {
7084
wifiLock.release(); // Release the WiFi lock
7185
}
7286
}
87+
88+
public void updateBlockTipHeight(int blockHeight) {
89+
if (this.blockHeight != blockHeight) {
90+
this.blockHeight = blockHeight;
91+
if (synced && connected) {
92+
updateNotification();
93+
}
94+
}
95+
}
96+
97+
public void updateNumberOfPeers(int numPeers) {
98+
boolean newConnectedState = numPeers > 0;
99+
if (connected != newConnectedState) {
100+
connected = newConnectedState;
101+
updateNotification();
102+
}
103+
}
104+
105+
public void updatePaused(boolean paused) {
106+
if (this.paused != paused) {
107+
this.paused = paused;
108+
updateNotification();
109+
}
110+
}
111+
112+
public void updateVerificationProgress(double progress) {
113+
boolean newSyncedState = progress > 0.999;
114+
boolean needNotificationUpdate = false;
115+
if (synced != newSyncedState) {
116+
synced = newSyncedState;
117+
needNotificationUpdate = true;
118+
}
119+
double newProgress = Math.floor(progress * 10000) / 100.0;
120+
if (verificationProgress != newProgress ) {
121+
verificationProgress = newProgress;
122+
needNotificationUpdate = true;
123+
}
124+
if (needNotificationUpdate) {
125+
updateNotification();
126+
}
127+
}
128+
129+
private void updateNotification() {
130+
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
131+
if (paused) {
132+
notificationBuilder.setContentTitle("Paused");
133+
} else if (!connected) {
134+
notificationBuilder.setContentTitle("Connecting...");
135+
} else if (!synced) {
136+
if (verificationProgress < 0) {
137+
notificationBuilder.setContentTitle(String.format("%.2f%% loaded...", verificationProgress));
138+
} else {
139+
notificationBuilder.setContentTitle(String.format("%.0f%% loaded...", verificationProgress));
140+
}
141+
} else {
142+
// Synced and connected
143+
notificationBuilder.setContentTitle(String.format("Blocktime %,d", blockHeight));
144+
}
145+
146+
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
147+
}
148+
149+
public native boolean register();
73150
}

0 commit comments

Comments
 (0)