Skip to content

Commit

Permalink
added button "Shutdown" for persistent mode to android notifications,…
Browse files Browse the repository at this point in the history
… localized strings for java layer
  • Loading branch information
myxmaster committed Jan 13, 2025
1 parent 06cfaef commit 59f345c
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 42 deletions.
17 changes: 17 additions & 0 deletions android/app/src/main/java/com/zeus/LndMobile.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
Expand All @@ -69,6 +70,7 @@
// TODO break this class up
class LndMobile extends ReactContextBaseJavaModule {
private final String TAG = "LndMobile";
public static Map<String, String> translationCache = new HashMap<>();
Messenger messenger;
private boolean lndMobileServiceBound = false;
private Messenger lndMobileServiceMessenger; // The service
Expand Down Expand Up @@ -245,6 +247,21 @@ public String getName() {
return "LndMobile";
}

@ReactMethod
public void updateTranslationCache(String locale, ReadableMap translations) {
translationCache.clear();
ReadableMapKeySetIterator iterator = translations.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
translationCache.put(key, translations.getString(key));
}

// Get service instance and rebuild notification
Intent intent = new Intent(getReactApplicationContext(), LndMobileService.class);
intent.setAction("app.zeusln.zeus.android.intent.action.UPDATE_NOTIFICATION");
getReactApplicationContext().startService(intent);
}

@ReactMethod
public void checkLndMobileServiceConnected(Promise p) {
if (lndMobileServiceBound) {
Expand Down
150 changes: 111 additions & 39 deletions android/app/src/main/java/com/zeus/LndMobileService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.database.sqlite.SQLiteDatabase;
import android.os.IBinder;
import android.os.Build;
Expand All @@ -34,6 +35,14 @@
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.json.JSONException;
import org.json.JSONObject;

import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil;
import com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier;

Expand Down Expand Up @@ -487,59 +496,122 @@ private boolean getPersistentServicesEnabled(Context context) {
return false;
}

private String getLocalizedString(String key) {
String translation = LndMobile.translationCache.get(key);
return translation != null ? translation : "MISSING STRING";
}

private String fetchLocalizedStringFromBundle(String locale, String key) throws IOException, JSONException {
String assetPath = "stored-locales/" + locale + ".json";
Log.d(TAG, "Attempting to load: " + assetPath);
AssetManager assetManager = getApplicationContext().getAssets();
String[] mainAssets = assetManager.list("");
for (String file : mainAssets) {
Log.d(TAG, "Root asset: " + file);
if (file.equals("assets")) {
String[] assetFiles = assetManager.list("assets");
for (String assetFile : assetFiles) {
Log.d(TAG, "Asset file: " + assetFile);
}
}
}
try (InputStream is = assetManager.open(assetPath);
BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
JSONObject json = new JSONObject(result.toString());

String[] keyParts = key.split("\\.");
JSONObject current = json;
for (int i = 0; i < keyParts.length - 1; i++) {
current = current.getJSONObject(keyParts[i]);
}
return current.getString(keyParts[keyParts.length - 1]);
}
}

@Override
public int onStartCommand(Intent intent, int flags, int startid) {
// Hyperlog.v(TAG, "onStartCommand()");
if (intent != null && intent.getAction() != null && intent.getAction().equals("app.zeusln.zeus.android.intent.action.STOP")) {
Log.i(TAG, "Received stopForeground Intent");
stopForeground(true);
stopSelf();
return START_NOT_STICKY;
} else {
boolean persistentServicesEnabled = getPersistentServicesEnabled(this);
// persistent services on, start service as foreground-svc
if (persistentServicesEnabled) {
Notification.Builder notificationBuilder = null;
Intent notificationIntent = new Intent (this, MainActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel chan = new NotificationChannel(BuildConfig.APPLICATION_ID, "ZEUS", NotificationManager.IMPORTANCE_NONE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
if (intent != null && intent.getAction() != null) {
if (intent.getAction().equals("app.zeusln.zeus.android.intent.action.STOP")) {
Log.i(TAG, "Received stopForeground Intent");
stopForeground(true);
stopSelf();
return START_NOT_STICKY;
} else if (intent.getAction().equals("app.zeusln.zeus.android.intent.action.GRACEFUL_STOP")) {
Handler handler = new Handler(msg -> {
if (msg.what == MSG_STOP_LND_RESULT) {
stopForeground(true);
stopSelf();
Process.killProcess(Process.myPid());
return true;
}
return false;
});
Messenger messenger = new Messenger(handler);
mClients.add(messenger);
stopLnd(messenger, -1);
return START_NOT_STICKY;
} else if (intent.getAction().equals("app.zeusln.zeus.android.intent.action.UPDATE_NOTIFICATION")) {
if (notificationManager == null) {
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert notificationManager != null;
notificationManager.createNotificationChannel(chan);

notificationBuilder = new Notification.Builder(this, BuildConfig.APPLICATION_ID);
} else {
notificationBuilder = new Notification.Builder(this);
}

notificationBuilder
.setContentTitle("ZEUS")
.setContentText("LND is running in the background")
.setSmallIcon(R.drawable.ic_stat_ic_notification_lnd)
.setContentIntent(pendingIntent)
.setOngoing(true);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
notificationBuilder.setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE);
}
notificationManager.notify(ONGOING_NOTIFICATION_ID, buildNotification());
return START_NOT_STICKY;
}
}
boolean persistentServicesEnabled = getPersistentServicesEnabled(this);
// persistent services on, start service as foreground-svc
if (persistentServicesEnabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel chan = new NotificationChannel(BuildConfig.APPLICATION_ID, "ZEUS", NotificationManager.IMPORTANCE_NONE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
assert notificationManager != null;
notificationManager.createNotificationChannel(chan);
}

Notification notification = notificationBuilder.build();
Notification notification = buildNotification();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(ONGOING_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
} else {
startForeground(ONGOING_NOTIFICATION_ID, notification);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(ONGOING_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
} else {
startForeground(ONGOING_NOTIFICATION_ID, notification);
}
}

// else noop, instead of calling startService, start will be handled by binding
return startid;
}

private Notification buildNotification() {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
Intent stopIntent = new Intent(this, LndMobileService.class);
stopIntent.setAction("app.zeusln.zeus.android.intent.action.GRACEFUL_STOP");
PendingIntent stopPendingIntent = PendingIntent.getService(this, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE);
Notification.Builder notificationBuilder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationBuilder = new Notification.Builder(this, BuildConfig.APPLICATION_ID);
} else {
notificationBuilder = new Notification.Builder(this);
}
notificationBuilder
.setContentText(getLocalizedString("androidNotification.lndRunningBackground"))
.setSmallIcon(R.drawable.ic_stat_ic_notification_lnd)
.setContentIntent(pendingIntent)
.setOngoing(true)
.addAction(0, getLocalizedString("androidNotification.shutdown"), stopPendingIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
notificationBuilder.setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE);
}
return notificationBuilder.build();
}

@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
Expand Down
4 changes: 4 additions & 0 deletions lndmobile/LndMobile.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export interface ILndMobile {
unbindLndMobileService(): Promise<void>; // TODO(hsjoberg): function looks broken
sendPongToLndMobileservice(): Promise<{ data: string }>;
checkLndMobileServiceConnected(): Promise<boolean>;
updateTranslationCache(
locale: string,
translations: { [key: string]: string }
): void;
}

export interface ILndMobileTools {
Expand Down
4 changes: 3 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1306,5 +1306,7 @@
"views.OnChainAddresses.sortBy.balanceAscending": "Balance (ascending)",
"views.OnChainAddresses.sortBy.balanceDescending": "Balance (descending)",
"views.OnChainAddresses.createAddress": "Create address",
"views.OnChainAddresses.changeAddresses": "Change addresses"
"views.OnChainAddresses.changeAddresses": "Change addresses",
"androidNotification.lndRunningBackground": "LND is running in the background",
"androidNotification.shutdown": "Shutdown"
}
16 changes: 16 additions & 0 deletions utils/LocaleUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { NativeModules } from 'react-native';

import stores from '../stores/Stores';
import * as EN from '../locales/en.json';
import * as CS from '../locales/cs.json';
Expand Down Expand Up @@ -64,6 +66,12 @@ const Hebrew: any = HE;
const Croatian: any = HR;
const Korean: any = KO;

// strings that are needed on the java layer
const JAVA_LAYER_STRINGS = [
'androidNotification.lndRunningBackground',
'androidNotification.shutdown'
];

export function localeString(localeString: string): any {
const { settings } = stores.settingsStore;
const { locale } = settings;
Expand Down Expand Up @@ -142,3 +150,11 @@ export const formatInlineNoun = (text: string): string => {
}
return text;
};

export const bridgeJavaStrings = async (locale: string) => {
const neededTranslations: { [key: string]: string } = {};
JAVA_LAYER_STRINGS.forEach((key) => {
neededTranslations[key] = localeString(key);
});
NativeModules.LndMobile.updateTranslationCache(locale, neededTranslations);
};
3 changes: 2 additions & 1 deletion views/Settings/Language.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Screen from '../../components/Screen';

import SettingsStore, { LOCALE_KEYS } from '../../stores/SettingsStore';

import { localeString } from '../../utils/LocaleUtils';
import { localeString, bridgeJavaStrings } from '../../utils/LocaleUtils';
import { themeColor } from '../../utils/ThemeUtils';

interface LanguageProps {
Expand Down Expand Up @@ -116,6 +116,7 @@ export default class Language extends React.Component<
await updateSettings({
locale: item.key
}).then(() => {
bridgeJavaStrings(item.key);
navigation.goBack();
});
}}
Expand Down
5 changes: 4 additions & 1 deletion views/Wallet/Wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
startLnd,
expressGraphSync
} from '../../utils/LndMobileUtils';
import { localeString } from '../../utils/LocaleUtils';
import { localeString, bridgeJavaStrings } from '../../utils/LocaleUtils';
import { protectedNavigation } from '../../utils/NavigationUtils';
import { isLightTheme, themeColor } from '../../utils/ThemeUtils';

Expand Down Expand Up @@ -248,6 +248,9 @@ export default class Wallet extends React.Component<WalletProps, WalletState> {

// This awaits on settings, so should await on Tor being bootstrapped before making requests
await SettingsStore.getSettings().then(async (settings: Settings) => {
const locale = settings.locale || 'en';
bridgeJavaStrings(locale);

SystemNavigationBar.setNavigationColor(
themeColor('background'),
isLightTheme() ? 'dark' : 'light'
Expand Down

0 comments on commit 59f345c

Please sign in to comment.