Skip to content

Commit 4f14902

Browse files
committed
Update
1 parent 9b1be19 commit 4f14902

File tree

24 files changed

+807
-78
lines changed

24 files changed

+807
-78
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
.externalNativeBuild
1414
.cxx
1515
local.properties
16+
app/google-services.json

app/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
alias(libs.plugins.androidApplication)
44
alias(libs.plugins.kotlinAndroid)
55
id("io.sentry.android.gradle") version "3.11.1"
6+
id("com.google.gms.google-services")
67
}
78

89
android {
@@ -62,6 +63,11 @@ android {
6263
}
6364

6465
dependencies {
66+
// Firebase
67+
implementation(platform("com.google.firebase:firebase-bom:32.1.1"))
68+
implementation("com.google.firebase:firebase-messaging-ktx:23.1.2")
69+
implementation("com.google.firebase:firebase-analytics-ktx:21.3.0")
70+
6571
// TPU
6672
implementation("com.google.accompanist:accompanist-insets:0.22.0-rc")
6773
implementation("io.noties.markwon:core:4.6.2")
@@ -105,6 +111,7 @@ dependencies {
105111
// Android Studio Preview support
106112
implementation("androidx.compose.ui:ui-tooling-preview")
107113
implementation(libs.androidx.appcompat)
114+
implementation(libs.androidx.work.runtime.ktx)
108115
debugImplementation("androidx.compose.ui:ui-tooling")
109116

110117
// UI Tests

app/src/main/AndroidManifest.xml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,27 @@
1616
android:usesCleartextTraffic="true"
1717
android:windowSoftInputMode="adjustResize"
1818
tools:targetApi="31">
19-
<service android:name=".ChatService"
19+
<meta-data
20+
android:name="com.google.firebase.messaging.default_notification_channel_id"
21+
android:value="communications" />
22+
<service
23+
android:name=".FirebaseChatService"
24+
android:exported="false">
25+
<intent-filter>
26+
<action android:name="com.google.firebase.MESSAGING_EVENT" />
27+
</intent-filter>
28+
</service>
29+
<!-- <service android:name=".ChatService"
2030
android:exported="true"
2131
android:stopWithTask="false"
2232
android:permission="android.permission.POST_NOTIFICATIONS">
2333
<intent-filter>
2434
<action android:name="android.service.notification.NotificationListenerService" />
2535
</intent-filter>
26-
</service>
36+
</service>-->
2737
<receiver
2838
android:name=".InlineNotificationActivity"
29-
android:exported="true"
39+
android:exported="false"
3040
android:permission="android.permission.INTERNET">
3141
<intent-filter>
3242
<action android:name="QUICK_REPLY_ACTION" />

app/src/main/java/com/troplo/privateuploader/ChatService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// NO LONGER USED, REPLACED BY FirebaseChatService.kt
2+
13
package com.troplo.privateuploader
24

35
import android.Manifest
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package com.troplo.privateuploader
2+
3+
import android.Manifest
4+
import android.app.ActivityManager
5+
import android.app.ActivityManager.RunningAppProcessInfo
6+
import android.app.NotificationChannel
7+
import android.app.NotificationManager
8+
import android.app.PendingIntent
9+
import android.content.Context
10+
import android.content.Intent
11+
import android.content.pm.PackageManager
12+
import android.util.Log
13+
import androidx.core.app.ActivityCompat
14+
import androidx.core.app.NotificationCompat
15+
import androidx.core.app.NotificationManagerCompat
16+
import androidx.core.app.Person
17+
import androidx.core.app.RemoteInput
18+
import androidx.work.OneTimeWorkRequest
19+
import androidx.work.WorkManager
20+
import androidx.work.Worker
21+
import androidx.work.WorkerParameters
22+
import com.google.firebase.messaging.FirebaseMessagingService
23+
import com.google.firebase.messaging.RemoteMessage
24+
import com.troplo.privateuploader.api.TpuApi
25+
import com.troplo.privateuploader.api.TpuFunctions
26+
import com.troplo.privateuploader.data.model.FCMTokenRequest
27+
import com.troplo.privateuploader.data.model.MessageEventFirebase
28+
import kotlinx.coroutines.CoroutineScope
29+
import kotlinx.coroutines.Dispatchers
30+
import kotlinx.coroutines.launch
31+
32+
33+
class FirebaseChatService : FirebaseMessagingService() {
34+
private val messages = mutableMapOf<Int, MutableList<NotificationCompat.MessagingStyle.Message>>()
35+
36+
private fun isAppOnForeground(context: Context): Boolean {
37+
val activityManager = context.getSystemService(ACTIVITY_SERVICE) as ActivityManager
38+
val appProcesses = activityManager.runningAppProcesses ?: return false
39+
val packageName = context.packageName
40+
for (appProcess in appProcesses) {
41+
if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName == packageName) {
42+
return true
43+
}
44+
}
45+
return false
46+
}
47+
48+
override fun onMessageReceived(remoteMessage: RemoteMessage) {
49+
Log.d(TAG, "[NewChatService] Message received")
50+
if(isAppOnForeground(this)) {
51+
Log.d(TAG, "[NewChatService] App is on foreground")
52+
return
53+
}
54+
sendNotification(
55+
MessageEventFirebase(
56+
content = remoteMessage.data["content"] ?: "",
57+
userId = remoteMessage.data["userId"]?.toInt() ?: 0,
58+
username = remoteMessage.data["username"] ?: "",
59+
createdAt = remoteMessage.data["createdAt"] ?: "",
60+
chatName = remoteMessage.data["chatName"] ?: "",
61+
associationId = remoteMessage.data["associationId"]?.toInt() ?: 0,
62+
avatar = remoteMessage.data["avatar"] ?: "",
63+
id = remoteMessage.data["id"]?.toInt() ?: 0
64+
)
65+
)
66+
}
67+
// [END receive_message]
68+
69+
private fun needsToBeScheduled() = true
70+
71+
// [START on_new_token]
72+
/**
73+
* Called if the FCM registration token is updated. This may occur if the security of
74+
* the previous token had been compromised. Note that this is called when the
75+
* FCM registration token is initially generated so this is where you would retrieve the token.
76+
*/
77+
override fun onNewToken(token: String) {
78+
Log.d(TAG, "Refreshed token: $token")
79+
80+
// If you want to send messages to this application instance or
81+
// manage this apps subscriptions on the server side, send the
82+
// FCM registration token to your app server.
83+
sendRegistrationToServer(token)
84+
}
85+
// [END on_new_token]
86+
87+
private fun scheduleJob() {
88+
// [START dispatch_job]
89+
val work = OneTimeWorkRequest.Builder(MyWorker::class.java)
90+
.build()
91+
WorkManager.getInstance(this)
92+
.beginWith(work)
93+
.enqueue()
94+
// [END dispatch_job]
95+
}
96+
97+
private fun handleNow() {
98+
Log.d(TAG, "Short lived task is done.")
99+
}
100+
101+
private fun sendRegistrationToServer(token: String?) {
102+
if(token != null) {
103+
Log.d("$TAG.FCMToken", token)
104+
CoroutineScope(Dispatchers.IO).launch {
105+
TpuApi.retrofitService.registerFcmToken(FCMTokenRequest(token))
106+
}
107+
}
108+
}
109+
110+
private fun sendNotification(message: MessageEventFirebase) {
111+
Log.d("TPU.Untagged", "[ChatService] Sending notification")
112+
113+
// Add any additional configuration to the notification builder as needed
114+
if (ActivityCompat.checkSelfPermission(
115+
this,
116+
Manifest.permission.POST_NOTIFICATIONS
117+
) != PackageManager.PERMISSION_GRANTED
118+
) {
119+
Log.d("TPU.Untagged", "[ChatService] No permission to post notifications")
120+
// TODO: Consider calling
121+
// ActivityCompat#requestPermissions
122+
// here to request the missing permissions, and then overriding
123+
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
124+
// int[] grantResults)
125+
// to handle the case where the user grants the permission. See the documentation
126+
// for ActivityCompat#requestPermissions for more details.
127+
return
128+
}
129+
asyncLoadIcon(message.avatar, this) {
130+
try {
131+
Log.d("TPU.Untagged", "[ChatService] Loaded icon")
132+
val chatPartner = Person.Builder().apply {
133+
setName(message.username)
134+
setKey(message.userId.toString())
135+
setIcon(it)
136+
setImportant(false)
137+
}.build()
138+
139+
val notificationManager = NotificationManagerCompat.from(this)
140+
val channel = NotificationChannel(
141+
"communications",
142+
"Messages from Communications",
143+
NotificationManager.IMPORTANCE_HIGH
144+
)
145+
notificationManager.createNotificationChannel(channel)
146+
if (messages[message.associationId] == null) messages[message.associationId] = mutableListOf()
147+
messages[message.associationId]?.add(
148+
NotificationCompat.MessagingStyle.Message(
149+
message.content,
150+
TpuFunctions.getDate(message.createdAt)?.time ?: 0,
151+
chatPartner
152+
)
153+
)
154+
155+
val style = NotificationCompat.MessagingStyle(chatPartner)
156+
.setConversationTitle(message.chatName)
157+
158+
for (msg in messages[message.associationId]!!) {
159+
style.addMessage(msg)
160+
}
161+
162+
val replyIntent = Intent(this, InlineNotificationActivity::class.java)
163+
replyIntent.putExtra("chatId", 69)
164+
val replyPendingIntent = PendingIntent.getBroadcast(this, 0, replyIntent, PendingIntent.FLAG_MUTABLE)
165+
166+
val remoteInput = RemoteInput.Builder("content")
167+
.setLabel("Reply")
168+
.build()
169+
170+
val replyAction = NotificationCompat.Action.Builder(
171+
R.drawable.tpu_logo,
172+
"Reply",
173+
replyPendingIntent
174+
)
175+
.addRemoteInput(remoteInput)
176+
.setAllowGeneratedReplies(true)
177+
.build()
178+
179+
val builder: NotificationCompat.Builder = NotificationCompat.Builder(this, "communications")
180+
.addPerson(chatPartner)
181+
.setStyle(style)
182+
.setContentText(message.content)
183+
.setContentTitle(message.username)
184+
.setSmallIcon(R.drawable.tpu_logo)
185+
.setWhen(TpuFunctions.getDate(message.createdAt)?.time ?: 0)
186+
.addAction(replyAction)
187+
val res = notificationManager.notify(message.associationId, builder.build())
188+
Log.d("TPU.Untagged", "[ChatService] Notification sent, $res")
189+
} catch (e: Exception) {
190+
Log.d("TPU.Untagged", "[ChatService] Error sending notification, ${e.printStackTrace()}")
191+
}
192+
}
193+
}
194+
195+
companion object {
196+
private const val TAG = "FirebaseChatService"
197+
}
198+
199+
internal class MyWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
200+
override fun doWork(): Result {
201+
// TODO(developer): add long running task here.
202+
return Result.success()
203+
}
204+
}
205+
}

app/src/main/java/com/troplo/privateuploader/InlineNotificationActivity.kt

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,35 @@ import java.io.Serializable
2121

2222
class InlineNotificationActivity: BroadcastReceiver() {
2323
override fun onReceive(context: Context, intent: Intent) {
24-
Log.d("TPU.Untagged", "[ChatService] InlineNotificationActivity onCreate, intent: $intent, extras: ${intent.extras}")
24+
try {
25+
Log.d(
26+
"TPU.Untagged",
27+
"[ChatService] InlineNotificationActivity onCreate, intent: $intent, extras: ${intent.extras}"
28+
)
2529

26-
val chatId = intent.getIntExtra("chatId", 0)
27-
val remoteInput = RemoteInput.getResultsFromIntent(intent)
28-
val content = remoteInput?.getCharSequence("content")?.toString()
29-
TpuApi.init(SessionManager(context).getAuthToken() ?: "", context)
30-
sendReply(chatId, content, context)
30+
val chatId = intent.getIntExtra("chatId", 0)
31+
val remoteInput = RemoteInput.getResultsFromIntent(intent)
32+
val content = remoteInput?.getCharSequence("content")?.toString()
33+
TpuApi.init(SessionManager(context).getAuthToken() ?: "", context)
34+
sendReply(chatId, content, context)
35+
} catch (e: Exception) {
36+
Log.d("TPU.InlineNotificationActivity", "Exception: $e")
37+
}
3138
}
3239

3340
private fun sendReply(chatId: Int, content: String?, context: Context) {
34-
if(chatId == 0) return
35-
Log.d("TPU.Untagged", "Sending reply to chatId: $chatId")
36-
CoroutineScope(Dispatchers.IO).launch {
37-
val response = TpuApi.retrofitService.sendMessage(id = chatId, messageRequest = MessageRequest(
38-
content = content ?: ""
39-
)).execute()
41+
try {
42+
if (chatId == 0) return
43+
Log.d("TPU.Untagged", "Sending reply to chatId: $chatId")
44+
CoroutineScope(Dispatchers.IO).launch {
45+
val response = TpuApi.retrofitService.sendMessage(
46+
id = chatId, messageRequest = MessageRequest(
47+
content = content ?: ""
48+
)
49+
).execute()
50+
}
51+
} catch (e: Exception) {
52+
Log.d("TPU.InlineNotificationActivity", "sendReply exception: $e")
4053
}
4154
}
4255
}

0 commit comments

Comments
 (0)