Merge remote-tracking branch 'origin/master' into feature/edit_messages

This commit is contained in:
Marcel Hibbe 2024-02-16 17:09:03 +01:00
commit 9a4bf14e09
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
15 changed files with 262 additions and 49 deletions

View File

@ -32,7 +32,7 @@ jobs:
with: with:
swap-size-gb: 10 swap-size-gb: 10
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 uses: github/codeql-action/init@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
- name: Set up JDK 17 - name: Set up JDK 17
@ -46,4 +46,4 @@ jobs:
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties" echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
./gradlew assembleDebug ./gradlew assembleDebug
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 uses: github/codeql-action/analyze@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3

View File

@ -19,4 +19,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: gradle/wrapper-validation-action@27152f6fa06a6b8062ef7195c795692e51fc2c81 # v2.0.0 - uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1

View File

@ -37,6 +37,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 uses: github/codeql-action/upload-sarif@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -25,7 +25,7 @@ jobs:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17
- name: Run unit tests with coverage - name: Run unit tests with coverage
uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0
with: with:
arguments: testGplayDebugUnit arguments: testGplayDebugUnit
- name: Upload test artifacts - name: Upload test artifacts

View File

@ -48,8 +48,8 @@ android {
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable) // mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
// xx .xxx .xx .xx // xx .xxx .xx .xx
versionCode 180010010 versionCode 190000001
versionName "18.1.0 Alpha 10" versionName "19.0.0 Alpha 01"
flavorDimensions "default" flavorDimensions "default"
renderscriptTargetApi 19 renderscriptTargetApi 19
@ -247,7 +247,7 @@ dependencies {
implementation 'com.github.wooplr:Spotlight:1.3' implementation 'com.github.wooplr:Spotlight:1.3'
implementation 'com.google.code.findbugs:jsr305:3.0.2' implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation 'com.github.nextcloud-deps:ChatKit:0.4.2' implementation 'com.github.nextcloud-deps:ChatKit:0.4.2'
implementation 'joda-time:joda-time:2.12.6' implementation 'joda-time:joda-time:2.12.7'
implementation "io.coil-kt:coil:${coilKtVersion}" implementation "io.coil-kt:coil:${coilKtVersion}"
implementation "io.coil-kt:coil-gif:${coilKtVersion}" implementation "io.coil-kt:coil-gif:${coilKtVersion}"
implementation "io.coil-kt:coil-svg:${coilKtVersion}" implementation "io.coil-kt:coil-svg:${coilKtVersion}"
@ -308,11 +308,11 @@ dependencies {
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.4' spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.4'
gplayImplementation 'com.google.android.gms:play-services-base:18.3.0' gplayImplementation 'com.google.android.gms:play-services-base:18.3.0'
gplayImplementation "com.google.firebase:firebase-messaging:23.4.0" gplayImplementation "com.google.firebase:firebase-messaging:23.4.1"
implementation 'androidx.activity:activity-ktx:1.8.2' implementation 'androidx.activity:activity-ktx:1.8.2'
implementation 'com.github.nextcloud.android-common:ui:0.14.0' implementation 'com.github.nextcloud.android-common:ui:0.15.0'
implementation 'com.github.nextcloud-deps:android-talk-webrtc:110.5481.0' implementation 'com.github.nextcloud-deps:android-talk-webrtc:110.5481.0'
} }

View File

@ -23,8 +23,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<application <application
android:name=".application.NextcloudTalkApplication" android:name=".application.NextcloudTalkApplication"
android:allowBackup="true" android:allowBackup="true"

View File

@ -1117,7 +1117,7 @@ class CallActivity : CallBaseActivity() {
micInputAudioRecorder.read(byteArr, 0, byteArr.size) micInputAudioRecorder.read(byteArr, 0, byteArr.size)
val isCurrentlySpeaking = abs(byteArr[0].toDouble()) > MICROPHONE_VALUE_THRESHOLD val isCurrentlySpeaking = abs(byteArr[0].toDouble()) > MICROPHONE_VALUE_THRESHOLD
if (isCurrentlySpeaking && !isSpeakingLongTerm) { if (microphoneOn && isCurrentlySpeaking && !isSpeakingLongTerm) {
isSpeakingLongTerm = true isSpeakingLongTerm = true
sendIsSpeakingMessage(true) sendIsSpeakingMessage(true)
} else if (!isCurrentlySpeaking && isSpeakingLongTerm) { } else if (!isCurrentlySpeaking && isSpeakingLongTerm) {

View File

@ -31,10 +31,12 @@ package com.nextcloud.talk.chat
import android.Manifest import android.Manifest
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor import android.content.res.AssetFileDescriptor
import android.content.res.Resources import android.content.res.Resources
@ -42,7 +44,9 @@ import android.database.Cursor
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.media.AudioFocusRequest
import android.media.AudioFormat import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord import android.media.AudioRecord
import android.media.MediaPlayer import android.media.MediaPlayer
import android.media.MediaRecorder import android.media.MediaRecorder
@ -389,6 +393,20 @@ class ChatActivity :
private var lastRecordMediaPosition: Int = 0 private var lastRecordMediaPosition: Int = 0
private var lastRecordedSeeked: Boolean = false private var lastRecordedSeeked: Boolean = false
private val audioFocusChangeListener = getAudioFocusChangeListener()
private val noisyAudioStreamReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
chatViewModel.isPausedDueToBecomingNoisy = true
if (isVoicePreviewPlaying) {
pausePreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
pausePlayback(currentlyPlayedVoiceMessage!!)
}
}
}
private lateinit var participantPermissions: ParticipantPermissions private lateinit var participantPermissions: ParticipantPermissions
private var videoURI: Uri? = null private var videoURI: Uri? = null
@ -1217,7 +1235,9 @@ class ChatActivity :
binding.messageInputView.micInputCloud.setOnClickListener { binding.messageInputView.micInputCloud.setOnClickListener {
if (mediaRecorderState == MediaRecorderState.RECORDING) { if (mediaRecorderState == MediaRecorderState.RECORDING) {
audioFocusRequest(false) {
recorder?.stop() recorder?.stop()
}
mediaRecorderState = MediaRecorderState.INITIAL mediaRecorderState = MediaRecorderState.INITIAL
stopMicInputRecordingAnimation() stopMicInputRecordingAnimation()
showPreviewVoiceRecording(true) showPreviewVoiceRecording(true)
@ -1446,8 +1466,11 @@ class ChatActivity :
duration = it.duration.toLong() duration = it.duration.toLong()
interpolator = LinearInterpolator() interpolator = LinearInterpolator()
} }
audioFocusRequest(true) {
voicePreviewMediaPlayer!!.start() voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.start() voicePreviewObjectAnimator!!.start()
handleBecomingNoisyBroadcast(register = true)
}
} }
setOnCompletionListener { setOnCompletionListener {
@ -1461,15 +1484,21 @@ class ChatActivity :
if (voicePreviewMediaPlayer == null) { if (voicePreviewMediaPlayer == null) {
initPreviewVoiceRecording() initPreviewVoiceRecording()
} else { } else {
audioFocusRequest(true) {
voicePreviewMediaPlayer!!.start() voicePreviewMediaPlayer!!.start()
voicePreviewObjectAnimator!!.resume() voicePreviewObjectAnimator!!.resume()
handleBecomingNoisyBroadcast(register = true)
}
} }
} }
private fun pausePreviewVoicePlaying() { private fun pausePreviewVoicePlaying() {
Log.d(TAG, "paused preview voice recording") Log.d(TAG, "paused preview voice recording")
audioFocusRequest(false) {
voicePreviewMediaPlayer!!.pause() voicePreviewMediaPlayer!!.pause()
voicePreviewObjectAnimator!!.pause() voicePreviewObjectAnimator!!.pause()
handleBecomingNoisyBroadcast(register = false)
}
} }
private fun stopPreviewVoicePlaying() { private fun stopPreviewVoicePlaying() {
@ -1479,9 +1508,12 @@ class ChatActivity :
voicePreviewObjectAnimator!!.end() voicePreviewObjectAnimator!!.end()
voicePreviewObjectAnimator = null voicePreviewObjectAnimator = null
binding.messageInputView.seekBar.clearAnimation() binding.messageInputView.seekBar.clearAnimation()
audioFocusRequest(false) {
voicePreviewMediaPlayer!!.stop() voicePreviewMediaPlayer!!.stop()
voicePreviewMediaPlayer!!.release() voicePreviewMediaPlayer!!.release()
voicePreviewMediaPlayer = null voicePreviewMediaPlayer = null
handleBecomingNoisyBroadcast(register = false)
}
} }
} }
@ -1935,6 +1967,73 @@ class ChatActivity :
} }
} }
private fun getAudioFocusChangeListener(): AudioManager.OnAudioFocusChangeListener {
return AudioManager.OnAudioFocusChangeListener { flag ->
when (flag) {
AudioManager.AUDIOFOCUS_LOSS -> {
chatViewModel.isPausedDueToBecomingNoisy = false
if (isVoicePreviewPlaying) {
stopPreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
stopMediaPlayer(currentlyPlayedVoiceMessage!!)
}
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
chatViewModel.isPausedDueToBecomingNoisy = false
if (isVoicePreviewPlaying) {
pausePreviewVoicePlaying()
}
if (currentlyPlayedVoiceMessage != null) {
pausePlayback(currentlyPlayedVoiceMessage!!)
}
}
}
}
}
private fun audioFocusRequest(shouldRequestFocus: Boolean, onGranted: () -> Unit) {
if (chatViewModel.isPausedDueToBecomingNoisy) {
onGranted()
return
}
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
val duration = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
val isGranted: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val focusRequest = AudioFocusRequest.Builder(duration)
.setOnAudioFocusChangeListener(audioFocusChangeListener)
.build()
if (shouldRequestFocus) {
audioManager.requestAudioFocus(focusRequest)
} else {
audioManager.abandonAudioFocusRequest(focusRequest)
}
} else {
@Deprecated("This method was deprecated in API level 26.")
if (shouldRequestFocus) {
audioManager.requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, duration)
} else {
audioManager.abandonAudioFocus(audioFocusChangeListener)
}
}
if (isGranted == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
onGranted()
}
}
private fun handleBecomingNoisyBroadcast(register: Boolean) {
if (register && !chatViewModel.receiverRegistered) {
registerReceiver(noisyAudioStreamReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
chatViewModel.receiverRegistered = true
} else if (!chatViewModel.receiverUnregistered) {
unregisterReceiver(noisyAudioStreamReceiver)
chatViewModel.receiverUnregistered = true
chatViewModel.receiverRegistered = false
}
}
private fun startPlayback(message: ChatMessage) { private fun startPlayback(message: ChatMessage) {
if (!active) { if (!active) {
// don't begin to play voice message if screen is not visible anymore. // don't begin to play voice message if screen is not visible anymore.
@ -1948,8 +2047,10 @@ class ChatActivity :
mediaPlayer?.let { mediaPlayer?.let {
if (!it.isPlaying) { if (!it.isPlaying) {
audioFocusRequest(true) {
it.start() it.start()
Log.d(TAG, "MediaPlayer has Started") handleBecomingNoisyBroadcast(register = true)
}
} }
mediaPlayerHandler = Handler() mediaPlayerHandler = Handler()
@ -1984,8 +2085,10 @@ class ChatActivity :
private fun pausePlayback(message: ChatMessage) { private fun pausePlayback(message: ChatMessage) {
if (mediaPlayer!!.isPlaying) { if (mediaPlayer!!.isPlaying) {
audioFocusRequest(false) {
mediaPlayer!!.pause() mediaPlayer!!.pause()
Log.d(TAG, "MediaPlayer is paused") handleBecomingNoisyBroadcast(register = false)
}
} }
message.isPlayingVoiceMessage = false message.isPlayingVoiceMessage = false
@ -2043,7 +2146,10 @@ class ChatActivity :
mediaPlayer?.let { mediaPlayer?.let {
if (it.isPlaying) { if (it.isPlaying) {
Log.d(TAG, "media player is stopped") Log.d(TAG, "media player is stopped")
audioFocusRequest(false) {
it.stop() it.stop()
handleBecomingNoisyBroadcast(register = false)
}
} }
} }
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
@ -2259,8 +2365,10 @@ class ChatActivity :
private fun stopMicInputRecordingAnimation() { private fun stopMicInputRecordingAnimation() {
if (micInputAudioRecordThread != null) { if (micInputAudioRecordThread != null) {
Log.d(TAG, "Mic Animation Ended") Log.d(TAG, "Mic Animation Ended")
audioFocusRequest(false) {
micInputAudioRecorder.stop() micInputAudioRecorder.stop()
micInputAudioRecorder.release() micInputAudioRecorder.release()
}
isMicInputAudioThreadRunning = false isMicInputAudioThreadRunning = false
micInputAudioRecordThread = null micInputAudioRecordThread = null
} }
@ -2311,7 +2419,9 @@ class ChatActivity :
} }
try { try {
audioFocusRequest(true) {
start() start()
}
mediaRecorderState = MediaRecorderState.RECORDING mediaRecorderState = MediaRecorderState.RECORDING
Log.d(TAG, "recording started") Log.d(TAG, "recording started")
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
@ -2352,8 +2462,10 @@ class ChatActivity :
recorder?.apply { recorder?.apply {
try { try {
if (mediaRecorderState == MediaRecorderState.RECORDING) { if (mediaRecorderState == MediaRecorderState.RECORDING) {
audioFocusRequest(false) {
stop() stop()
reset() reset()
}
mediaRecorderState = MediaRecorderState.INITIAL mediaRecorderState = MediaRecorderState.INITIAL
Log.d(TAG, "stopped recorder") Log.d(TAG, "stopped recorder")
} }

View File

@ -48,6 +48,11 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository)
open class GetReminderExistState(val reminder: Reminder) : ViewState open class GetReminderExistState(val reminder: Reminder) : ViewState
private val _getReminderExistState: MutableLiveData<ViewState> = MutableLiveData(GetReminderStartState) private val _getReminderExistState: MutableLiveData<ViewState> = MutableLiveData(GetReminderStartState)
var isPausedDueToBecomingNoisy = false
var receiverRegistered = false
var receiverUnregistered = false
val getReminderExistState: LiveData<ViewState> val getReminderExistState: LiveData<ViewState>
get() = _getReminderExistState get() = _getReminderExistState

View File

@ -44,6 +44,7 @@ import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.media3.common.AudioAttributes
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
@ -165,7 +166,10 @@ class FullScreenMediaActivity : AppCompatActivity() {
} }
private fun initializePlayer() { private fun initializePlayer() {
player = ExoPlayer.Builder(applicationContext).build() player = ExoPlayer.Builder(applicationContext)
.setAudioAttributes(AudioAttributes.DEFAULT, true)
.setHandleAudioBecomingNoisy(true)
.build()
binding.playerView.player = player binding.playerView.player = player
} }
@ -202,6 +206,7 @@ class FullScreenMediaActivity : AppCompatActivity() {
windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
supportActionBar?.show() supportActionBar?.show()
} }
private fun applyWindowInsets() { private fun applyWindowInsets() {
val playerView = binding.playerView val playerView = binding.playerView
val exoControls = playerView.findViewById<FrameLayout>(R.id.exo_bottom_bar) val exoControls = playerView.findViewById<FrameLayout>(R.id.exo_bottom_bar)

View File

@ -2,7 +2,9 @@
* Nextcloud Talk application * Nextcloud Talk application
* *
* @author Marcel Hibbe * @author Marcel Hibbe
* @author Parneet Singh
* Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2024-2025 Parneet Singh <gurayaparneet@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -22,7 +24,6 @@ package com.nextcloud.talk.jobs
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.Notification
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
@ -86,7 +87,6 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
private var mNotifyManager: NotificationManager? = null private var mNotifyManager: NotificationManager? = null
private var mBuilder: NotificationCompat.Builder? = null private var mBuilder: NotificationCompat.Builder? = null
private lateinit var notification: Notification
private var notificationId: Int = 0 private var notificationId: Int = 0
lateinit var roomToken: String lateinit var roomToken: String
@ -168,7 +168,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
} }
if (uploadSuccess) { if (uploadSuccess) {
mNotifyManager?.cancel(notificationId) cancelNotification()
return Result.success() return Result.success()
} else if (isStopped) { } else if (isStopped) {
// since work is cancelled the result would be ignored anyways // since work is cancelled the result would be ignored anyways
@ -196,12 +196,12 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
} }
override fun onTransferProgress(percentage: Int) { override fun onTransferProgress(percentage: Int) {
notification = mBuilder!! val progressUpdateNotification = mBuilder!!
.setProgress(HUNDRED_PERCENT, percentage, false) .setProgress(HUNDRED_PERCENT, percentage, false)
.setContentText(getNotificationContentText(percentage)) .setContentText(getNotificationContentText(percentage))
.build() .build()
mNotifyManager!!.notify(notificationId, notification) mNotifyManager!!.notify(notificationId, progressUpdateNotification)
} }
override fun onStopped() { override fun onStopped() {
@ -223,22 +223,60 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
} }
private fun initNotificationWithPercentage() { private fun initNotificationWithPercentage() {
notification = mBuilder!! val initNotification = mBuilder!!
.setContentTitle(getResourceString(context, R.string.nc_upload_in_progess)) .setContentTitle(context.resources.getString(R.string.nc_upload_in_progess))
.setContentText(getNotificationContentText(ZERO_PERCENT)) .setContentText(getNotificationContentText(ZERO_PERCENT))
.setSmallIcon(R.drawable.upload_white) .setSmallIcon(R.drawable.upload_white)
.setOngoing(true) .setOngoing(true)
.setProgress(HUNDRED_PERCENT, ZERO_PERCENT, false) .setProgress(HUNDRED_PERCENT, ZERO_PERCENT, false)
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.setGroup(NotificationUtils.KEY_UPLOAD_GROUP)
.setContentIntent(getIntentToOpenConversation()) .setContentIntent(getIntentToOpenConversation())
.addAction( .addAction(
R.drawable.ic_cancel_white_24dp, getResourceString(context, R.string.nc_cancel), R.drawable.ic_cancel_white_24dp,
getResourceString(context, R.string.nc_cancel),
getCancelUploadIntent() getCancelUploadIntent()
) )
.build() .build()
notificationId = SystemClock.uptimeMillis().toInt() notificationId = SystemClock.uptimeMillis().toInt()
mNotifyManager!!.notify(notificationId, notification) mNotifyManager!!.notify(notificationId, initNotification)
// only need one summary notification but multiple upload worker can call it more than once but it is safe
// because of the same notification object config and id.
makeSummaryNotification()
}
private fun makeSummaryNotification() {
// summary notification encapsulating the group of notifications
val summaryNotification = NotificationCompat.Builder(
context,
NotificationUtils.NotificationChannels
.NOTIFICATION_CHANNEL_UPLOADS.name
).setSmallIcon(R.drawable.upload_white)
.setGroup(NotificationUtils.KEY_UPLOAD_GROUP)
.setGroupSummary(true)
.build()
mNotifyManager?.notify(NotificationUtils.GROUP_SUMMARY_NOTIFICATION_ID, summaryNotification)
}
private fun getActiveUploadNotifications(): Int? {
// filter out active notifications that are upload notifications using group
return mNotifyManager?.activeNotifications?.filter {
it.notification.group == NotificationUtils
.KEY_UPLOAD_GROUP
}?.size
}
private fun cancelNotification() {
mNotifyManager?.cancel(notificationId)
// summary notification would not get dismissed automatically
// if child notifications are cancelled programmatically
// so check if only 1 notification left if yes
// then cancel it (which is summary notification)
if (getActiveUploadNotifications() == 1) {
mNotifyManager?.cancel(NotificationUtils.GROUP_SUMMARY_NOTIFICATION_ID)
}
} }
private fun getNotificationContentText(percentage: Int): String { private fun getNotificationContentText(percentage: Int): String {
@ -289,17 +327,21 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
getResourceString(context, R.string.nc_upload_failed_notification_text), getResourceString(context, R.string.nc_upload_failed_notification_text),
fileName fileName
) )
notification = mBuilder!! val failureNotification = NotificationCompat.Builder(
context,
NotificationUtils.NotificationChannels
.NOTIFICATION_CHANNEL_UPLOADS.name
)
.setContentTitle(failureTitle) .setContentTitle(failureTitle)
.setContentText(failureText) .setContentText(failureText)
.setSmallIcon(R.drawable.baseline_error_24) .setSmallIcon(R.drawable.baseline_error_24)
.setGroup(NotificationUtils.KEY_UPLOAD_GROUP)
.setOngoing(false) .setOngoing(false)
.build() .build()
// Cancel original notification
mNotifyManager?.cancel(notificationId) mNotifyManager?.cancel(notificationId)
// Then show information about failure // update current notification with failure info
mNotifyManager!!.notify(SystemClock.uptimeMillis().toInt(), notification) mNotifyManager!!.notify(SystemClock.uptimeMillis().toInt(), failureNotification)
} }
private fun getResourceString(context: Context, resourceId: Int): String { private fun getResourceString(context: Context, resourceId: Int): String {

View File

@ -68,6 +68,10 @@ object NotificationUtils {
// RemoteInput key - used for replies sent directly from notification // RemoteInput key - used for replies sent directly from notification
const val KEY_DIRECT_REPLY = "key_direct_reply" const val KEY_DIRECT_REPLY = "key_direct_reply"
// notification group keys
const val KEY_UPLOAD_GROUP = "com.nextcloud.talk.utils.KEY_UPLOAD_GROUP"
const val GROUP_SUMMARY_NOTIFICATION_ID = -1
@TargetApi(Build.VERSION_CODES.O) @TargetApi(Build.VERSION_CODES.O)
private fun createNotificationChannel( private fun createNotificationChannel(
context: Context, context: Context,

View File

@ -109,6 +109,7 @@
<string name="nc_conversations_empty">会話に参加するか、新しいのを開始</string> <string name="nc_conversations_empty">会話に参加するか、新しいのを開始</string>
<string name="nc_conversations_empty_details">友達や同僚に挨拶しましょう!</string> <string name="nc_conversations_empty_details">友達や同僚に挨拶しましょう!</string>
<string name="nc_copy_message">コピー</string> <string name="nc_copy_message">コピー</string>
<string name="nc_create_poll">投票を作成</string>
<string name="nc_date_header_today">今日</string> <string name="nc_date_header_today">今日</string>
<string name="nc_date_header_yesterday">昨日</string> <string name="nc_date_header_yesterday">昨日</string>
<string name="nc_delete">削除</string> <string name="nc_delete">削除</string>
@ -144,6 +145,7 @@
<string name="nc_expire_message_one_day">1日</string> <string name="nc_expire_message_one_day">1日</string>
<string name="nc_expire_message_one_hour">1時間</string> <string name="nc_expire_message_one_hour">1時間</string>
<string name="nc_expire_message_one_week">1週間</string> <string name="nc_expire_message_one_week">1週間</string>
<string name="nc_expire_messages_explanation">チャットメッセージは、一定時間経過後に期限切れにすることができます。注意: チャットで共有されたファイルは、ファイル所有者は削除されませんが、会話では共有されなくなります。</string>
<string name="nc_external_server_failed">シグナリング設定を取得できませんでした</string> <string name="nc_external_server_failed">シグナリング設定を取得できませんでした</string>
<string name="nc_failed_signaling_settings">ターゲットのサーバーは、モバイル経由での公開の会話への参加をサポートしていません。Webブラウザー経由で会話への参加を試みてください。</string> <string name="nc_failed_signaling_settings">ターゲットのサーバーは、モバイル経由での公開の会話への参加をサポートしていません。Webブラウザー経由で会話への参加を試みてください。</string>
<string name="nc_failed_to_perform_operation">申し訳ありませんが、何かがおかしいです!</string> <string name="nc_failed_to_perform_operation">申し訳ありませんが、何かがおかしいです!</string>
@ -330,6 +332,7 @@
<string name="nc_settings_theme_key">テーマ</string> <string name="nc_settings_theme_key">テーマ</string>
<string name="nc_settings_theme_light">ライト</string> <string name="nc_settings_theme_light">ライト</string>
<string name="nc_settings_theme_title">テーマ</string> <string name="nc_settings_theme_title">テーマ</string>
<string name="nc_settings_typing_status_desc">自分のタイピング状況を共有し、他の人のタイピング状況を表示する</string>
<string name="nc_settings_use_credentials_title">プロキシは資格情報を必要とします</string> <string name="nc_settings_use_credentials_title">プロキシは資格情報を必要とします</string>
<string name="nc_settings_warning">警告</string> <string name="nc_settings_warning">警告</string>
<string name="nc_settings_wrong_account">現在のアカウントのみ再認可できます</string> <string name="nc_settings_wrong_account">現在のアカウントのみ再認可できます</string>
@ -377,8 +380,11 @@
<string name="open_in_files_app">アプリでファイルを開く</string> <string name="open_in_files_app">アプリでファイルを開く</string>
<string name="play_pause_voice_message">音声メッセージを再生/一時停止</string> <string name="play_pause_voice_message">音声メッセージを再生/一時停止</string>
<string name="polls_add_option">オプションを追加</string> <string name="polls_add_option">オプションを追加</string>
<string name="polls_end_poll">投票を終了する</string>
<string name="polls_multiple_answers">複数の答え</string>
<string name="polls_options">オプション</string> <string name="polls_options">オプション</string>
<string name="polls_private_poll">プライベート投票</string> <string name="polls_private_poll">プライベート投票</string>
<string name="polls_question">質問</string>
<string name="polls_results_subtitle">結果</string> <string name="polls_results_subtitle">結果</string>
<string name="polls_settings">設定</string> <string name="polls_settings">設定</string>
<string name="polls_submit_vote">投票</string> <string name="polls_submit_vote">投票</string>
@ -386,9 +392,13 @@
<string name="reactions_tab_all">すべて</string> <string name="reactions_tab_all">すべて</string>
<string name="read_storage_no_permission">ストレージからのファイル共有は権限がなければ不可能です</string> <string name="read_storage_no_permission">ストレージからのファイル共有は権限がなければ不可能です</string>
<string name="record_cancel_start">録画開始をキャンセル</string> <string name="record_cancel_start">録画開始をキャンセル</string>
<string name="record_failed_info">レコーディングが失敗しました。あなたの管理者に連絡してください。</string>
<string name="record_start_description">録画を開始</string> <string name="record_start_description">録画を開始</string>
<string name="record_stop_description">録画を停止</string> <string name="record_stop_description">録画を停止</string>
<string name="recording_consent_all">全ての通話には録音の同意が必要です。</string>
<string name="recording_consent_for_conversation_description">この会話に参加する前に、録音の同意が必要です。</string>
<string name="recording_consent_for_conversation_title">レコーディングの同意</string> <string name="recording_consent_for_conversation_title">レコーディングの同意</string>
<string name="recording_consent_title">通話は録音されるかもしれません。</string>
<string name="recording_settings_title">記録中</string> <string name="recording_settings_title">記録中</string>
<string name="save">保存</string> <string name="save">保存</string>
<string name="scope_federated_description">信頼できるサーバーのみと同期</string> <string name="scope_federated_description">信頼できるサーバーのみと同期</string>
@ -417,6 +427,7 @@
<string name="shared_items_file">ファイル</string> <string name="shared_items_file">ファイル</string>
<string name="shared_items_media">メディア</string> <string name="shared_items_media">メディア</string>
<string name="shared_items_other">その他</string> <string name="shared_items_other">その他</string>
<string name="shared_items_poll">投票</string>
<string name="shared_items_voice">音声番号</string> <string name="shared_items_voice">音声番号</string>
<string name="starred">お気に入り</string> <string name="starred">お気に入り</string>
<string name="startCallForbidden">通話を開始することが許可されていません</string> <string name="startCallForbidden">通話を開始することが許可されていません</string>
@ -436,6 +447,7 @@
<string name="today">今日</string> <string name="today">今日</string>
<string name="tomorrow">明日</string> <string name="tomorrow">明日</string>
<string name="translate">翻訳</string> <string name="translate">翻訳</string>
<string name="translation_copy_translated_text">翻訳されたテキストをコピー</string>
<string name="translation_detect_language">言語を検出する</string> <string name="translation_detect_language">言語を検出する</string>
<string name="translation_device_settings">デバイスの設定</string> <string name="translation_device_settings">デバイスの設定</string>
<string name="translation_error_message">言語を検出できませんでした</string> <string name="translation_error_message">言語を検出できませんでした</string>

View File

@ -264,7 +264,7 @@
<string name="nc_settings">Impostatziones</string> <string name="nc_settings">Impostatziones</string>
<string name="nc_settings_account_updated">Su contu chi tenias giai est istadu agiornadu, imbetzes de nd\'agiùnghere unu nou</string> <string name="nc_settings_account_updated">Su contu chi tenias giai est istadu agiornadu, imbetzes de nd\'agiùnghere unu nou</string>
<string name="nc_settings_advanced_title">Avantzadu</string> <string name="nc_settings_advanced_title">Avantzadu</string>
<string name="nc_settings_appearance">Visibilidade</string> <string name="nc_settings_appearance">Aspetu</string>
<string name="nc_settings_call_ringtone">Mutidas</string> <string name="nc_settings_call_ringtone">Mutidas</string>
<string name="nc_settings_incognito_keyboard_desc">Imparat a sa tastiera a disativare s\'imparu personale (chene garantzias)</string> <string name="nc_settings_incognito_keyboard_desc">Imparat a sa tastiera a disativare s\'imparu personale (chene garantzias)</string>
<string name="nc_settings_incognito_keyboard_title">Tastiera in incògnita</string> <string name="nc_settings_incognito_keyboard_title">Tastiera in incògnita</string>

View File

@ -143,7 +143,10 @@
<trusted-key id="AFCC4C7594D09E2182C60E0F7A01B0F236E5430F" group="com.google.code.gson"/> <trusted-key id="AFCC4C7594D09E2182C60E0F7A01B0F236E5430F" group="com.google.code.gson"/>
<trusted-key id="B02335AA54CCF21E52BBF9ABD9C565AA72BA2FDD" group="io.grpc"/> <trusted-key id="B02335AA54CCF21E52BBF9ABD9C565AA72BA2FDD" group="io.grpc"/>
<trusted-key id="B087A0EB8416563AFE64CEBA4604091C01C3086A" group="com.mebigfatguy.fb-contrib" name="fb-contrib" version="7.6.4"/> <trusted-key id="B087A0EB8416563AFE64CEBA4604091C01C3086A" group="com.mebigfatguy.fb-contrib" name="fb-contrib" version="7.6.4"/>
<trusted-key id="B41089A2DA79B0FA5810252872385FF0AF338D52" group="joda-time" name="joda-time" version="2.12.6"/> <trusted-key id="B41089A2DA79B0FA5810252872385FF0AF338D52">
<trusting group="joda-time" name="joda-time" version="2.12.6"/>
<trusting group="joda-time" name="joda-time" version="2.12.7"/>
</trusted-key>
<trusted-key id="B6E73D84EA4FCC47166087253FAAD2CD5ECBB314" group="org.apache.commons" name="commons-parent" version="52"/> <trusted-key id="B6E73D84EA4FCC47166087253FAAD2CD5ECBB314" group="org.apache.commons" name="commons-parent" version="52"/>
<trusted-key id="B801E2F8EF035068EC1139CC29579F18FA8FD93B" group="com.google.j2objc" name="j2objc-annotations" version="1.3"/> <trusted-key id="B801E2F8EF035068EC1139CC29579F18FA8FD93B" group="com.google.j2objc" name="j2objc-annotations" version="1.3"/>
<trusted-key id="BC87A3FD0A54480F0BADBEBD21939FF0CA2A6567" group="commons-codec" name="commons-codec" version="1.15"/> <trusted-key id="BC87A3FD0A54480F0BADBEBD21939FF0CA2A6567" group="commons-codec" name="commons-codec" version="1.15"/>
@ -1879,6 +1882,14 @@
<sha256 value="89c21ebe5a3aedd8c910bbe0f7c0c6ea6f30dc9dba58d68b39bee3759a7dc52f" origin="Generated by Gradle" reason="Artifact is not signed"/> <sha256 value="89c21ebe5a3aedd8c910bbe0f7c0c6ea6f30dc9dba58d68b39bee3759a7dc52f" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact> </artifact>
</component> </component>
<component group="com.github.nextcloud.android-common" name="core" version="0.15.0">
<artifact name="core-0.15.0.aar">
<sha256 value="d370010eeae5928f525f31c09e33f9c78ed5e610af4b9f84b1ba68ce0727267b" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="core-0.15.0.module">
<sha256 value="609090237e7d6c9745dcb883819e53bb4c9337b89740aa88fd1c56d73ecfa56b" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="0.14.0"> <component group="com.github.nextcloud.android-common" name="material-color-utilities" version="0.14.0">
<artifact name="material-color-utilities-0.14.0.jar"> <artifact name="material-color-utilities-0.14.0.jar">
<sha256 value="bfcd5205b056b948bc6ff9a556898188f4a4e92179c723906e10f1164b776eed" origin="Generated by Gradle" reason="Artifact is not signed"/> <sha256 value="bfcd5205b056b948bc6ff9a556898188f4a4e92179c723906e10f1164b776eed" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -1887,6 +1898,14 @@
<sha256 value="b12eadfbfe39b7fb6e62a13c9aca93b468e66a0cf0a2d841b849fe86ebc2d605" origin="Generated by Gradle" reason="Artifact is not signed"/> <sha256 value="b12eadfbfe39b7fb6e62a13c9aca93b468e66a0cf0a2d841b849fe86ebc2d605" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact> </artifact>
</component> </component>
<component group="com.github.nextcloud.android-common" name="material-color-utilities" version="0.15.0">
<artifact name="material-color-utilities-0.15.0.jar">
<sha256 value="ced5cd660ebfa6aa7461a2157f67a8e76ae12830f759adec0b51631de4ac5434" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="material-color-utilities-0.15.0.module">
<sha256 value="edc221870d47808e96595f11d58dc6f45e50a9d8979658f024ae9a816778ecfb" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud.android-common" name="ui" version="0.14.0"> <component group="com.github.nextcloud.android-common" name="ui" version="0.14.0">
<artifact name="ui-0.14.0.aar"> <artifact name="ui-0.14.0.aar">
<sha256 value="2ff1b8ca1ce4d4151ce967c4704a4def19a128d3f710491872e007217f3ac84f" origin="Generated by Gradle" reason="Artifact is not signed"/> <sha256 value="2ff1b8ca1ce4d4151ce967c4704a4def19a128d3f710491872e007217f3ac84f" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -1895,6 +1914,14 @@
<sha256 value="a600ef42a3ffe9a5900893f7f68052272ed8749ae2c5c44471f792f9d1d9d01f" origin="Generated by Gradle" reason="Artifact is not signed"/> <sha256 value="a600ef42a3ffe9a5900893f7f68052272ed8749ae2c5c44471f792f9d1d9d01f" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact> </artifact>
</component> </component>
<component group="com.github.nextcloud.android-common" name="ui" version="0.15.0">
<artifact name="ui-0.15.0.aar">
<sha256 value="7e520034d730a11f5089548a8f15fbdd5d5054f8f0465f711664235b3939b78d" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="ui-0.15.0.module">
<sha256 value="ac04b3a93aae2e9e8585d01447210fe0c5c85dec13e241b5e7c8956a3ed8a962" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.wooplr" name="Spotlight" version="1.3"> <component group="com.github.wooplr" name="Spotlight" version="1.3">
<artifact name="Spotlight-1.3.aar"> <artifact name="Spotlight-1.3.aar">
<sha256 value="2216a78710c8626623d3fd8f6519ec49e26d86930757b51288b7889653b7b44e" origin="Generated by Gradle" reason="Artifact is not signed"/> <sha256 value="2216a78710c8626623d3fd8f6519ec49e26d86930757b51288b7889653b7b44e" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -2225,6 +2252,14 @@
<sha256 value="f376e72c66dac4215b25d8dcda887ca355c661f630d2d155160e0c6dca7d4ace" origin="Generated by Gradle" reason="Artifact is not signed"/> <sha256 value="f376e72c66dac4215b25d8dcda887ca355c661f630d2d155160e0c6dca7d4ace" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact> </artifact>
</component> </component>
<component group="com.google.firebase" name="firebase-messaging" version="23.4.1">
<artifact name="firebase-messaging-23.4.1.aar">
<sha256 value="15c2637c28fb6895b6a4c998a191b663f3b78ac815f6ac59ab7d673a8d9af202" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="firebase-messaging-23.4.1.pom">
<sha256 value="188ee169e2d73488e9b14c7610bf3fd9a834658c0759af51b16b0b255865f7c6" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.google.guava" name="failureaccess" version="1.0.1"> <component group="com.google.guava" name="failureaccess" version="1.0.1">
<artifact name="failureaccess-1.0.1.pom"> <artifact name="failureaccess-1.0.1.pom">
<sha256 value="e96042ce78fecba0da2be964522947c87b40a291b5fd3cd672a434924103c4b9" origin="Generated by Gradle"/> <sha256 value="e96042ce78fecba0da2be964522947c87b40a291b5fd3cd672a434924103c4b9" origin="Generated by Gradle"/>