Skip to content

Commit

Permalink
Merge #331: Use node model state to update the android notification
Browse files Browse the repository at this point in the history
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
  • Loading branch information
hebasto committed Jun 3, 2023
2 parents cfcfebc + b641df4 commit 23a1ab4
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 @@ -398,6 +398,12 @@ QML_RES_QML = \
qml/pages/settings/SettingsStorage.qml \
qml/pages/settings/SettingsTheme.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 23a1ab4

Please sign in to comment.