Skip to content

Commit

Permalink
android: use node model state to update notification
Browse files Browse the repository at this point in the history
The AndroidNotifier class connects to the NodeModel's state
signals and uses JNI to send callbacks to the Android service
managing the foreground notification.
  • Loading branch information
johnny9 committed May 31, 2023
1 parent 960490b commit b641df4
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 8 deletions.
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,7 @@ AM_CONDITIONAL([TARGET_DARWIN], [test "$TARGET_OS" = "darwin"])
AM_CONDITIONAL([BUILD_DARWIN], [test "$BUILD_OS" = "darwin"])
AM_CONDITIONAL([TARGET_LINUX], [test "$TARGET_OS" = "linux"])
AM_CONDITIONAL([TARGET_WINDOWS], [test "$TARGET_OS" = "windows"])
AM_CONDITIONAL([TARGET_ANDROID], [test "$TARGET_OS" = "android"])
AM_CONDITIONAL([ENABLE_WALLET], [test "$enable_wallet" = "yes"])
AM_CONDITIONAL([USE_SQLITE], [test "$use_sqlite" = "yes"])
AM_CONDITIONAL([USE_BDB], [test "$use_bdb" = "yes"])
Expand Down
6 changes: 6 additions & 0 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,12 @@ QML_RES_QML = \
qml/pages/settings/SettingsProxy.qml \
qml/pages/settings/SettingsStorage.qml

if TARGET_ANDROID
BITCOIN_QT_H += qml/androidnotifier.h
BITCOIN_QML_BASE_CPP += qml/androidnotifier.cpp
QT_MOC_CPP += qml/moc_androidnotifier.cpp
endif

BITCOIN_QT_CPP = $(BITCOIN_QT_BASE_CPP)
if TARGET_WINDOWS
BITCOIN_QT_CPP += $(BITCOIN_QT_WINDOWS_CPP)
Expand Down
108 changes: 108 additions & 0 deletions src/qml/androidnotifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) 2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <qml/androidnotifier.h>

#include <jni.h>

extern "C" {
JNIEXPORT jboolean JNICALL Java_org_bitcoincore_qt_BitcoinQtService_register(JNIEnv *env, jobject obj);
}

static JavaVM * g_vm = nullptr;
static jobject g_obj;

JNIEXPORT jboolean JNICALL Java_org_bitcoincore_qt_BitcoinQtService_register(JNIEnv * env, jobject obj)
{
env->GetJavaVM(&g_vm);
g_obj = env->NewGlobalRef(obj);

return (jboolean) true;
}

namespace {

JNIEnv* getJNIEnv(JavaVM* javaVM) {
JNIEnv* env;
jint result = javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);

if (result == JNI_EDETACHED) {
javaVM->AttachCurrentThread(&env, nullptr);
} else if (result != JNI_OK) {
// Error handling
return nullptr;
}

return env;
}

}

AndroidNotifier::AndroidNotifier(const NodeModel & node_model,
QObject * parent)
: QObject(parent)
, m_node_model(node_model)
{
QObject::connect(&node_model, &NodeModel::blockTipHeightChanged,
this, &AndroidNotifier::onBlockTipHeightChanged);
QObject::connect(&node_model, &NodeModel::numOutboundPeersChanged,
this, &AndroidNotifier::onNumOutboundPeersChanged);
QObject::connect(&node_model, &NodeModel::pauseChanged,
this, &AndroidNotifier::onPausedChanged);
QObject::connect(&node_model, &NodeModel::verificationProgressChanged,
this, &AndroidNotifier::onVerificationProgressChanged);
}

void AndroidNotifier::onBlockTipHeightChanged()
{
if (g_vm != nullptr) {
JNIEnv * env = getJNIEnv(g_vm);
if (env == nullptr) {
return;
}
jclass clazz = env->GetObjectClass(g_obj);
jmethodID mid = env->GetMethodID(clazz, "updateBlockTipHeight", "(I)V");
env->CallVoidMethod(g_obj, mid, m_node_model.blockTipHeight());
}
}

void AndroidNotifier::onNumOutboundPeersChanged()
{
if (g_vm != nullptr) {
JNIEnv * env = getJNIEnv(g_vm);
if (env == nullptr) {
return;
}
jclass clazz = env->GetObjectClass(g_obj);
jmethodID mid = env->GetMethodID(clazz, "updateNumberOfPeers", "(I)V");
env->CallVoidMethod(g_obj, mid, m_node_model.numOutboundPeers());
}
}

void AndroidNotifier::onVerificationProgressChanged()
{
if (g_vm != nullptr) {
JNIEnv * env = getJNIEnv(g_vm);
if (env == nullptr) {
return;
}
jclass clazz = env->GetObjectClass(g_obj);
jmethodID mid = env->GetMethodID(clazz, "updateVerificationProgress", "(D)V");
env->CallVoidMethod(g_obj, mid, static_cast<jdouble>(m_node_model.verificationProgress()));
}
}

void AndroidNotifier::onPausedChanged()
{
if (g_vm != nullptr) {
JNIEnv * env = getJNIEnv(g_vm);
if (env == nullptr) {
return;
}
jclass clazz = env->GetObjectClass(g_obj);
jmethodID mid = env->GetMethodID(clazz, "updatePaused", "(Z)V");
env->CallVoidMethod(g_obj, mid, static_cast<jboolean>(m_node_model.pause()));
}
}

26 changes: 26 additions & 0 deletions src/qml/androidnotifier.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef BITCOIN_QML_ANDROIDNOTIFIER_H
#define BITCOIN_QML_ANDROIDNOTIFIER_H

#include <qml/models/nodemodel.h>

#include <QObject>
#include <jni.h>

class AndroidNotifier : public QObject
{
Q_OBJECT

public:
explicit AndroidNotifier(const NodeModel & node_model, QObject * parent = nullptr);

public Q_SLOTS:
void onBlockTipHeightChanged();
void onNumOutboundPeersChanged();
void onVerificationProgressChanged();
void onPausedChanged();

private:
const NodeModel & m_node_model;
};

#endif // BITCOIN_QML_ANDROIDNOTIFIER_H
6 changes: 6 additions & 0 deletions src/qml/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
#include <node/interface_ui.h>
#include <noui.h>
#include <qml/appmode.h>
#ifdef __ANDROID__
#include <qml/androidnotifier.h>
#endif
#include <qml/components/blockclockdial.h>
#include <qml/controls/linegraph.h>
#include <qml/models/chainmodel.h>
Expand Down Expand Up @@ -233,6 +236,9 @@ int QmlGuiMain(int argc, char* argv[])
// QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException);

NetworkTrafficTower network_traffic_tower{node_model};
#ifdef __ANDROID__
AndroidNotifier android_notifier{node_model};
#endif

ChainModel chain_model{*chain};
chain_model.setCurrentNetworkName(QString::fromStdString(gArgs.GetChainName()));
Expand Down
6 changes: 3 additions & 3 deletions src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ public void onCreate(Bundle savedInstanceState)

Intent intent = new Intent(this, BitcoinQtService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
startForegroundService(intent);
} else {
startService(intent);
startService(intent);
}

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE);
| View.SYSTEM_UI_FLAG_IMMERSIVE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
Expand Down
87 changes: 82 additions & 5 deletions src/qt/android/src/org/bitcoincore/qt/BitcoinQtService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,26 @@

public class BitcoinQtService extends QtService
{
private static final String TAG = "BitcoinQtService";
private static final int NOTIFICATION_ID = 21000000;
private PowerManager.WakeLock wakeLock;
private WifiManager.WifiLock wifiLock;
private Notification.Builder notificationBuilder;
private boolean connected = false;
private boolean paused = false;
private boolean synced = false;
private int blockHeight = 0;
private double verificationProgress = 0.0;


@Override
public void onCreate() {
super.onCreate();

CharSequence name = "Bitcoin Core";
String description = "Bitcoin Core App notifications";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
// IMPORTANCE_LOW notifications won't make sound
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel("bitcoin_channel_id", name, importance);
channel.setDescription(description);

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

Notification notification = new Notification.Builder(this, "bitcoin_channel_id")
notificationBuilder = new Notification.Builder(this, "bitcoin_channel_id")
.setSmallIcon(R.drawable.bitcoin)
.setContentTitle("Running bitcoin")
.setOngoing(true)
.setContentIntent(pendingIntent)
.build();
.setContentIntent(pendingIntent);

startForeground(1, notification);
startForeground(NOTIFICATION_ID, notificationBuilder.build());
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BitcoinCore::IBD");

Expand All @@ -56,6 +65,11 @@ public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
wakeLock.acquire();
wifiLock.acquire();
if (register()) {
Log.d(TAG, "Registered JVM to native module");
} else {
Log.e(TAG, "Failed to register JVM to native module");
}
return START_NOT_STICKY;
}

Expand All @@ -70,4 +84,67 @@ public void onDestroy() {
wifiLock.release(); // Release the WiFi lock
}
}

public void updateBlockTipHeight(int blockHeight) {
if (this.blockHeight != blockHeight) {
this.blockHeight = blockHeight;
if (synced && connected) {
updateNotification();
}
}
}

public void updateNumberOfPeers(int numPeers) {
boolean newConnectedState = numPeers > 0;
if (connected != newConnectedState) {
connected = newConnectedState;
updateNotification();
}
}

public void updatePaused(boolean paused) {
if (this.paused != paused) {
this.paused = paused;
updateNotification();
}
}

public void updateVerificationProgress(double progress) {
boolean newSyncedState = progress > 0.999;
boolean needNotificationUpdate = false;
if (synced != newSyncedState) {
synced = newSyncedState;
needNotificationUpdate = true;
}
double newProgress = Math.floor(progress * 10000) / 100.0;
if (verificationProgress != newProgress ) {
verificationProgress = newProgress;
needNotificationUpdate = true;
}
if (needNotificationUpdate) {
updateNotification();
}
}

private void updateNotification() {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (paused) {
notificationBuilder.setContentTitle("Paused");
} else if (!connected) {
notificationBuilder.setContentTitle("Connecting...");
} else if (!synced) {
if (verificationProgress < 0) {
notificationBuilder.setContentTitle(String.format("%.2f%% loaded...", verificationProgress));
} else {
notificationBuilder.setContentTitle(String.format("%.0f%% loaded...", verificationProgress));
}
} else {
// Synced and connected
notificationBuilder.setContentTitle(String.format("Blocktime %,d", blockHeight));
}

notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}

public native boolean register();
}

0 comments on commit b641df4

Please sign in to comment.