yayan преди 1 година
родител
ревизия
34257f2947

+ 5 - 0
app/src/main/java/io/nexilis/alpha/AlphaApplication.kt

@@ -2,8 +2,13 @@ package io.nexilis.alpha
 
 import android.app.Application
 import dagger.hilt.android.HiltAndroidApp
+import io.nexilis.service.core.ChatNotification
+import javax.inject.Inject
 
 @HiltAndroidApp
 class AlphaApplication : Application() {
 
+    @Inject
+    lateinit var chatNotification: ChatNotification
+
 }

+ 1 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Sign.kt

@@ -192,6 +192,7 @@ fun SignIn(navController: NavHostController, completion: (Boolean) -> Unit) {
                 completion(r)
                 if (r && pin.isNotEmpty()) {
                     context.getSharedPreferences().put(key = "pin", value = pin)
+                    context.getSharedPreferences().put(key = "is_login", value = true)
                 }
             }
         }) {

+ 1 - 0
build.gradle

@@ -7,4 +7,5 @@ plugins {
     id 'org.jetbrains.kotlin.android' version '1.9.10' apply false
     id 'com.google.dagger.hilt.android' version '2.48' apply false
     id 'com.google.devtools.ksp' version '1.9.10-1.0.13' apply false
+    id 'com.google.gms.google-services' version '4.3.14' apply false
 }

+ 4 - 0
cpaas-lite/build.gradle

@@ -4,6 +4,7 @@ plugins {
     id 'com.google.dagger.hilt.android'
     id 'kotlin-kapt'
     id 'com.google.devtools.ksp'
+    id 'com.google.gms.google-services'
 }
 
 android {
@@ -58,6 +59,9 @@ dependencies {
 
     implementation "com.squareup.okhttp3:okhttp:4.12.0"
     implementation "androidx.work:work-runtime-ktx:2.9.0"
+
+    implementation(platform("com.google.firebase:firebase-bom:32.7.2"))
+    implementation "com.google.firebase:firebase-messaging-ktx"
 }
 
 kapt {

+ 47 - 0
cpaas-lite/google-services.json

@@ -0,0 +1,47 @@
+{
+  "project_info": {
+    "project_number": "36827599888",
+    "project_id": "push-kit-de2e3",
+    "storage_bucket": "push-kit-de2e3.appspot.com"
+  },
+  "client": [
+    {
+      "client_info": {
+        "mobilesdk_app_id": "1:36827599888:android:29e65c0ec8a560c5916695",
+        "android_client_info": {
+          "package_name": "io.nexilis.service"
+        }
+      },
+      "oauth_client": [
+        {
+          "client_id": "36827599888-6ka4jn56q9tl2c2lkr1subdid6969mp1.apps.googleusercontent.com",
+          "client_type": 1,
+          "android_info": {
+            "package_name": "io.nexilis.service",
+            "certificate_hash": "2efad00ed38e60ebef77ba5dd5687d198d2bf5f5"
+          }
+        },
+        {
+          "client_id": "36827599888-jot1op87kda3265l7vbk3f3t5tfk053h.apps.googleusercontent.com",
+          "client_type": 3
+        }
+      ],
+      "api_key": [
+        {
+          "current_key": "AIzaSyCLMlbL2GSDy7RVpw0dS55efV2uljB3R2U"
+        }
+      ],
+      "services": {
+        "appinvite_service": {
+          "other_platform_oauth_client": [
+            {
+              "client_id": "36827599888-jot1op87kda3265l7vbk3f3t5tfk053h.apps.googleusercontent.com",
+              "client_type": 3
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "configuration_version": "1"
+}

+ 17 - 2
cpaas-lite/src/main/AndroidManifest.xml

@@ -3,13 +3,21 @@
 
     <uses-permission android:name="android.permission.INTERNET" />
 
-    <application
-        android:enabled="true">
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
+    <application android:enabled="true">
         <service
             android:name="io.newuniverse.SDK.nuSDKService"
             android:enabled="true"
             android:exported="false"
             android:foregroundServiceType="microphone|camera|mediaProjection" />
+        <service
+            android:name=".FirebaseService"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="com.google.firebase.MESSAGING_EVENT" />
+            </intent-filter>
+        </service>
 
         <provider
             android:name="androidx.core.content.FileProvider"
@@ -20,5 +28,12 @@
                 android:name="android.support.FILE_PROVIDER_PATHS"
                 android:resource="@xml/path_provider" />
         </provider>
+
+        <meta-data
+            android:name="firebase_messaging_auto_init_enabled"
+            android:value="false" />
+        <meta-data
+            android:name="firebase_analytics_collection_enabled"
+            android:value="false" />
     </application>
 </manifest>

+ 23 - 0
cpaas-lite/src/main/java/io/nexilis/service/Api.kt

@@ -3,11 +3,14 @@ package io.nexilis.service
 import android.app.Activity
 import android.content.Context
 import android.content.Intent
+import android.os.Build
 import android.util.Log
 import io.newuniverse.SDK.nuSDKService
 import io.nexilis.service.core.Data
+import io.nexilis.service.core.getSharedPreferences
 import io.nexilis.service.core.optString
 import io.nexilis.service.core.put
+import kotlinx.coroutines.launch
 import java.security.SecureRandom
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.locks.Condition
@@ -95,5 +98,25 @@ class Api {
                 }
             }.start()
         }
+
+        internal fun sendToken(context: Context, token: String) {
+            if (context.getSharedPreferences().getBoolean("is_login", false)) {
+                context.getSharedPreferences().getString("pin", "")?.let {
+                    apiScope.launch {
+                        Service.sendAsync(
+                            Data(
+                                code = "ATO",
+                                status = System.nanoTime().toString(),
+                                f_pin = it,
+                                bodies = mutableMapOf(
+                                    "TKN" to token,
+                                    "Bo" to Build.BRAND
+                                )
+                            )
+                        )
+                    }
+                }
+            }
+        }
     }
 }

+ 60 - 0
cpaas-lite/src/main/java/io/nexilis/service/FirebaseService.kt

@@ -0,0 +1,60 @@
+package io.nexilis.service
+
+import android.util.Log
+import com.google.firebase.messaging.FirebaseMessagingService
+import com.google.firebase.messaging.RemoteMessage
+import io.nexilis.service.core.Data
+import io.nexilis.service.core.Incoming
+import io.nexilis.service.core.Network
+import io.nexilis.service.core.getSharedPreferences
+import org.json.JSONObject
+
+class FirebaseService : FirebaseMessagingService() {
+
+    override fun onNewToken(token: String) {
+        super.onNewToken(token)
+        Log.d(tag, "onNewToken:$token")
+        Api.sendToken(applicationContext, token)
+    }
+
+    override fun onMessageReceived(message: RemoteMessage) {
+        super.onMessageReceived(message)
+        Log.d(tag, "onMessageReceived:${message.data}")
+        try {
+            val json = JSONObject(message.data.toMap())
+            json.optString("message_id").let { messageId ->
+                applicationContext.getSharedPreferences().getString("pin", null)?.let { me ->
+                    pullMessage(messageId, me)
+                }
+            }
+        } catch (_: Exception) {
+
+        }
+    }
+
+    private fun pullMessage(messageId: String, me: String) {
+        Network().post(
+            "https://digixplatform.com/pull_notification",
+            "message_id=$messageId&pin=$me"
+        ) { r, body ->
+            if (r && !body.isNullOrEmpty()) {
+                val data = JSONObject(body).optString("data")
+                if (data.isNotEmpty()) {
+                    val d = Data()
+                    if (d.parse(data)) {
+                        Incoming(applicationContext).process(d) {
+                            if (it) {
+                                Network().post(
+                                    "https://digixplatform.com/ack_message",
+                                    "message_id=$messageId&pin=$me"
+                                ) { _, _ ->
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+}

+ 5 - 1
cpaas-lite/src/main/java/io/nexilis/service/SdkCallback.kt

@@ -1,7 +1,9 @@
 package io.nexilis.service
 
+import android.content.Context
 import android.util.Log
 import io.newuniverse.SDK.nuSDKCallBack
+import io.newuniverse.SDK.nuSDKService
 import io.nexilis.service.core.Data
 import io.nexilis.service.core.Incoming
 import kotlin.concurrent.withLock
@@ -10,6 +12,8 @@ class SdkCallback : nuSDKCallBack {
 
     private val tag = "SdkCallback"
 
+    private val context: Context by lazy { nuSDKService.getInstance(pass) as Context }
+
     override fun connectionStateChanged(
         p0: String?,
         p1: String?,
@@ -70,7 +74,7 @@ class SdkCallback : nuSDKCallBack {
                 p0?.let { s ->
                     data.packetId = s
                 }
-                Incoming.getInstance().process(data)
+                Incoming(context).process(data)
             }
         } catch (e: Exception) {
             Log.e(tag, e.message, e)

+ 4 - 4
cpaas-lite/src/main/java/io/nexilis/service/Service.kt

@@ -64,7 +64,7 @@ class Service {
             try {
                 Log.d(tag, "Service:sGetResponse >>> $data")
                 val response = nuSDKService.getInstance(pass)
-                    .sGetResponse(pass, data.toString(), DEFAULT_TIMEOUT, false)
+                    ?.sGetResponse(pass, data.toString(), DEFAULT_TIMEOUT, false)
                     ?: return null
                 Log.d(tag, "Service:sGetResponse <<< $response")
                 val r = Data()
@@ -81,7 +81,7 @@ class Service {
             try {
                 Log.d(tag, "Service:sGetResponse >>> $string")
                 val response = nuSDKService.getInstance(pass)
-                    .sGetResponse(pass, string, DEFAULT_TIMEOUT, false)
+                    ?.sGetResponse(pass, string, DEFAULT_TIMEOUT, false)
                     ?: return null
                 Log.d(tag, "Service:sGetResponse <<< $response")
                 val r = Data()
@@ -102,7 +102,7 @@ class Service {
                 if (data.media.isNotEmpty()) {
                     d = data.toByteArray()
                 }
-                return nuSDKService.getInstance(pass).sSend(pass, d, 1, DEFAULT_TIMEOUT)
+                return nuSDKService.getInstance(pass)?.sSend(pass, d, 1, DEFAULT_TIMEOUT)
             } catch (e: Exception) {
                 Log.e(tag, e.message, e)
             }
@@ -120,7 +120,7 @@ class Service {
             try {
                 Log.d(tag, "Service:sSendResponse:$data")
                 return nuSDKService.getInstance(pass)
-                    .sSendResponse(pass, data.packetId, data.toString(), DEFAULT_TIMEOUT)
+                    ?.sSendResponse(pass, data.packetId, data.toString(), DEFAULT_TIMEOUT)
             } catch (e: Exception) {
                 Log.e(tag, e.message, e)
             }

+ 5 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/ApiModule.kt

@@ -106,4 +106,9 @@ object ApiModule {
     fun provideWorkingAreaDao(database: ApiRoomDatabase): WorkingAreaDao {
         return database.workingAreaDao()
     }
+
+    @Provides
+    fun provideChatNotification(context: Context) : ChatNotification {
+        return ChatNotification(context)
+    }
 }

+ 43 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/ChatNotification.kt

@@ -0,0 +1,43 @@
+package io.nexilis.service.core
+
+import android.annotation.TargetApi
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import io.nexilis.service.R
+
+class ChatNotification(private val context: Context) {
+
+    @TargetApi(Build.VERSION_CODES.O)
+    private fun createNotificationChannel(
+        channelId: String,
+        name: String
+    ): NotificationChannel {
+        return NotificationChannel(
+            channelId, name, NotificationManager.IMPORTANCE_DEFAULT
+        ).also { channel ->
+            val notificationManager =
+                context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+            notificationManager.createNotificationChannel(channel)
+        }
+    }
+
+    fun getNotification(title: String, text: String): Notification {
+        val channelId = context.getString(R.string.notification_chat_channel_id)
+        val name = context.getString(R.string.channel_name_chat)
+
+        val builder = NotificationCompat.Builder(context, channelId)
+            .setContentTitle(title)
+            .setContentText(text)
+            .setSmallIcon(context.applicationInfo.icon)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            createNotificationChannel(channelId, name).also {
+                builder.setChannelId(it.id)
+            }
+        }
+        return builder.build()
+    }
+}

+ 7 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/Extension.kt

@@ -33,6 +33,13 @@ fun SharedPreferences.put(key: String, value: String) {
     }
 }
 
+fun SharedPreferences.put(key: String, value: Boolean) {
+    with(this.edit()) {
+        putBoolean(key, value)
+        apply()
+    }
+}
+
 fun Context.createImageFile(): File {
     val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
     val imageFileName = "IMG_" + timeStamp + "_"

+ 38 - 22
cpaas-lite/src/main/java/io/nexilis/service/core/Incoming.kt

@@ -1,8 +1,14 @@
 package io.nexilis.service.core
 
+import android.Manifest
 import android.content.Context
+import android.content.pm.PackageManager
 import android.util.Log
-import io.newuniverse.SDK.nuSDKService
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationManagerCompat
+import com.google.android.gms.tasks.OnCompleteListener
+import com.google.firebase.messaging.FirebaseMessaging
+import io.nexilis.service.Api
 import io.nexilis.service.Service
 import io.nexilis.service.apiScope
 import io.nexilis.service.data.entities.Buddy
@@ -11,31 +17,14 @@ import io.nexilis.service.data.entities.GroupMember
 import io.nexilis.service.data.entities.Message
 import io.nexilis.service.data.entities.MessageStatus
 import io.nexilis.service.data.rooms.ApiRoomDatabase
-import io.nexilis.service.pass
 import io.nexilis.service.tag
 import kotlinx.coroutines.launch
 import org.json.JSONArray
 import java.util.zip.ZipInputStream
 
-class Incoming private constructor() {
+class Incoming(private val context: Context) {
 
-    private val context: Context by lazy { nuSDKService.getInstance(pass) as Context }
-
-    companion object {
-
-        @Volatile
-        private var INSTANCE: Incoming? = null
-
-        fun getInstance(): Incoming {
-            return INSTANCE ?: synchronized(this) {
-                val instance = Incoming()
-                INSTANCE = instance
-                instance
-            }
-        }
-    }
-
-    fun process(data: Data) {
+    fun process(data: Data, completion: ((Boolean) -> Unit)? = null) {
         Log.d(tag, "process(${data.code}):$data")
         apiScope.launch {
             val callback = Service.map.remove(data.status)
@@ -65,7 +54,7 @@ class Incoming private constructor() {
                 }
 
                 "S0" -> {
-                    pushMessage(data)
+                    pushMessage(data, completion)
                 }
 
                 "INQ" -> {
@@ -108,6 +97,14 @@ class Incoming private constructor() {
                         f_pin = data.f_pin
                     )
                 )
+                FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
+                    if (!task.isSuccessful) {
+                        Log.w(tag, "Fetching FCM registration token failed", task.exception)
+                        return@OnCompleteListener
+                    }
+                    val token = task.result
+                    Api.sendToken(context, token)
+                })
             }
         } catch (e: Exception) {
             Log.e(tag, e.message, e)
@@ -369,7 +366,7 @@ class Incoming private constructor() {
         }
     }
 
-    private suspend fun pushMessage(data: Data) {
+    private suspend fun pushMessage(data: Data, completion: ((Boolean) -> Unit)? = null) {
         try {
             val messageId = data.bodies["A18"] ?: ""
             if (messageId.isEmpty()) return
@@ -471,6 +468,25 @@ class Incoming private constructor() {
                 )
             }
             Service.sendAck(me, data.status)
+            completion?.let { it(true) }
+            with(NotificationManagerCompat.from(context)) {
+                if (ActivityCompat.checkSelfPermission(
+                        context,
+                        Manifest.permission.POST_NOTIFICATIONS
+                    ) != PackageManager.PERMISSION_GRANTED
+                ) {
+                    return@with
+                }
+                val buddy = ApiRoomDatabase.getDatabase(context).buddyDao().getBuddySync(opposite)
+                Log.d(tag, "getBuddy:${buddy}")
+                notify(
+                    112346,
+                    ChatNotification(context).getNotification(
+                        "${buddy.first_name} ${buddy.last_name}".trim(),
+                        data.bodies["A07"]?.toNormalString() ?: ""
+                    )
+                )
+            }
         } catch (e: Exception) {
             Log.e(tag, e.message, e)
         }

+ 29 - 3
cpaas-lite/src/main/java/io/nexilis/service/core/Network.kt

@@ -14,6 +14,7 @@ import okhttp3.OkHttpClient
 import okhttp3.Request
 import okhttp3.RequestBody
 import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
 import okhttp3.Response
 import java.io.File
 import java.io.IOException
@@ -51,15 +52,15 @@ class Network {
             .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
             .post(body)
             .build()
-        Log.d(tag, "Upload:enqueue:$url:${file.name}")
+        Log.d(tag, "upload:enqueue:$url:${file.name}")
         client.newCall(request).enqueue(object : Callback {
             override fun onFailure(call: Call, e: IOException) {
-                Log.d(tag, "Upload:onFailure")
+                Log.d(tag, "upload:onFailure")
                 completion(false)
             }
 
             override fun onResponse(call: Call, response: Response) {
-                Log.d(tag, "Upload:onResponse:${response.code}")
+                Log.d(tag, "upload:onResponse:${response.code}")
                 completion(response.code == 200)
             }
         })
@@ -74,4 +75,29 @@ class Network {
         }
         return "text/plain"
     }
+
+    fun post(url: String, body: String, completion: (Boolean, body: String?) -> Unit) {
+        val client = OkHttpClient()
+        val requestBody: RequestBody =
+            body.toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull())
+        val request: Request = Request.Builder()
+            .url(url)
+            .addHeader("User-Agent", "Mozilla/5.0")
+            .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
+            .post(requestBody)
+            .build()
+        Log.d(tag, "post:enqueue:$url:$body")
+        client.newCall(request).enqueue(object : Callback {
+            override fun onFailure(call: Call, e: IOException) {
+                Log.d(tag, "post:onFailure")
+                completion(false, null)
+            }
+
+            override fun onResponse(call: Call, response: Response) {
+                Log.d(tag, "post:onResponse:${response.code}")
+                completion(response.code == 200, response.body?.string())
+            }
+        })
+    }
+
 }

+ 3 - 0
cpaas-lite/src/main/java/io/nexilis/service/data/daos/BuddyDao.kt

@@ -20,6 +20,9 @@ interface BuddyDao {
     @Query("select * from Buddy where f_pin = :pin")
     fun getBuddy(pin: String): LiveData<Buddy>
 
+    @Query("select * from Buddy where f_pin = :pin")
+    fun getBuddySync(pin: String): Buddy
+
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     suspend fun insert(entity: Buddy)
 

+ 2 - 0
cpaas-lite/src/main/res/values/string.xml

@@ -1,7 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="notification_channel_id">123412</string>
+    <string name="notification_chat_channel_id">123413</string>
     <string name="notification_title">Sending data..</string>
     <string name="cancel_processing">Cancel</string>
     <string name="channel_name">Outgoing</string>
+    <string name="channel_name_chat">Chat</string>
 </resources>