From 855096d0ee0835d0ba81b9db511939760b5136ef Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Fri, 9 Jun 2023 20:27:04 -0700 Subject: [PATCH] supporting unified push as default if detected --- app/mobile/App.js | 4 + app/mobile/android/app/build.gradle | 4 + .../android/app/src/main/AndroidManifest.xml | 15 +++ .../main/java/com/databag/CustomReceiver.java | 103 ++++++++++++++++++ .../main/java/com/databag/MainActivity.java | 33 ++++++ app/mobile/android/build.gradle | 11 ++ app/mobile/src/api/setAccountAccess.js | 4 +- app/mobile/src/api/setLogin.js | 4 +- app/mobile/src/context/useAppContext.hook.js | 32 ++++-- 9 files changed, 194 insertions(+), 16 deletions(-) create mode 100644 app/mobile/android/app/src/main/java/com/databag/CustomReceiver.java diff --git a/app/mobile/App.js b/app/mobile/App.js index caabfdaa..7fdd944e 100644 --- a/app/mobile/App.js +++ b/app/mobile/App.js @@ -18,6 +18,7 @@ import { Access } from 'src/access/Access'; import { Dashboard } from 'src/dashboard/Dashboard'; import { Session } from 'src/session/Session'; import ReceiveSharingIntent from 'react-native-receive-sharing-intent'; +import {PermissionsAndroid} from 'react-native'; // silence warning: Sending `onAnimatedValueUpdate` with no listeners registered //LogBox.ignoreLogs(['Sending']); @@ -27,6 +28,9 @@ export default function App() { const [sharing, setSharing] = useState(); useEffect(() => { + + PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS); + ReceiveSharingIntent.getReceivedFiles(files => { setSharing(files); }, diff --git a/app/mobile/android/app/build.gradle b/app/mobile/android/app/build.gradle index 9c0d284b..759e5400 100644 --- a/app/mobile/android/app/build.gradle +++ b/app/mobile/android/app/build.gradle @@ -158,8 +158,12 @@ dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") + implementation 'androidx.core:core:1.8.0' + implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0") + implementation 'com.github.UnifiedPush:android-connector:2.1.1' + debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group:'com.squareup.okhttp3', module:'okhttp' diff --git a/app/mobile/android/app/src/main/AndroidManifest.xml b/app/mobile/android/app/src/main/AndroidManifest.xml index 77235871..a02cfc4c 100644 --- a/app/mobile/android/app/src/main/AndroidManifest.xml +++ b/app/mobile/android/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ + + + + + + + + + + + + diff --git a/app/mobile/android/app/src/main/java/com/databag/CustomReceiver.java b/app/mobile/android/app/src/main/java/com/databag/CustomReceiver.java new file mode 100644 index 00000000..7152ebe4 --- /dev/null +++ b/app/mobile/android/app/src/main/java/com/databag/CustomReceiver.java @@ -0,0 +1,103 @@ +package com.databag; + +import android.content.Context; +import org.jetbrains.annotations.NotNull; +import org.unifiedpush.android.connector.MessagingReceiver; +import android.util.Log; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Intent; + +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ActivityManager; +import android.os.Build; +import android.net.Uri; +import android.media.RingtoneManager; +import androidx.core.app.NotificationCompat; +import java.nio.charset.StandardCharsets; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +public class CustomReceiver extends MessagingReceiver { + public CustomReceiver() { + super(); + } + + private boolean forgrounded () { + ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo(); + ActivityManager.getMyMemoryState(appProcessInfo); + return (appProcessInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND || appProcessInfo.importance == RunningAppProcessInfo.IMPORTANCE_VISIBLE); + } + + @Override + public void onNewEndpoint(@NotNull Context context, @NotNull String endpoint, @NotNull String instance) { + + final ReactInstanceManager reactInstanceManager = + ((ReactApplication) context.getApplicationContext()) + .getReactNativeHost() + .getReactInstanceManager(); + ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); + + WritableMap params = Arguments.createMap(); + params.putString("instance", instance); + params.putString("endpoint", endpoint); + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("unifiedPushURL", params); + + // Called when a new endpoint be used for sending push messages + } + + @Override + public void onRegistrationFailed(@NotNull Context context, @NotNull String instance) { + // called when the registration is not possible, eg. no network + } + + @Override + public void onUnregistered(@NotNull Context context, @NotNull String instance) { + // called when this application is unregistered from receiving push messages + } + + @Override + public void onMessage(@NotNull Context context, @NotNull byte[] message, @NotNull String instance) { + + if (forgrounded()) { + return; + } + + String strMessage = new String(message, StandardCharsets.UTF_8); + + + Intent intent = new Intent(context, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0 /* Request code */, intent, + PendingIntent.FLAG_IMMUTABLE); + + String channelId = "fcm_default_channel"; + Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, + channelId) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle(strMessage).setAutoCancel(true).setSound( + defaultSoundUri).setContentIntent(pendingIntent); + + NotificationManager notificationManager = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel(channelId, "Channel human readable title", + NotificationManager.IMPORTANCE_DEFAULT); + notificationManager.createNotificationChannel(channel); + } + + notificationManager.notify(0, notificationBuilder.build()); + } +} + diff --git a/app/mobile/android/app/src/main/java/com/databag/MainActivity.java b/app/mobile/android/app/src/main/java/com/databag/MainActivity.java index e653c22c..a7170a13 100644 --- a/app/mobile/android/app/src/main/java/com/databag/MainActivity.java +++ b/app/mobile/android/app/src/main/java/com/databag/MainActivity.java @@ -1,5 +1,6 @@ package com.databag; +import java.util.ArrayList; import android.content.Intent; import android.os.Bundle; import com.facebook.react.ReactActivity; @@ -7,6 +8,19 @@ import com.facebook.react.ReactActivityDelegate; import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; import com.facebook.react.defaults.DefaultReactActivityDelegate; +import org.unifiedpush.android.connector.UnifiedPush; + +import android.content.Context; + +import org.unifiedpush.android.connector.RegistrationDialogContent; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; + public class MainActivity extends ReactActivity { /** @@ -21,6 +35,25 @@ public class MainActivity extends ReactActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(null); + MainActivity activityContext = this; + + this.getSharedPreferences("unifiedpush.connector", Context.MODE_PRIVATE).edit().putBoolean("unifiedpush.no_distrib_dialog", true).apply(); + + + ReactInstanceManager mReactInstanceManager = getReactNativeHost().getReactInstanceManager(); + mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { + public void onReactContextInitialized(ReactContext validContext) { + + UnifiedPush.registerAppWithDialog( + activityContext, + "default", + new RegistrationDialogContent(), + new ArrayList(), + getApplicationContext().getPackageName() + ); + + } + }); } /** diff --git a/app/mobile/android/build.gradle b/app/mobile/android/build.gradle index 44b54cc7..83743b74 100644 --- a/app/mobile/android/build.gradle +++ b/app/mobile/android/build.gradle @@ -21,3 +21,14 @@ buildscript { classpath("com.facebook.react:react-native-gradle-plugin") } } + +allprojects { + repositories { + maven { + url "https://www.jitpack.io" + content { + includeModule 'com.github.UnifiedPush', 'android-connector' + } + } + } +} diff --git a/app/mobile/src/api/setAccountAccess.js b/app/mobile/src/api/setAccountAccess.js index d7949e7d..233968a3 100644 --- a/app/mobile/src/api/setAccountAccess.js +++ b/app/mobile/src/api/setAccountAccess.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function setAccountAccess(server, token, appName, appVersion, platform, deviceToken, notifications) { - let access = await fetchWithTimeout(`https://${server}/account/access?token=${token}&appName=${appName}&appVersion=${appVersion}&platform=${platform}&deviceToken=${deviceToken}`, { method: 'PUT', body: JSON.stringify(notifications) }) +export async function setAccountAccess(server, token, appName, appVersion, platform, deviceToken, pushType, notifications) { + let access = await fetchWithTimeout(`https://${server}/account/access?token=${token}&appName=${appName}&appVersion=${appVersion}&platform=${platform}&deviceToken=${deviceToken}&pushType=${pushType}`, { method: 'PUT', body: JSON.stringify(notifications) }) checkResponse(access) return await access.json() } diff --git a/app/mobile/src/api/setLogin.js b/app/mobile/src/api/setLogin.js index 3487da97..0d1c49c7 100644 --- a/app/mobile/src/api/setLogin.js +++ b/app/mobile/src/api/setLogin.js @@ -1,10 +1,10 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; import base64 from 'react-native-base64' -export async function setLogin(username, server, password, appName, appVersion, platform, deviceToken, notifications) { +export async function setLogin(username, server, password, appName, appVersion, platform, deviceToken, pushType, notifications) { let headers = new Headers() headers.append('Authorization', 'Basic ' + base64.encode(username + ":" + password)); - let login = await fetchWithTimeout(`https://${server}/account/apps?appName=${appName}&appVersion=${appVersion}&platform=${platform}&deviceToken=${deviceToken}`, { method: 'POST', body: JSON.stringify(notifications), headers: headers }) + let login = await fetchWithTimeout(`https://${server}/account/apps?appName=${appName}&appVersion=${appVersion}&platform=${platform}&deviceToken=${deviceToken}&pushType=${pushType}`, { method: 'POST', body: JSON.stringify(notifications), headers: headers }) checkResponse(login) return await login.json() } diff --git a/app/mobile/src/context/useAppContext.hook.js b/app/mobile/src/context/useAppContext.hook.js index 2abddb38..bf7025fa 100644 --- a/app/mobile/src/context/useAppContext.hook.js +++ b/app/mobile/src/context/useAppContext.hook.js @@ -14,6 +14,7 @@ import { ChannelContext } from 'context/ChannelContext'; import { RingContext } from 'context/RingContext'; import { getVersion, getApplicationName, getDeviceId } from 'react-native-device-info' import messaging from '@react-native-firebase/messaging'; +import { DeviceEventEmitter } from 'react-native'; export function useAppContext() { const [state, setState] = useState({ @@ -33,6 +34,7 @@ export function useAppContext() { const ws = useRef(null); const deviceToken = useRef(null); + const pushType = useRef(null); const access = useRef(null); const init = useRef(false); @@ -41,19 +43,23 @@ export function useAppContext() { } useEffect(() => { + + // select the unified token if available + DeviceEventEmitter.addListener('unifiedPushURL', (e) => { + deviceToken.current = e.endpoint; + pushType.current = "up"; + }); + (async () => { try { - try { - deviceToken.current = await messaging().getToken(); - } - catch (err) { - console.log(err); - //Alert.alert('FCM', err.toString()); + const token = await messaging().getToken(); + if (!deviceToken.current) { + deviceToken.current = token; + pushType.current = "fcm"; } } catch (err) { console.log(err); - deviceToken.current = null; } access.current = await store.actions.init(); if (access.current) { @@ -104,7 +110,7 @@ export function useAppContext() { throw new Error('invalid session state'); } await addAccount(server, username, password, token); - const session = await setLogin(username, server, password, getApplicationName(), getVersion(), getDeviceId(), deviceToken.current, notifications) + const session = await setLogin(username, server, password, getApplicationName(), getVersion(), getDeviceId(), deviceToken.current, pushType.current, notifications) access.current = { server, token: session.appToken, guid: session.guid }; await store.actions.setSession(access.current); await setSession(); @@ -116,7 +122,7 @@ export function useAppContext() { if (!init.current || access.current) { throw new Error('invalid session state'); } - const session = await setAccountAccess(server, token, getApplicationName(), getVersion(), getDeviceId(), deviceToken.current, notifications); + const session = await setAccountAccess(server, token, getApplicationName(), getVersion(), getDeviceId(), deviceToken.current, pushType.current, notifications); access.current = { server, token: session.appToken, guid: session.guid }; await store.actions.setSession(access.current); await setSession(); @@ -129,7 +135,7 @@ export function useAppContext() { throw new Error('invalid session state'); } const acc = username.split('@'); - const session = await setLogin(acc[0], acc[1], password, getApplicationName(), getVersion(), getDeviceId(), deviceToken.current, notifications) + const session = await setLogin(acc[0], acc[1], password, getApplicationName(), getVersion(), getDeviceId(), deviceToken.current, pushType.current, notifications) access.current = { server: acc[1], token: session.appToken, guid: session.guid }; await store.actions.setSession(access.current); await setSession(); @@ -143,8 +149,10 @@ export function useAppContext() { } updateState({ loggingOut: true }); try { - await messaging().deleteToken(); - deviceToken.current = await messaging().getToken(); + if (pushType.current == "fcm") { + await messaging().deleteToken(); + deviceToken.current = await messaging().getToken(); + } await clearLogin(state.server, state.token); } catch (err) {