mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-28 17:38:55 +03:00
add diagnose screen and permission warnings in settings
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
bd58cd8a65
commit
c7a72aaf4a
31 changed files with 1533 additions and 237 deletions
|
@ -0,0 +1,146 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 10,
|
||||||
|
"identityHash": "1b2dab0ea495c45c9c9ee6e64ba74039",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "User",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "userId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "baseUrl",
|
||||||
|
"columnName": "baseUrl",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "token",
|
||||||
|
"columnName": "token",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "displayName",
|
||||||
|
"columnName": "displayName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pushConfigurationState",
|
||||||
|
"columnName": "pushConfigurationState",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "capabilities",
|
||||||
|
"columnName": "capabilities",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverVersion",
|
||||||
|
"columnName": "serverVersion",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "clientCertificate",
|
||||||
|
"columnName": "clientCertificate",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "externalSignalingServer",
|
||||||
|
"columnName": "externalSignalingServer",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "current",
|
||||||
|
"columnName": "current",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "scheduledForDeletion",
|
||||||
|
"columnName": "scheduledForDeletion",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "ArbitraryStorage",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "accountIdentifier",
|
||||||
|
"columnName": "accountIdentifier",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "key",
|
||||||
|
"columnName": "key",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "storageObject",
|
||||||
|
"columnName": "object",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "value",
|
||||||
|
"columnName": "value",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"accountIdentifier",
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1b2dab0ea495c45c9c9ee6e64ba74039')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ class MainActivityTest {
|
||||||
displayName = "Test Name",
|
displayName = "Test Name",
|
||||||
pushConfigurationState = null,
|
pushConfigurationState = null,
|
||||||
capabilities = null,
|
capabilities = null,
|
||||||
|
serverVersion = null,
|
||||||
certificateAlias = null,
|
certificateAlias = null,
|
||||||
externalSignalingServer = null
|
externalSignalingServer = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,6 +57,7 @@ class GetFirebasePushTokenWorker(val context: Context, workerParameters: WorkerP
|
||||||
Log.d(TAG, "Fetched firebase push token is: $pushToken")
|
Log.d(TAG, "Fetched firebase push token is: $pushToken")
|
||||||
|
|
||||||
appPreferences.pushToken = pushToken
|
appPreferences.pushToken = pushToken
|
||||||
|
appPreferences.pushTokenLatestFetch = System.currentTimeMillis()
|
||||||
|
|
||||||
val data: Data =
|
val data: Data =
|
||||||
Data.Builder().putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker").build()
|
Data.Builder().putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker").build()
|
||||||
|
|
|
@ -78,6 +78,7 @@ class NCFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
Log.d(TAG, "onNewToken. token = $token")
|
Log.d(TAG, "onNewToken. token = $token")
|
||||||
|
|
||||||
appPreferences.pushToken = token
|
appPreferences.pushToken = token
|
||||||
|
appPreferences.pushTokenLatestGeneration = System.currentTimeMillis()
|
||||||
|
|
||||||
val data: Data =
|
val data: Data =
|
||||||
Data.Builder().putString(PushRegistrationWorker.ORIGIN, "NCFirebaseMessagingService#onNewToken").build()
|
Data.Builder().putString(PushRegistrationWorker.ORIGIN, "NCFirebaseMessagingService#onNewToken").build()
|
||||||
|
|
|
@ -241,6 +241,10 @@
|
||||||
android:name=".settings.SettingsActivity"
|
android:name=".settings.SettingsActivity"
|
||||||
android:theme="@style/AppTheme" />
|
android:theme="@style/AppTheme" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".diagnose.DiagnoseActivity"
|
||||||
|
android:theme="@style/AppTheme" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".conversationinfo.ConversationInfoActivity"
|
android:name=".conversationinfo.ConversationInfoActivity"
|
||||||
android:theme="@style/AppTheme" />
|
android:theme="@style/AppTheme" />
|
||||||
|
|
|
@ -51,7 +51,6 @@ import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||||
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
||||||
import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
||||||
import com.nextcloud.talk.jobs.WebsocketConnectionsWorker
|
import com.nextcloud.talk.jobs.WebsocketConnectionsWorker
|
||||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
|
||||||
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
|
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
|
||||||
import com.nextcloud.talk.models.json.generic.Status
|
import com.nextcloud.talk.models.json.generic.Status
|
||||||
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
|
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
|
||||||
|
@ -250,7 +249,7 @@ class AccountVerificationActivity : BaseActivity() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun storeProfile(displayName: String?, userId: String, capabilities: Capabilities) {
|
private fun storeProfile(displayName: String?, userId: String, capabilitiesOverall: CapabilitiesOverall) {
|
||||||
userManager.storeProfile(
|
userManager.storeProfile(
|
||||||
username,
|
username,
|
||||||
UserManager.UserAttributes(
|
UserManager.UserAttributes(
|
||||||
|
@ -261,7 +260,8 @@ class AccountVerificationActivity : BaseActivity() {
|
||||||
token = token,
|
token = token,
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
pushConfigurationState = null,
|
pushConfigurationState = null,
|
||||||
capabilities = LoganSquare.serialize(capabilities),
|
capabilities = LoganSquare.serialize(capabilitiesOverall.ocs!!.data!!.capabilities),
|
||||||
|
serverVersion = LoganSquare.serialize(capabilitiesOverall.ocs!!.data!!.serverVersion),
|
||||||
certificateAlias = appPreferences.temporaryClientCertAlias,
|
certificateAlias = appPreferences.temporaryClientCertAlias,
|
||||||
externalSignalingServer = null
|
externalSignalingServer = null
|
||||||
)
|
)
|
||||||
|
@ -302,7 +302,7 @@ class AccountVerificationActivity : BaseActivity() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchProfile(credentials: String, capabilities: CapabilitiesOverall) {
|
private fun fetchProfile(credentials: String, capabilitiesOverall: CapabilitiesOverall) {
|
||||||
ncApi.getUserProfile(
|
ncApi.getUserProfile(
|
||||||
credentials,
|
credentials,
|
||||||
ApiUtils.getUrlForUserProfile(baseUrl)
|
ApiUtils.getUrlForUserProfile(baseUrl)
|
||||||
|
@ -325,7 +325,7 @@ class AccountVerificationActivity : BaseActivity() {
|
||||||
storeProfile(
|
storeProfile(
|
||||||
displayName,
|
displayName,
|
||||||
userProfileOverall.ocs!!.data!!.userId!!,
|
userProfileOverall.ocs!!.data!!.userId!!,
|
||||||
capabilities.ocs!!.data!!.capabilities!!
|
capabilitiesOverall
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
|
|
|
@ -41,6 +41,7 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import android.provider.Settings
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -107,6 +108,7 @@ import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
|
||||||
import com.nextcloud.talk.ui.dialog.FilterConversationFragment
|
import com.nextcloud.talk.ui.dialog.FilterConversationFragment
|
||||||
import com.nextcloud.talk.users.UserManager
|
import com.nextcloud.talk.users.UserManager
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
import com.nextcloud.talk.utils.ClosedInterfaceImpl
|
||||||
import com.nextcloud.talk.utils.FileUtils
|
import com.nextcloud.talk.utils.FileUtils
|
||||||
import com.nextcloud.talk.utils.Mimetype
|
import com.nextcloud.talk.utils.Mimetype
|
||||||
import com.nextcloud.talk.utils.ParticipantPermissions
|
import com.nextcloud.talk.utils.ParticipantPermissions
|
||||||
|
@ -126,6 +128,7 @@ import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isServerEOL
|
||||||
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUnifiedSearchAvailable
|
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUnifiedSearchAvailable
|
||||||
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUserStatusAvailable
|
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUserStatusAvailable
|
||||||
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
|
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
|
||||||
|
import com.nextcloud.talk.utils.power.PowerManagerUtils
|
||||||
import com.nextcloud.talk.utils.rx.SearchViewObservable.Companion.observeSearchView
|
import com.nextcloud.talk.utils.rx.SearchViewObservable.Companion.observeSearchView
|
||||||
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
|
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
@ -234,7 +237,8 @@ class ConversationsListActivity :
|
||||||
|
|
||||||
// handle notification permission on API level >= 33
|
// handle notification permission on API level >= 33
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||||
!platformPermissionUtil.isPostNotificationsPermissionGranted()
|
!platformPermissionUtil.isPostNotificationsPermissionGranted() &&
|
||||||
|
ClosedInterfaceImpl().isGooglePlayServicesAvailable
|
||||||
) {
|
) {
|
||||||
requestPermissions(
|
requestPermissions(
|
||||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
|
@ -1269,7 +1273,9 @@ class ConversationsListActivity :
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) {
|
|
||||||
|
when (requestCode) {
|
||||||
|
UploadAndShareFilesWorker.REQUEST_PERMISSION -> {
|
||||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
Log.d(TAG, "upload starting after permissions were granted")
|
Log.d(TAG, "upload starting after permissions were granted")
|
||||||
showSendFilesConfirmDialog()
|
showSendFilesConfirmDialog()
|
||||||
|
@ -1281,6 +1287,43 @@ class ConversationsListActivity :
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
REQUEST_POST_NOTIFICATIONS_PERMISSION -> {
|
||||||
|
// whenever user allowed notifications, also check to ignore battery optimization
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
if (!PowerManagerUtils().isIgnoringBatteryOptimizations() &&
|
||||||
|
ClosedInterfaceImpl().isGooglePlayServicesAvailable
|
||||||
|
) {
|
||||||
|
val dialogText = String.format(
|
||||||
|
context.resources.getString(R.string.nc_ignore_battery_optimization_dialog_text),
|
||||||
|
context.resources.getString(R.string.nc_app_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
val dialogBuilder = MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.nc_ignore_battery_optimization_dialog_title)
|
||||||
|
.setMessage(dialogText)
|
||||||
|
.setPositiveButton(R.string.nc_ok) { _, _ ->
|
||||||
|
startActivity(
|
||||||
|
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.nc_common_dismiss, null)
|
||||||
|
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
|
||||||
|
val dialog = dialogBuilder.show()
|
||||||
|
viewThemeUtils.platform.colorTextButtons(
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Notification permission is denied. Either because user denied it when being asked. " +
|
||||||
|
"Or permission is already denied and android decided to not offer the dialog."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openConversation(textToPaste: String? = "") {
|
private fun openConversation(textToPaste: String? = "") {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import com.nextcloud.talk.data.source.local.converters.CapabilitiesConverter
|
||||||
import com.nextcloud.talk.data.source.local.converters.ExternalSignalingServerConverter
|
import com.nextcloud.talk.data.source.local.converters.ExternalSignalingServerConverter
|
||||||
import com.nextcloud.talk.data.source.local.converters.HashMapHashMapConverter
|
import com.nextcloud.talk.data.source.local.converters.HashMapHashMapConverter
|
||||||
import com.nextcloud.talk.data.source.local.converters.PushConfigurationConverter
|
import com.nextcloud.talk.data.source.local.converters.PushConfigurationConverter
|
||||||
|
import com.nextcloud.talk.data.source.local.converters.ServerVersionConverter
|
||||||
import com.nextcloud.talk.data.source.local.converters.SignalingSettingsConverter
|
import com.nextcloud.talk.data.source.local.converters.SignalingSettingsConverter
|
||||||
import com.nextcloud.talk.data.storage.ArbitraryStoragesDao
|
import com.nextcloud.talk.data.storage.ArbitraryStoragesDao
|
||||||
import com.nextcloud.talk.data.storage.model.ArbitraryStorageEntity
|
import com.nextcloud.talk.data.storage.model.ArbitraryStorageEntity
|
||||||
|
@ -42,15 +43,20 @@ import net.sqlcipher.database.SQLiteDatabase
|
||||||
import net.sqlcipher.database.SQLiteDatabaseHook
|
import net.sqlcipher.database.SQLiteDatabaseHook
|
||||||
import net.sqlcipher.database.SupportFactory
|
import net.sqlcipher.database.SupportFactory
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import androidx.room.AutoMigration
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [UserEntity::class, ArbitraryStorageEntity::class],
|
entities = [UserEntity::class, ArbitraryStorageEntity::class],
|
||||||
version = 9,
|
version = 10,
|
||||||
|
autoMigrations = [
|
||||||
|
AutoMigration(from = 9, to = 10)
|
||||||
|
],
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
)
|
)
|
||||||
@TypeConverters(
|
@TypeConverters(
|
||||||
PushConfigurationConverter::class,
|
PushConfigurationConverter::class,
|
||||||
CapabilitiesConverter::class,
|
CapabilitiesConverter::class,
|
||||||
|
ServerVersionConverter::class,
|
||||||
ExternalSignalingServerConverter::class,
|
ExternalSignalingServerConverter::class,
|
||||||
SignalingSettingsConverter::class,
|
SignalingSettingsConverter::class,
|
||||||
HashMapHashMapConverter::class
|
HashMapHashMapConverter::class
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Mario Danic
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||||
|
* Copyright (C) 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.data.source.local.converters
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import com.bluelinelabs.logansquare.LoganSquare
|
||||||
|
import com.nextcloud.talk.models.json.capabilities.ServerVersion
|
||||||
|
|
||||||
|
class ServerVersionConverter {
|
||||||
|
@TypeConverter
|
||||||
|
fun fromServerVersionToString(serverVersion: ServerVersion?): String {
|
||||||
|
return if (serverVersion == null) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
LoganSquare.serialize(serverVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromStringToServerVersion(value: String): ServerVersion? {
|
||||||
|
return if (value.isBlank()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
return LoganSquare.parse(value, ServerVersion::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,6 +41,7 @@ object UserMapper {
|
||||||
entity.displayName,
|
entity.displayName,
|
||||||
entity.pushConfigurationState,
|
entity.pushConfigurationState,
|
||||||
entity.capabilities,
|
entity.capabilities,
|
||||||
|
entity.serverVersion,
|
||||||
entity.clientCertificate,
|
entity.clientCertificate,
|
||||||
entity.externalSignalingServer,
|
entity.externalSignalingServer,
|
||||||
entity.current,
|
entity.current,
|
||||||
|
@ -59,6 +60,7 @@ object UserMapper {
|
||||||
displayName = model.displayName
|
displayName = model.displayName
|
||||||
pushConfigurationState = model.pushConfigurationState
|
pushConfigurationState = model.pushConfigurationState
|
||||||
capabilities = model.capabilities
|
capabilities = model.capabilities
|
||||||
|
serverVersion = model.serverVersion
|
||||||
clientCertificate = model.clientCertificate
|
clientCertificate = model.clientCertificate
|
||||||
externalSignalingServer = model.externalSignalingServer
|
externalSignalingServer = model.externalSignalingServer
|
||||||
current = model.current
|
current = model.current
|
||||||
|
|
|
@ -22,6 +22,7 @@ package com.nextcloud.talk.data.user.model
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.nextcloud.talk.models.ExternalSignalingServer
|
import com.nextcloud.talk.models.ExternalSignalingServer
|
||||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
||||||
|
import com.nextcloud.talk.models.json.capabilities.ServerVersion
|
||||||
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
@ -37,6 +38,7 @@ data class User(
|
||||||
var displayName: String? = null,
|
var displayName: String? = null,
|
||||||
var pushConfigurationState: PushConfigurationState? = null,
|
var pushConfigurationState: PushConfigurationState? = null,
|
||||||
var capabilities: Capabilities? = null,
|
var capabilities: Capabilities? = null,
|
||||||
|
var serverVersion: ServerVersion? = null,
|
||||||
var clientCertificate: String? = null,
|
var clientCertificate: String? = null,
|
||||||
var externalSignalingServer: ExternalSignalingServer? = null,
|
var externalSignalingServer: ExternalSignalingServer? = null,
|
||||||
var current: Boolean = FALSE,
|
var current: Boolean = FALSE,
|
||||||
|
|
|
@ -28,6 +28,7 @@ import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.nextcloud.talk.models.ExternalSignalingServer
|
import com.nextcloud.talk.models.ExternalSignalingServer
|
||||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
||||||
|
import com.nextcloud.talk.models.json.capabilities.ServerVersion
|
||||||
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.lang.Boolean.FALSE
|
import java.lang.Boolean.FALSE
|
||||||
|
@ -60,6 +61,9 @@ data class UserEntity(
|
||||||
@ColumnInfo(name = "capabilities")
|
@ColumnInfo(name = "capabilities")
|
||||||
var capabilities: Capabilities? = null,
|
var capabilities: Capabilities? = null,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "serverVersion", defaultValue = "")
|
||||||
|
var serverVersion: ServerVersion? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = "clientCertificate")
|
@ColumnInfo(name = "clientCertificate")
|
||||||
var clientCertificate: String? = null,
|
var clientCertificate: String? = null,
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,476 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.diagnose
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Build.MANUFACTURER
|
||||||
|
import android.os.Build.MODEL
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.text.bold
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import autodagger.AutoInjector
|
||||||
|
import com.nextcloud.talk.BuildConfig
|
||||||
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.activities.BaseActivity
|
||||||
|
import com.nextcloud.talk.api.NcApi
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
|
||||||
|
import com.nextcloud.talk.databinding.ActivityDiagnoseBinding
|
||||||
|
import com.nextcloud.talk.users.UserManager
|
||||||
|
import com.nextcloud.talk.utils.ClosedInterfaceImpl
|
||||||
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
|
import com.nextcloud.talk.utils.NotificationUtils
|
||||||
|
import com.nextcloud.talk.utils.PushUtils.Companion.LATEST_PUSH_REGISTRATION_AT_PUSH_PROXY
|
||||||
|
import com.nextcloud.talk.utils.PushUtils.Companion.LATEST_PUSH_REGISTRATION_AT_SERVER
|
||||||
|
import com.nextcloud.talk.utils.UserIdUtils
|
||||||
|
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||||
|
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
|
||||||
|
import com.nextcloud.talk.utils.power.PowerManagerUtils
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
|
class DiagnoseActivity : BaseActivity() {
|
||||||
|
private lateinit var binding: ActivityDiagnoseBinding
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var arbitraryStorageManager: ArbitraryStorageManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var ncApi: NcApi
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var userManager: UserManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var currentUserProvider: CurrentUserProviderNew
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var platformPermissionUtil: PlatformPermissionUtil
|
||||||
|
|
||||||
|
private var isGooglePlayServicesAvailable: Boolean = false
|
||||||
|
|
||||||
|
private val markdownText = SpannableStringBuilder()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||||
|
|
||||||
|
binding = ActivityDiagnoseBinding.inflate(layoutInflater)
|
||||||
|
setupActionBar()
|
||||||
|
setContentView(binding.root)
|
||||||
|
setupSystemColors()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
supportActionBar?.show()
|
||||||
|
|
||||||
|
isGooglePlayServicesAvailable = ClosedInterfaceImpl().isGooglePlayServicesAvailable
|
||||||
|
|
||||||
|
markdownText.clear()
|
||||||
|
setupMetaValues()
|
||||||
|
setupPhoneValues()
|
||||||
|
setupAppValues()
|
||||||
|
setupAccountValues()
|
||||||
|
|
||||||
|
createLayoutFromMarkdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupActionBar() {
|
||||||
|
setSupportActionBar(binding.settingsToolbar)
|
||||||
|
binding.settingsToolbar.setNavigationOnClickListener {
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent, null)))
|
||||||
|
supportActionBar?.title = context.getString(R.string.nc_settings_diagnose_title)
|
||||||
|
viewThemeUtils.material.themeToolbar(binding.settingsToolbar)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.menu_diagnose, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||||
|
super.onPrepareOptionsMenu(menu)
|
||||||
|
menu.findItem(R.id.create_issue).isVisible =
|
||||||
|
applicationContext.packageName.equals(ORIGINAL_NEXTCLOUD_TALK_APPLICATION_ID)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
return when (item.itemId) {
|
||||||
|
android.R.id.home -> {
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.copy -> {
|
||||||
|
copyToClipboard(markdownText.toString())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.share -> {
|
||||||
|
shareToOtherApps(markdownText.toString())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.send_mail -> {
|
||||||
|
composeEmail(markdownText.toString())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.create_issue -> {
|
||||||
|
createGithubIssue(markdownText.toString())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareToOtherApps(message: String) {
|
||||||
|
val sendIntent: Intent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
putExtra(Intent.EXTRA_TEXT, message)
|
||||||
|
type = "text/plain"
|
||||||
|
}
|
||||||
|
val shareIntent = Intent.createChooser(sendIntent, getString(R.string.share))
|
||||||
|
startActivity(shareIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun composeEmail(text: String) {
|
||||||
|
val intent = Intent(Intent.ACTION_SENDTO).apply {
|
||||||
|
val appName = context.resources.getString(R.string.nc_app_product_name)
|
||||||
|
|
||||||
|
data = Uri.parse("mailto:")
|
||||||
|
putExtra(Intent.EXTRA_SUBJECT, appName)
|
||||||
|
putExtra(Intent.EXTRA_TEXT, text)
|
||||||
|
}
|
||||||
|
if (intent.resolveActivity(packageManager) != null) {
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createGithubIssue(text: String) {
|
||||||
|
copyToClipboard(text)
|
||||||
|
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(resources!!.getString(R.string.nc_talk_android_issues_url))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyToClipboard(text: String) {
|
||||||
|
val clipboardManager =
|
||||||
|
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clipData = ClipData.newPlainText(
|
||||||
|
resources?.getString(R.string.nc_app_product_name),
|
||||||
|
text
|
||||||
|
)
|
||||||
|
clipboardManager.setPrimaryClip(clipData)
|
||||||
|
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.resources.getString(R.string.nc_common_copy_success),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupMetaValues() {
|
||||||
|
addHeadline(context.resources.getString(R.string.nc_diagnose_meta_category_title))
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_meta_system_report_date))
|
||||||
|
addValue(DisplayUtils.unixTimeToHumanReadable(System.currentTimeMillis()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPhoneValues() {
|
||||||
|
addHeadline(context.resources.getString(R.string.nc_diagnose_phone_category_title))
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_device_name_title))
|
||||||
|
addValue(getDeviceName())
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_android_version_title))
|
||||||
|
addValue(Build.VERSION.SDK_INT.toString())
|
||||||
|
|
||||||
|
if (isGooglePlayServicesAvailable) {
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_gplay_available_title))
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_gplay_available_yes))
|
||||||
|
} else {
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_gplay_available_title))
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_gplay_available_no))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun setupAppValues() {
|
||||||
|
addHeadline(context.resources.getString(R.string.nc_diagnose_app_category_title))
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_app_name_title))
|
||||||
|
addValue(context.resources.getString(R.string.nc_app_product_name))
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_app_version_title))
|
||||||
|
addValue(String.format("v" + BuildConfig.VERSION_NAME))
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_build_flavor))
|
||||||
|
addValue(BuildConfig.FLAVOR)
|
||||||
|
|
||||||
|
if (isGooglePlayServicesAvailable) {
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_battery_optimization_title))
|
||||||
|
|
||||||
|
if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_battery_optimization_ignored))
|
||||||
|
} else {
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_battery_optimization_not_ignored))
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle notification permission on API level >= 33
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_notification_permission))
|
||||||
|
if (platformPermissionUtil.isPostNotificationsPermissionGranted()) {
|
||||||
|
addValue(context.resources.getString(R.string.nc_settings_notifications_granted))
|
||||||
|
} else {
|
||||||
|
addValue(context.resources.getString(R.string.nc_settings_notifications_declined))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_notification_calls_channel_permission))
|
||||||
|
addValue(NotificationUtils.isCallsNotificationChannelEnabled(this).toString())
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_notification_messages_channel_permission))
|
||||||
|
addValue(NotificationUtils.isMessagesNotificationChannelEnabled(this).toString())
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_title))
|
||||||
|
if (appPreferences.pushToken.isNullOrEmpty()) {
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_firebase_push_token_missing))
|
||||||
|
} else {
|
||||||
|
addValue("${appPreferences.pushToken.substring(0, 5)}...")
|
||||||
|
}
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_generated))
|
||||||
|
if (appPreferences.pushTokenLatestGeneration != null && appPreferences.pushTokenLatestGeneration != 0L) {
|
||||||
|
addValue(
|
||||||
|
DisplayUtils.unixTimeToHumanReadable(
|
||||||
|
appPreferences
|
||||||
|
.pushTokenLatestGeneration
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
addValue(context.resources.getString(R.string.nc_common_unknown))
|
||||||
|
}
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_firebase_push_token_latest_fetch))
|
||||||
|
if (appPreferences.pushTokenLatestFetch != null && appPreferences.pushTokenLatestFetch != 0L) {
|
||||||
|
addValue(DisplayUtils.unixTimeToHumanReadable(appPreferences.pushTokenLatestFetch))
|
||||||
|
} else {
|
||||||
|
addValue(context.resources.getString(R.string.nc_common_unknown))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_app_users_amount))
|
||||||
|
addValue(userManager.users.blockingGet().size.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAccountValues() {
|
||||||
|
addHeadline(context.resources.getString(R.string.nc_diagnose_account_category_title))
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_account_server))
|
||||||
|
addValue(userManager.currentUser.blockingGet().baseUrl!!)
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_account_user_name))
|
||||||
|
addValue(userManager.currentUser.blockingGet().displayName!!)
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_account_user_status_enabled))
|
||||||
|
addValue(
|
||||||
|
translateBoolean(
|
||||||
|
(userManager.currentUser.blockingGet().capabilities?.userStatusCapability?.enabled)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_account_server_notification_app))
|
||||||
|
addValue(
|
||||||
|
translateBoolean(
|
||||||
|
userManager.currentUser.blockingGet().capabilities?.notificationsCapability?.features?.isNotEmpty()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isGooglePlayServicesAvailable) {
|
||||||
|
setupPushRegistrationDiagnose()
|
||||||
|
}
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_server_version))
|
||||||
|
addValue(userManager.currentUser.blockingGet().serverVersion?.versionString!!)
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_server_talk_version))
|
||||||
|
addValue(userManager.currentUser.blockingGet().capabilities?.spreedCapability?.version!!)
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_signaling_mode_title))
|
||||||
|
|
||||||
|
if (userManager.currentUser.blockingGet().externalSignalingServer?.externalSignalingServer?.isNotEmpty()
|
||||||
|
== true
|
||||||
|
) {
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_signaling_mode_extern))
|
||||||
|
} else {
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_signaling_mode_intern))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPushRegistrationDiagnose() {
|
||||||
|
val accountId = UserIdUtils.getIdForUser(userManager.currentUser.blockingGet())
|
||||||
|
|
||||||
|
val latestPushRegistrationAtServer = arbitraryStorageManager.getStorageSetting(
|
||||||
|
accountId,
|
||||||
|
LATEST_PUSH_REGISTRATION_AT_SERVER,
|
||||||
|
""
|
||||||
|
).blockingGet()?.value
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_latest_push_registration_at_server))
|
||||||
|
if (latestPushRegistrationAtServer.isNullOrEmpty()) {
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_latest_push_registration_at_server_fail))
|
||||||
|
} else {
|
||||||
|
addValue(DisplayUtils.unixTimeToHumanReadable(latestPushRegistrationAtServer.toLong()))
|
||||||
|
}
|
||||||
|
|
||||||
|
val latestPushRegistrationAtPushProxy = arbitraryStorageManager.getStorageSetting(
|
||||||
|
accountId,
|
||||||
|
LATEST_PUSH_REGISTRATION_AT_PUSH_PROXY,
|
||||||
|
""
|
||||||
|
).blockingGet()?.value
|
||||||
|
|
||||||
|
addKey(context.resources.getString(R.string.nc_diagnose_latest_push_registration_at_push_proxy))
|
||||||
|
if (latestPushRegistrationAtPushProxy.isNullOrEmpty()) {
|
||||||
|
addValue(context.resources.getString(R.string.nc_diagnose_latest_push_registration_at_push_proxy_fail))
|
||||||
|
} else {
|
||||||
|
addValue(DisplayUtils.unixTimeToHumanReadable(latestPushRegistrationAtPushProxy.toLong()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDeviceName(): String =
|
||||||
|
if (MODEL.startsWith(MANUFACTURER, ignoreCase = true)) {
|
||||||
|
MODEL
|
||||||
|
} else {
|
||||||
|
"$MANUFACTURER $MODEL"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBoolean(answer: Boolean?): String {
|
||||||
|
return when (answer) {
|
||||||
|
null -> context.resources.getString(R.string.nc_common_unknown)
|
||||||
|
true -> context.resources.getString(R.string.nc_yes)
|
||||||
|
else -> context.resources.getString(R.string.nc_no)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
private fun createLayoutFromMarkdown() {
|
||||||
|
val standardMargin = 16
|
||||||
|
val halfMargin = 8
|
||||||
|
val standardPadding = 16
|
||||||
|
|
||||||
|
binding.diagnoseContentWrapper.removeAllViews()
|
||||||
|
|
||||||
|
markdownText.lines().forEach {
|
||||||
|
if (it.startsWith(MARKDOWN_HEADLINE)) {
|
||||||
|
val headline = TextView(context, null, 0)
|
||||||
|
headline.textSize = 2.0f
|
||||||
|
headline.setTextSize(
|
||||||
|
TypedValue.COMPLEX_UNIT_PX,
|
||||||
|
context.resources.getDimension(R.dimen.headline_text_size)
|
||||||
|
)
|
||||||
|
headline.setTypeface(null, Typeface.BOLD)
|
||||||
|
headline.text = it.removeRange(0, 4)
|
||||||
|
|
||||||
|
binding.diagnoseContentWrapper.addView(headline)
|
||||||
|
|
||||||
|
headline.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
setMargins(0, standardMargin, 0, standardMargin)
|
||||||
|
}
|
||||||
|
headline.setPadding(0, standardPadding, 0, standardPadding)
|
||||||
|
|
||||||
|
viewThemeUtils.platform.colorTextView(headline)
|
||||||
|
} else if (it.startsWith(MARKDOWN_BOLD)) {
|
||||||
|
val key = TextView(context, null, 0)
|
||||||
|
key.setTextColor(resources.getColor(R.color.high_emphasis_text, null))
|
||||||
|
key.setTypeface(null, Typeface.BOLD)
|
||||||
|
key.text = it.replace(MARKDOWN_BOLD, "")
|
||||||
|
|
||||||
|
binding.diagnoseContentWrapper.addView(key)
|
||||||
|
|
||||||
|
key.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
setMargins(0, 0, 0, halfMargin)
|
||||||
|
}
|
||||||
|
} else if (it.isNotEmpty()) {
|
||||||
|
val value = TextView(context, null, 0)
|
||||||
|
value.setTextColor(resources.getColor(R.color.high_emphasis_text, null))
|
||||||
|
value.text = it
|
||||||
|
|
||||||
|
binding.diagnoseContentWrapper.addView(value)
|
||||||
|
|
||||||
|
value.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
setMargins(0, 0, 0, standardMargin)
|
||||||
|
}
|
||||||
|
value.setPadding(0, 0, 0, standardPadding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addHeadline(text: String) {
|
||||||
|
markdownText.append("$MARKDOWN_HEADLINE $text")
|
||||||
|
markdownText.append("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addKey(text: String) {
|
||||||
|
markdownText.bold { append("$MARKDOWN_BOLD$text$MARKDOWN_BOLD") }
|
||||||
|
markdownText.append("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addValue(text: String) {
|
||||||
|
markdownText.append(text)
|
||||||
|
markdownText.append("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG = DiagnoseActivity::class.java.simpleName
|
||||||
|
private const val MARKDOWN_HEADLINE = "###"
|
||||||
|
private const val MARKDOWN_BOLD = "**"
|
||||||
|
private const val ORIGINAL_NEXTCLOUD_TALK_APPLICATION_ID = "com.nextcloud.talk2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,6 +80,7 @@ public class CapabilitiesWorker extends Worker {
|
||||||
capabilitiesOverall.getOcs().getData().getCapabilities() != null) {
|
capabilitiesOverall.getOcs().getData().getCapabilities() != null) {
|
||||||
|
|
||||||
user.setCapabilities(capabilitiesOverall.getOcs().getData().getCapabilities());
|
user.setCapabilities(capabilitiesOverall.getOcs().getData().getCapabilities());
|
||||||
|
user.setServerVersion(capabilitiesOverall.getOcs().getData().getServerVersion());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int rowsCount = userManager.updateOrCreateUser(user).blockingGet();
|
int rowsCount = userManager.updateOrCreateUser(user).blockingGet();
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
*
|
*
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
* @author Tim Krüger
|
* @author Tim Krüger
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||||
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||||
*
|
*
|
||||||
|
|
|
@ -29,9 +29,11 @@ import kotlinx.parcelize.Parcelize
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@JsonObject
|
@JsonObject
|
||||||
data class CapabilitiesList(
|
data class CapabilitiesList(
|
||||||
|
@JsonField(name = ["version"])
|
||||||
|
var serverVersion: ServerVersion?,
|
||||||
@JsonField(name = ["capabilities"])
|
@JsonField(name = ["capabilities"])
|
||||||
var capabilities: Capabilities?
|
var capabilities: Capabilities?
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
constructor() : this(null)
|
constructor() : this(null, null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.models.json.capabilities
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@JsonObject
|
||||||
|
data class ServerVersion(
|
||||||
|
@JsonField(name = ["major"])
|
||||||
|
var major: Int = 0,
|
||||||
|
@JsonField(name = ["minor"])
|
||||||
|
var minor: Int = 0,
|
||||||
|
@JsonField(name = ["micro"])
|
||||||
|
var micro: Int = 0,
|
||||||
|
@JsonField(name = ["string"])
|
||||||
|
var versionString: String? = null
|
||||||
|
) : Parcelable {
|
||||||
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
|
constructor() : this(0, 0, 0, null)
|
||||||
|
}
|
|
@ -45,8 +45,10 @@ data class SpreedCapability(
|
||||||
@Contextual
|
@Contextual
|
||||||
Any
|
Any
|
||||||
>
|
>
|
||||||
>?
|
>?,
|
||||||
|
@JsonField(name = ["version"])
|
||||||
|
var version: String
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||||
constructor() : this(null, null)
|
constructor() : this(null, null, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
*
|
*
|
||||||
* @author Andy Scherzinger
|
* @author Andy Scherzinger
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
|
* @author Marcel Hibbe
|
||||||
* @author Tim Krüger
|
* @author Tim Krüger
|
||||||
* @author Ezhil Shanmugham
|
* @author Ezhil Shanmugham
|
||||||
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
|
* Copyright (C) 2021 Tim Krüger <t@timkrueger.me>
|
||||||
* Copyright (C) 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
|
* Copyright (C) 2021-2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
|
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
|
||||||
|
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||||
* Copyright (C) 2023 Ezhil Shanmugham <ezhil56x.contact@gmail.com>
|
* Copyright (C) 2023 Ezhil Shanmugham <ezhil56x.contact@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
|
||||||
|
@ -25,6 +27,7 @@
|
||||||
*/
|
*/
|
||||||
package com.nextcloud.talk.settings
|
package com.nextcloud.talk.settings
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
@ -56,6 +59,7 @@ import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
|
@ -72,8 +76,10 @@ import com.nextcloud.talk.activities.MainActivity
|
||||||
import com.nextcloud.talk.api.NcApi
|
import com.nextcloud.talk.api.NcApi
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme
|
||||||
|
import com.nextcloud.talk.conversationlist.ConversationsListActivity
|
||||||
import com.nextcloud.talk.data.user.model.User
|
import com.nextcloud.talk.data.user.model.User
|
||||||
import com.nextcloud.talk.databinding.ActivitySettingsBinding
|
import com.nextcloud.talk.databinding.ActivitySettingsBinding
|
||||||
|
import com.nextcloud.talk.diagnose.DiagnoseActivity
|
||||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||||
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
||||||
import com.nextcloud.talk.jobs.ContactAddressBookWorker
|
import com.nextcloud.talk.jobs.ContactAddressBookWorker
|
||||||
|
@ -84,6 +90,7 @@ import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
|
||||||
import com.nextcloud.talk.profile.ProfileActivity
|
import com.nextcloud.talk.profile.ProfileActivity
|
||||||
import com.nextcloud.talk.users.UserManager
|
import com.nextcloud.talk.users.UserManager
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
import com.nextcloud.talk.utils.ClosedInterfaceImpl
|
||||||
import com.nextcloud.talk.utils.DisplayUtils
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
import com.nextcloud.talk.utils.LoggingUtils.sendMailWithAttachment
|
import com.nextcloud.talk.utils.LoggingUtils.sendMailWithAttachment
|
||||||
import com.nextcloud.talk.utils.NotificationUtils
|
import com.nextcloud.talk.utils.NotificationUtils
|
||||||
|
@ -92,6 +99,8 @@ import com.nextcloud.talk.utils.NotificationUtils.getMessageRingtoneUri
|
||||||
import com.nextcloud.talk.utils.SecurityUtils
|
import com.nextcloud.talk.utils.SecurityUtils
|
||||||
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
|
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
|
||||||
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||||
|
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
|
||||||
|
import com.nextcloud.talk.utils.power.PowerManagerUtils
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferencesImpl
|
import com.nextcloud.talk.utils.preferences.AppPreferencesImpl
|
||||||
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
|
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
|
||||||
import io.reactivex.Observer
|
import io.reactivex.Observer
|
||||||
|
@ -124,6 +133,9 @@ class SettingsActivity : BaseActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var currentUserProvider: CurrentUserProviderNew
|
lateinit var currentUserProvider: CurrentUserProviderNew
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var platformPermissionUtil: PlatformPermissionUtil
|
||||||
|
|
||||||
private var currentUser: User? = null
|
private var currentUser: User? = null
|
||||||
private var credentials: String? = null
|
private var credentials: String? = null
|
||||||
private lateinit var proxyTypeFlow: Flow<String>
|
private lateinit var proxyTypeFlow: Flow<String>
|
||||||
|
@ -163,12 +175,11 @@ class SettingsActivity : BaseActivity() {
|
||||||
resources!!.getString(R.string.nc_app_product_name)
|
resources!!.getString(R.string.nc_app_product_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
setupDiagnose()
|
||||||
setupPrivacyUrl()
|
setupPrivacyUrl()
|
||||||
setupSourceCodeUrl()
|
setupSourceCodeUrl()
|
||||||
binding.settingsVersionSummary.text = String.format("v" + BuildConfig.VERSION_NAME)
|
binding.settingsVersionSummary.text = String.format("v" + BuildConfig.VERSION_NAME)
|
||||||
|
|
||||||
setupSoundSettings()
|
|
||||||
|
|
||||||
setupPhoneBookIntegration()
|
setupPhoneBookIntegration()
|
||||||
|
|
||||||
setupClientCertView()
|
setupClientCertView()
|
||||||
|
@ -193,18 +204,7 @@ class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
setupCheckables()
|
setupCheckables()
|
||||||
setupScreenLockSetting()
|
setupScreenLockSetting()
|
||||||
|
setupNotificationSettings()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
binding.settingsNotificationsTitle.text = resources!!.getString(
|
|
||||||
R.string.nc_settings_notification_sounds_post_oreo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val callRingtoneUri = getCallRingtoneUri(context, (appPreferences))
|
|
||||||
binding.callsRingtone.text = getRingtoneName(context, callRingtoneUri)
|
|
||||||
val messageRingtoneUri = getMessageRingtoneUri(context, (appPreferences))
|
|
||||||
binding.messagesRingtone.text = getRingtoneName(context, messageRingtoneUri)
|
|
||||||
|
|
||||||
setupProxyTypeSettings()
|
setupProxyTypeSettings()
|
||||||
setupProxyCredentialSettings()
|
setupProxyCredentialSettings()
|
||||||
registerChangeListeners()
|
registerChangeListeners()
|
||||||
|
@ -273,7 +273,112 @@ class SettingsActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupSoundSettings() {
|
private fun setupNotificationSettings() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
binding.settingsNotificationsTitle.text = resources!!.getString(
|
||||||
|
R.string.nc_settings_notification_sounds_post_oreo
|
||||||
|
)
|
||||||
|
}
|
||||||
|
setupNotificationSoundsSettings()
|
||||||
|
setupNotificationPermissionSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
private fun setupNotificationPermissionSettings() {
|
||||||
|
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
|
||||||
|
binding.settingsGplayOnlyWrapper.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
setTroubleshootingClickListenersIfNecessary()
|
||||||
|
|
||||||
|
if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
|
||||||
|
binding.batteryOptimizationIgnored.text =
|
||||||
|
resources!!.getString(R.string.nc_diagnose_battery_optimization_ignored)
|
||||||
|
binding.batteryOptimizationIgnored.setTextColor(
|
||||||
|
resources.getColor(R.color.high_emphasis_text, null)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
binding.batteryOptimizationIgnored.text =
|
||||||
|
resources!!.getString(R.string.nc_diagnose_battery_optimization_not_ignored)
|
||||||
|
binding.batteryOptimizationIgnored.setTextColor(resources.getColor(R.color.nc_darkRed, null))
|
||||||
|
|
||||||
|
binding.settingsBatteryOptimizationWrapper.setOnClickListener {
|
||||||
|
val dialogText = String.format(
|
||||||
|
context.resources.getString(R.string.nc_ignore_battery_optimization_dialog_text),
|
||||||
|
context.resources.getString(R.string.nc_app_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
val dialogBuilder = MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.nc_ignore_battery_optimization_dialog_title)
|
||||||
|
.setMessage(dialogText)
|
||||||
|
.setPositiveButton(R.string.nc_ok) { _, _ ->
|
||||||
|
startActivity(
|
||||||
|
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.nc_common_dismiss, null)
|
||||||
|
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
|
||||||
|
val dialog = dialogBuilder.show()
|
||||||
|
viewThemeUtils.platform.colorTextButtons(
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle notification permission on API level >= 33
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (platformPermissionUtil.isPostNotificationsPermissionGranted()) {
|
||||||
|
binding.ncDiagnoseNotificationPermissionSubtitle.text =
|
||||||
|
resources.getString(R.string.nc_settings_notifications_granted)
|
||||||
|
binding.ncDiagnoseNotificationPermissionSubtitle.setTextColor(
|
||||||
|
resources.getColor(R.color.high_emphasis_text, null)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
binding.ncDiagnoseNotificationPermissionSubtitle.text =
|
||||||
|
resources.getString(R.string.nc_settings_notifications_declined)
|
||||||
|
binding.ncDiagnoseNotificationPermissionSubtitle.setTextColor(
|
||||||
|
resources.getColor(R.color.nc_darkRed, null)
|
||||||
|
)
|
||||||
|
binding.settingsNotificationsPermissionWrapper.setOnClickListener {
|
||||||
|
requestPermissions(
|
||||||
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
|
ConversationsListActivity.REQUEST_POST_NOTIFICATIONS_PERMISSION
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.settingsNotificationsPermissionWrapper.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.settingsGplayOnlyWrapper.visibility = View.GONE
|
||||||
|
binding.settingsGplayNotAvailable.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupNotificationSoundsSettings() {
|
||||||
|
if (NotificationUtils.isCallsNotificationChannelEnabled(this)) {
|
||||||
|
val callRingtoneUri = getCallRingtoneUri(context, (appPreferences))
|
||||||
|
|
||||||
|
binding.callsRingtone.setTextColor(resources.getColor(R.color.high_emphasis_text, null))
|
||||||
|
binding.callsRingtone.text = getRingtoneName(context, callRingtoneUri)
|
||||||
|
} else {
|
||||||
|
binding.callsRingtone.setTextColor(
|
||||||
|
ResourcesCompat.getColor(context.resources, R.color.nc_darkRed, null)
|
||||||
|
)
|
||||||
|
binding.callsRingtone.text = resources!!.getString(R.string.nc_common_disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NotificationUtils.isMessagesNotificationChannelEnabled(this)) {
|
||||||
|
val messageRingtoneUri = getMessageRingtoneUri(context, (appPreferences))
|
||||||
|
binding.messagesRingtone.setTextColor(resources.getColor(R.color.high_emphasis_text, null))
|
||||||
|
binding.messagesRingtone.text = getRingtoneName(context, messageRingtoneUri)
|
||||||
|
} else {
|
||||||
|
binding.messagesRingtone.setTextColor(
|
||||||
|
ResourcesCompat.getColor(context.resources, R.color.nc_darkRed, null)
|
||||||
|
)
|
||||||
|
binding.messagesRingtone.text = resources!!.getString(R.string.nc_common_disabled)
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
binding.settingsCallSound.setOnClickListener {
|
binding.settingsCallSound.setOnClickListener {
|
||||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||||
|
@ -295,7 +400,53 @@ class SettingsActivity : BaseActivity() {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "setupSoundSettings currently not supported for versions < Build.VERSION_CODES.O")
|
Log.w(TAG, "setupSoundSettings currently not supported for versions < Build.VERSION_CODES.O")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTroubleshootingClickListenersIfNecessary() {
|
||||||
|
fun click() {
|
||||||
|
val dialogBuilder = MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.nc_notifications_troubleshooting_dialog_title)
|
||||||
|
.setMessage(R.string.nc_notifications_troubleshooting_dialog_text)
|
||||||
|
.setNegativeButton(R.string.nc_diagnose_dialog_open_checklist) { _, _ ->
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(resources.getString(R.string.notification_checklist_url))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setPositiveButton(R.string.nc_diagnose_dialog_open_dontkillmyapp_website) { _, _ ->
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse(resources.getString(R.string.dontkillmyapp_url))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.setNeutralButton(R.string.nc_diagnose_dialog_open_diagnose) { _, _ ->
|
||||||
|
val intent = Intent(context, DiagnoseActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
|
||||||
|
val dialog = dialogBuilder.show()
|
||||||
|
viewThemeUtils.platform.colorTextButtons(
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (platformPermissionUtil.isPostNotificationsPermissionGranted() &&
|
||||||
|
PowerManagerUtils().isIgnoringBatteryOptimizations()
|
||||||
|
) {
|
||||||
|
binding.settingsNotificationsPermissionWrapper.setOnClickListener { click() }
|
||||||
|
binding.settingsBatteryOptimizationWrapper.setOnClickListener { click() }
|
||||||
|
}
|
||||||
|
} else if (PowerManagerUtils().isIgnoringBatteryOptimizations()) {
|
||||||
|
binding.settingsBatteryOptimizationWrapper.setOnClickListener { click() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,6 +465,13 @@ class SettingsActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupDiagnose() {
|
||||||
|
binding.diagnoseWrapper.setOnClickListener {
|
||||||
|
val intent = Intent(context, DiagnoseActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupPrivacyUrl() {
|
private fun setupPrivacyUrl() {
|
||||||
if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_privacy_url))) {
|
if (!TextUtils.isEmpty(resources!!.getString(R.string.nc_privacy_url))) {
|
||||||
binding.settingsPrivacy.setOnClickListener {
|
binding.settingsPrivacy.setOnClickListener {
|
||||||
|
@ -490,6 +648,7 @@ class SettingsActivity : BaseActivity() {
|
||||||
).show()
|
).show()
|
||||||
restartApp()
|
restartApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
|
@ -915,10 +1074,10 @@ class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
if (requestCode == ContactAddressBookWorker.REQUEST_PERMISSION &&
|
|
||||||
grantResults.isNotEmpty() &&
|
when (requestCode) {
|
||||||
grantResults[0] == PackageManager.PERMISSION_GRANTED
|
ContactAddressBookWorker.REQUEST_PERMISSION -> {
|
||||||
) {
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
WorkManager
|
WorkManager
|
||||||
.getInstance(this)
|
.getInstance(this)
|
||||||
.enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java).build())
|
.enqueue(OneTimeWorkRequest.Builder(ContactAddressBookWorker::class.java).build())
|
||||||
|
@ -934,6 +1093,23 @@ class SettingsActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConversationsListActivity.REQUEST_POST_NOTIFICATIONS_PERMISSION -> {
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_DENIED) {
|
||||||
|
Snackbar.make(
|
||||||
|
binding.root,
|
||||||
|
context.resources.getString(R.string.nc_settings_notifications_declined_hint),
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Notification permission is denied. Either because user denied it when being asked. " +
|
||||||
|
"Or permission is already denied and android decided to not offer the dialog."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun observeScreenLock() {
|
private fun observeScreenLock() {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
var state = appPreferences.isScreenLocked
|
var state = appPreferences.isScreenLocked
|
||||||
|
@ -1140,6 +1316,7 @@ class SettingsActivity : BaseActivity() {
|
||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
textInputLayout.helperText = context.resources.getString(
|
textInputLayout.helperText = context.resources.getString(
|
||||||
R.string.nc_settings_phone_book_integration_phone_number_dialog_invalid
|
R.string.nc_settings_phone_book_integration_phone_number_dialog_invalid
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.nextcloud.talk.data.user.UsersRepository
|
||||||
import com.nextcloud.talk.data.user.model.User
|
import com.nextcloud.talk.data.user.model.User
|
||||||
import com.nextcloud.talk.models.ExternalSignalingServer
|
import com.nextcloud.talk.models.ExternalSignalingServer
|
||||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
||||||
|
import com.nextcloud.talk.models.json.capabilities.ServerVersion
|
||||||
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||||
import io.reactivex.Maybe
|
import io.reactivex.Maybe
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
@ -194,6 +195,10 @@ class UserManager internal constructor(private val userRepository: UsersReposito
|
||||||
user.capabilities = LoganSquare
|
user.capabilities = LoganSquare
|
||||||
.parse(userAttributes.capabilities, Capabilities::class.java)
|
.parse(userAttributes.capabilities, Capabilities::class.java)
|
||||||
}
|
}
|
||||||
|
if (userAttributes.serverVersion != null) {
|
||||||
|
user.serverVersion = LoganSquare
|
||||||
|
.parse(userAttributes.serverVersion, ServerVersion::class.java)
|
||||||
|
}
|
||||||
user.clientCertificate = userAttributes.certificateAlias
|
user.clientCertificate = userAttributes.certificateAlias
|
||||||
if (userAttributes.externalSignalingServer != null) {
|
if (userAttributes.externalSignalingServer != null) {
|
||||||
user.externalSignalingServer = LoganSquare
|
user.externalSignalingServer = LoganSquare
|
||||||
|
@ -220,6 +225,9 @@ class UserManager internal constructor(private val userRepository: UsersReposito
|
||||||
if (!TextUtils.isEmpty(userAttributes.capabilities)) {
|
if (!TextUtils.isEmpty(userAttributes.capabilities)) {
|
||||||
user.capabilities = LoganSquare.parse(userAttributes.capabilities, Capabilities::class.java)
|
user.capabilities = LoganSquare.parse(userAttributes.capabilities, Capabilities::class.java)
|
||||||
}
|
}
|
||||||
|
if (!TextUtils.isEmpty(userAttributes.serverVersion)) {
|
||||||
|
user.serverVersion = LoganSquare.parse(userAttributes.serverVersion, ServerVersion::class.java)
|
||||||
|
}
|
||||||
if (!TextUtils.isEmpty(userAttributes.certificateAlias)) {
|
if (!TextUtils.isEmpty(userAttributes.certificateAlias)) {
|
||||||
user.clientCertificate = userAttributes.certificateAlias
|
user.clientCertificate = userAttributes.certificateAlias
|
||||||
}
|
}
|
||||||
|
@ -248,6 +256,7 @@ class UserManager internal constructor(private val userRepository: UsersReposito
|
||||||
val displayName: String?,
|
val displayName: String?,
|
||||||
val pushConfigurationState: String?,
|
val pushConfigurationState: String?,
|
||||||
val capabilities: String?,
|
val capabilities: String?,
|
||||||
|
val serverVersion: String?,
|
||||||
val certificateAlias: String?,
|
val certificateAlias: String?,
|
||||||
val externalSignalingServer: String?
|
val externalSignalingServer: String?
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,6 +34,7 @@ import android.os.Build
|
||||||
import android.service.notification.StatusBarNotification
|
import android.service.notification.StatusBarNotification
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import coil.executeBlocking
|
import coil.executeBlocking
|
||||||
import coil.imageLoader
|
import coil.imageLoader
|
||||||
|
@ -275,6 +276,30 @@ object NotificationUtils {
|
||||||
return isVisible
|
return isVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isCallsNotificationChannelEnabled(context: Context): Boolean {
|
||||||
|
val channel = getNotificationChannel(context, NotificationChannels.NOTIFICATION_CHANNEL_CALLS_V4.name)
|
||||||
|
if (channel != null) {
|
||||||
|
return isNotificationChannelEnabled(context, channel)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isMessagesNotificationChannelEnabled(context: Context): Boolean {
|
||||||
|
val channel = getNotificationChannel(context, NotificationChannels.NOTIFICATION_CHANNEL_MESSAGES_V4.name)
|
||||||
|
if (channel != null) {
|
||||||
|
return isNotificationChannelEnabled(context, channel)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isNotificationChannelEnabled(context: Context, channel: NotificationChannel): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
channel.importance != NotificationManager.IMPORTANCE_NONE
|
||||||
|
} else {
|
||||||
|
NotificationManagerCompat.from(context).areNotificationsEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getRingtoneUri(
|
private fun getRingtoneUri(
|
||||||
context: Context,
|
context: Context,
|
||||||
ringtonePreferencesString: String?,
|
ringtonePreferencesString: String?,
|
||||||
|
|
|
@ -31,6 +31,7 @@ import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.api.NcApi
|
import com.nextcloud.talk.api.NcApi
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||||
|
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
|
||||||
import com.nextcloud.talk.data.user.model.User
|
import com.nextcloud.talk.data.user.model.User
|
||||||
import com.nextcloud.talk.events.EventStatus
|
import com.nextcloud.talk.events.EventStatus
|
||||||
import com.nextcloud.talk.models.SignatureVerification
|
import com.nextcloud.talk.models.SignatureVerification
|
||||||
|
@ -73,6 +74,9 @@ class PushUtils {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appPreferences: AppPreferences
|
lateinit var appPreferences: AppPreferences
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var arbitraryStorageManager: ArbitraryStorageManager
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
@Inject
|
@Inject
|
||||||
var eventBus: EventBus? = null
|
var eventBus: EventBus? = null
|
||||||
|
@ -243,6 +247,13 @@ class PushUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNext(pushRegistrationOverall: PushRegistrationOverall) {
|
override fun onNext(pushRegistrationOverall: PushRegistrationOverall) {
|
||||||
|
arbitraryStorageManager.storeStorageSetting(
|
||||||
|
getIdForUser(user),
|
||||||
|
LATEST_PUSH_REGISTRATION_AT_SERVER,
|
||||||
|
System.currentTimeMillis().toString(),
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
Log.d(TAG, "pushTokenHash successfully registered at nextcloud server.")
|
Log.d(TAG, "pushTokenHash successfully registered at nextcloud server.")
|
||||||
val proxyMap: MutableMap<String, String?> = HashMap()
|
val proxyMap: MutableMap<String, String?> = HashMap()
|
||||||
proxyMap["pushToken"] = token
|
proxyMap["pushToken"] = token
|
||||||
|
@ -273,6 +284,13 @@ class PushUtils {
|
||||||
|
|
||||||
override fun onNext(t: Unit) {
|
override fun onNext(t: Unit) {
|
||||||
try {
|
try {
|
||||||
|
arbitraryStorageManager.storeStorageSetting(
|
||||||
|
getIdForUser(user),
|
||||||
|
LATEST_PUSH_REGISTRATION_AT_PUSH_PROXY,
|
||||||
|
System.currentTimeMillis().toString(),
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
Log.d(TAG, "pushToken successfully registered at pushproxy.")
|
Log.d(TAG, "pushToken successfully registered at pushproxy.")
|
||||||
updatePushStateForUser(proxyMap, user)
|
updatePushStateForUser(proxyMap, user)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
@ -396,5 +414,7 @@ class PushUtils {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "PushUtils"
|
private const val TAG = "PushUtils"
|
||||||
|
const val LATEST_PUSH_REGISTRATION_AT_SERVER: String = "LATEST_PUSH_REGISTRATION_AT_SERVER"
|
||||||
|
const val LATEST_PUSH_REGISTRATION_AT_PUSH_PROXY: String = "LATEST_PUSH_REGISTRATION_AT_PUSH_PROXY"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,182 +0,0 @@
|
||||||
/*
|
|
||||||
* Nextcloud Talk application
|
|
||||||
*
|
|
||||||
* @author Mario Danic
|
|
||||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This class is in part based on the code from the great people that wrote Signal
|
|
||||||
* https://github.com/signalapp/Signal-Android/raw/f9adb4e4554a44fd65b77320e34bf4bccf7924ce/src/org/thoughtcrime/securesms/webrtc/locks/LockManager.java
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.nextcloud.talk.utils.power;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.net.wifi.WifiManager;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.provider.Settings;
|
|
||||||
|
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import autodagger.AutoInjector;
|
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication.class)
|
|
||||||
|
|
||||||
public class PowerManagerUtils {
|
|
||||||
private static final String TAG = "PowerManagerUtils";
|
|
||||||
private final PowerManager.WakeLock fullLock;
|
|
||||||
private final PowerManager.WakeLock partialLock;
|
|
||||||
private final WifiManager.WifiLock wifiLock;
|
|
||||||
private final boolean wifiLockEnforced;
|
|
||||||
@Inject
|
|
||||||
Context context;
|
|
||||||
private ProximityLock proximityLock;
|
|
||||||
private boolean proximityDisabled = false;
|
|
||||||
|
|
||||||
private int orientation;
|
|
||||||
|
|
||||||
public PowerManagerUtils() {
|
|
||||||
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
|
||||||
|
|
||||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
|
||||||
fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "nctalk:fullwakelock");
|
|
||||||
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nctalk:partialwakelock");
|
|
||||||
proximityLock = new ProximityLock(pm);
|
|
||||||
|
|
||||||
// we suppress a possible leak because this is indeed application context
|
|
||||||
@SuppressLint("WifiManagerPotentialLeak") WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
|
||||||
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "nctalk:wifiwakelock");
|
|
||||||
|
|
||||||
fullLock.setReferenceCounted(false);
|
|
||||||
partialLock.setReferenceCounted(false);
|
|
||||||
wifiLock.setReferenceCounted(false);
|
|
||||||
|
|
||||||
wifiLockEnforced = isWifiPowerActiveModeEnabled(context);
|
|
||||||
orientation = context.getResources().getConfiguration().orientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrientation(int newOrientation) {
|
|
||||||
orientation = newOrientation;
|
|
||||||
updateInCallWakeLockState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updatePhoneState(PhoneState state) {
|
|
||||||
switch (state) {
|
|
||||||
case IDLE:
|
|
||||||
setWakeLockState(WakeLockState.SLEEP);
|
|
||||||
break;
|
|
||||||
case PROCESSING:
|
|
||||||
setWakeLockState(WakeLockState.PARTIAL);
|
|
||||||
break;
|
|
||||||
case INTERACTIVE:
|
|
||||||
setWakeLockState(WakeLockState.FULL);
|
|
||||||
break;
|
|
||||||
case WITH_PROXIMITY_SENSOR_LOCK:
|
|
||||||
proximityDisabled = false;
|
|
||||||
updateInCallWakeLockState();
|
|
||||||
break;
|
|
||||||
case WITHOUT_PROXIMITY_SENSOR_LOCK:
|
|
||||||
proximityDisabled = true;
|
|
||||||
updateInCallWakeLockState();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateInCallWakeLockState() {
|
|
||||||
if (orientation != Configuration.ORIENTATION_LANDSCAPE && wifiLockEnforced && !proximityDisabled) {
|
|
||||||
setWakeLockState(WakeLockState.PROXIMITY);
|
|
||||||
} else {
|
|
||||||
setWakeLockState(WakeLockState.FULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isWifiPowerActiveModeEnabled(Context context) {
|
|
||||||
int wifi_pwr_active_mode = Settings.Secure.getInt(context.getContentResolver(), "wifi_pwr_active_mode", -1);
|
|
||||||
return (wifi_pwr_active_mode != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("WakelockTimeout")
|
|
||||||
private synchronized void setWakeLockState(WakeLockState newState) {
|
|
||||||
switch (newState) {
|
|
||||||
case FULL:
|
|
||||||
if (!fullLock.isHeld()) {
|
|
||||||
fullLock.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!partialLock.isHeld()) {
|
|
||||||
partialLock.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wifiLock.isHeld()) {
|
|
||||||
wifiLock.acquire();
|
|
||||||
}
|
|
||||||
proximityLock.release();
|
|
||||||
break;
|
|
||||||
case PARTIAL:
|
|
||||||
if (!partialLock.isHeld()) {
|
|
||||||
partialLock.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wifiLock.isHeld()) {
|
|
||||||
wifiLock.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
fullLock.release();
|
|
||||||
proximityLock.release();
|
|
||||||
break;
|
|
||||||
case SLEEP:
|
|
||||||
fullLock.release();
|
|
||||||
partialLock.release();
|
|
||||||
wifiLock.release();
|
|
||||||
proximityLock.release();
|
|
||||||
break;
|
|
||||||
case PROXIMITY:
|
|
||||||
if (!partialLock.isHeld()) {
|
|
||||||
partialLock.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wifiLock.isHeld()) {
|
|
||||||
wifiLock.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
fullLock.release(
|
|
||||||
|
|
||||||
);
|
|
||||||
proximityLock.acquire();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// something went very very wrong
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PhoneState {
|
|
||||||
IDLE,
|
|
||||||
PROCESSING, //used when the phone is active but before the user should be alerted.
|
|
||||||
INTERACTIVE,
|
|
||||||
WITHOUT_PROXIMITY_SENSOR_LOCK,
|
|
||||||
WITH_PROXIMITY_SENSOR_LOCK
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum WakeLockState {
|
|
||||||
FULL,
|
|
||||||
PARTIAL,
|
|
||||||
SLEEP,
|
|
||||||
PROXIMITY
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
/*
|
||||||
|
* Nextcloud Talk application
|
||||||
|
*
|
||||||
|
* @author Mario Danic
|
||||||
|
* @author Marcel Hibbe
|
||||||
|
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This class is in part based on the code from the great people that wrote Signal
|
||||||
|
* https://github.com/signalapp/Signal-Android/raw/f9adb4e4554a44fd65b77320e34bf4bccf7924ce/src/org/thoughtcrime/securesms/webrtc/locks/LockManager.java
|
||||||
|
*/
|
||||||
|
package com.nextcloud.talk.utils.power
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Context.POWER_SERVICE
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.net.wifi.WifiManager
|
||||||
|
import android.net.wifi.WifiManager.WifiLock
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.provider.Settings
|
||||||
|
import autodagger.AutoInjector
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
|
class PowerManagerUtils {
|
||||||
|
private val fullLock: PowerManager.WakeLock
|
||||||
|
private val partialLock: PowerManager.WakeLock
|
||||||
|
private val wifiLock: WifiLock
|
||||||
|
private val wifiLockEnforced: Boolean
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@Inject
|
||||||
|
var context: Context? = null
|
||||||
|
private val proximityLock: ProximityLock
|
||||||
|
private var proximityDisabled = false
|
||||||
|
private var orientation: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
|
val pm = context!!.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
fullLock = pm.newWakeLock(
|
||||||
|
PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
|
||||||
|
"nctalk:fullwakelock"
|
||||||
|
)
|
||||||
|
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nctalk:partialwakelock")
|
||||||
|
proximityLock = ProximityLock(pm)
|
||||||
|
|
||||||
|
// we suppress a possible leak because this is indeed application context
|
||||||
|
@SuppressLint("WifiManagerPotentialLeak")
|
||||||
|
val wm =
|
||||||
|
context!!.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||||
|
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "nctalk:wifiwakelock")
|
||||||
|
fullLock.setReferenceCounted(false)
|
||||||
|
partialLock.setReferenceCounted(false)
|
||||||
|
wifiLock.setReferenceCounted(false)
|
||||||
|
wifiLockEnforced = isWifiPowerActiveModeEnabled(context)
|
||||||
|
orientation = context!!.resources.configuration.orientation
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isIgnoringBatteryOptimizations(): Boolean {
|
||||||
|
val packageName = context!!.packageName
|
||||||
|
val pm = context!!.getSystemService(POWER_SERVICE) as PowerManager
|
||||||
|
return pm.isIgnoringBatteryOptimizations(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOrientation(newOrientation: Int) {
|
||||||
|
orientation = newOrientation
|
||||||
|
updateInCallWakeLockState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePhoneState(state: PhoneState?) {
|
||||||
|
when (state) {
|
||||||
|
PhoneState.IDLE -> setWakeLockState(WakeLockState.SLEEP)
|
||||||
|
PhoneState.PROCESSING -> setWakeLockState(WakeLockState.PARTIAL)
|
||||||
|
PhoneState.INTERACTIVE -> setWakeLockState(WakeLockState.FULL)
|
||||||
|
PhoneState.WITH_PROXIMITY_SENSOR_LOCK -> {
|
||||||
|
proximityDisabled = false
|
||||||
|
updateInCallWakeLockState()
|
||||||
|
}
|
||||||
|
|
||||||
|
PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK -> {
|
||||||
|
proximityDisabled = true
|
||||||
|
updateInCallWakeLockState()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateInCallWakeLockState() {
|
||||||
|
if (orientation != Configuration.ORIENTATION_LANDSCAPE && wifiLockEnforced && !proximityDisabled) {
|
||||||
|
setWakeLockState(WakeLockState.PROXIMITY)
|
||||||
|
} else {
|
||||||
|
setWakeLockState(WakeLockState.FULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isWifiPowerActiveModeEnabled(context: Context?): Boolean {
|
||||||
|
val wifiPowerActiveMode = Settings.Secure.getInt(context!!.contentResolver, "wifi_pwr_active_mode", -1)
|
||||||
|
return wifiPowerActiveMode != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("WakelockTimeout")
|
||||||
|
@Synchronized
|
||||||
|
private fun setWakeLockState(newState: WakeLockState) {
|
||||||
|
when (newState) {
|
||||||
|
WakeLockState.FULL -> {
|
||||||
|
if (!fullLock.isHeld) {
|
||||||
|
fullLock.acquire()
|
||||||
|
}
|
||||||
|
if (!partialLock.isHeld) {
|
||||||
|
partialLock.acquire()
|
||||||
|
}
|
||||||
|
if (!wifiLock.isHeld) {
|
||||||
|
wifiLock.acquire()
|
||||||
|
}
|
||||||
|
proximityLock.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
WakeLockState.PARTIAL -> {
|
||||||
|
if (!partialLock.isHeld) {
|
||||||
|
partialLock.acquire()
|
||||||
|
}
|
||||||
|
if (!wifiLock.isHeld) {
|
||||||
|
wifiLock.acquire()
|
||||||
|
}
|
||||||
|
fullLock.release()
|
||||||
|
proximityLock.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
WakeLockState.SLEEP -> {
|
||||||
|
fullLock.release()
|
||||||
|
partialLock.release()
|
||||||
|
wifiLock.release()
|
||||||
|
proximityLock.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
WakeLockState.PROXIMITY -> {
|
||||||
|
if (!partialLock.isHeld) {
|
||||||
|
partialLock.acquire()
|
||||||
|
}
|
||||||
|
if (!wifiLock.isHeld) {
|
||||||
|
wifiLock.acquire()
|
||||||
|
}
|
||||||
|
fullLock.release()
|
||||||
|
proximityLock.acquire()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class PhoneState {
|
||||||
|
IDLE,
|
||||||
|
PROCESSING,
|
||||||
|
|
||||||
|
// used when the phone is active but before the user should be alerted.
|
||||||
|
INTERACTIVE,
|
||||||
|
WITHOUT_PROXIMITY_SENSOR_LOCK,
|
||||||
|
WITH_PROXIMITY_SENSOR_LOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class WakeLockState {
|
||||||
|
FULL,
|
||||||
|
PARTIAL,
|
||||||
|
SLEEP,
|
||||||
|
PROXIMITY
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = PowerManagerUtils::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,6 +69,14 @@ public interface AppPreferences {
|
||||||
|
|
||||||
void setPushToken(String pushToken);
|
void setPushToken(String pushToken);
|
||||||
|
|
||||||
|
Long getPushTokenLatestGeneration();
|
||||||
|
|
||||||
|
void setPushTokenLatestGeneration(Long date);
|
||||||
|
|
||||||
|
Long getPushTokenLatestFetch();
|
||||||
|
|
||||||
|
void setPushTokenLatestFetch(Long date);
|
||||||
|
|
||||||
void removePushToken();
|
void removePushToken();
|
||||||
|
|
||||||
String getTemporaryClientCertAlias();
|
String getTemporaryClientCertAlias();
|
||||||
|
|
|
@ -159,6 +159,28 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
|
||||||
pushToken = ""
|
pushToken = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getPushTokenLatestGeneration(): Long {
|
||||||
|
return runBlocking { async { readLong(PUSH_TOKEN_LATEST_GENERATION).first() } }.getCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPushTokenLatestGeneration(date: Long) =
|
||||||
|
runBlocking<Unit> {
|
||||||
|
async {
|
||||||
|
writeLong(PUSH_TOKEN_LATEST_GENERATION, date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPushTokenLatestFetch(): Long {
|
||||||
|
return runBlocking { async { readLong(PUSH_TOKEN_LATEST_FETCH).first() } }.getCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPushTokenLatestFetch(date: Long) =
|
||||||
|
runBlocking<Unit> {
|
||||||
|
async {
|
||||||
|
writeLong(PUSH_TOKEN_LATEST_FETCH, date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getTemporaryClientCertAlias(): String {
|
override fun getTemporaryClientCertAlias(): String {
|
||||||
return runBlocking { async { readString(TEMP_CLIENT_CERT_ALIAS).first() } }.getCompleted()
|
return runBlocking { async { readString(TEMP_CLIENT_CERT_ALIAS).first() } }.getCompleted()
|
||||||
}
|
}
|
||||||
|
@ -512,6 +534,8 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
|
||||||
const val PROXY_USERNAME = "proxy_username"
|
const val PROXY_USERNAME = "proxy_username"
|
||||||
const val PROXY_PASSWORD = "proxy_password"
|
const val PROXY_PASSWORD = "proxy_password"
|
||||||
const val PUSH_TOKEN = "push_token"
|
const val PUSH_TOKEN = "push_token"
|
||||||
|
const val PUSH_TOKEN_LATEST_GENERATION = "push_token_latest_generation"
|
||||||
|
const val PUSH_TOKEN_LATEST_FETCH = "push_token_latest_fetch"
|
||||||
const val TEMP_CLIENT_CERT_ALIAS = "tempClientCertAlias"
|
const val TEMP_CLIENT_CERT_ALIAS = "tempClientCertAlias"
|
||||||
const val PUSH_TO_TALK_INTRO_SHOWN = "pushToTalk_intro_shown"
|
const val PUSH_TO_TALK_INTRO_SHOWN = "pushToTalk_intro_shown"
|
||||||
const val CALL_RINGTONE = "call_ringtone"
|
const val CALL_RINGTONE = "call_ringtone"
|
||||||
|
|
60
app/src/main/res/layout/activity_diagnose.xml
Normal file
60
app/src/main/res/layout/activity_diagnose.xml
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
~
|
||||||
|
~ 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
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/parent_container"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/settings_appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/settings_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="@color/appbar"
|
||||||
|
android:theme="?attr/actionBarPopupTheme"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways"
|
||||||
|
app:navigationIconTint="@color/fontAppbar"
|
||||||
|
app:popupTheme="@style/appActionBarPopupMenu"
|
||||||
|
app:titleTextColor="@color/fontAppbar"
|
||||||
|
tools:title="@string/nc_app_product_name" />
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/diagnose_content_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/standard_padding">
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
|
@ -7,7 +7,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/settings_appbar"
|
android:id="@+id/settings_appbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -195,6 +194,70 @@
|
||||||
android:textSize="@dimen/headline_text_size"
|
android:textSize="@dimen/headline_text_size"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/settings_gplay_only_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/settings_notifications_permission_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/standard_padding"
|
||||||
|
android:background="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="@dimen/headline_text_size"
|
||||||
|
android:text="@string/nc_diagnose_notification_permission" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/nc_diagnose_notification_permission_subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/settings_battery_optimization_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/standard_padding"
|
||||||
|
android:background="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="@dimen/headline_text_size"
|
||||||
|
android:text="@string/nc_diagnose_battery_optimization_title" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/battery_optimization_ignored"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="@string/nc_diagnose_battery_optimization_ignored"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/settings_gplay_not_available"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/standard_padding"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/nc_diagnose_gplay_available_no"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/settings_call_sound"
|
android:id="@+id/settings_call_sound"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -242,6 +305,7 @@
|
||||||
android:textSize="@dimen/supporting_text_text_size"/>
|
android:textSize="@dimen/supporting_text_text_size"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -515,6 +579,28 @@
|
||||||
android:textSize="@dimen/headline_text_size"
|
android:textSize="@dimen/headline_text_size"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/diagnose_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/standard_padding"
|
||||||
|
android:background="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/diagnose_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="@dimen/headline_text_size"
|
||||||
|
android:text="@string/nc_settings_diagnose_title"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/diagnose_subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/nc_settings_diagnose_subtitle"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/settings_client_cert"
|
android:id="@+id/settings_client_cert"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
34
app/src/main/res/menu/menu_diagnose.xml
Normal file
34
app/src/main/res/menu/menu_diagnose.xml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
~ Nextcloud Talk application
|
||||||
|
~
|
||||||
|
~ @author Marcel Hibbe
|
||||||
|
~ Copyright (C) 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||||
|
~
|
||||||
|
~ 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
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:id="@+id/copy"
|
||||||
|
android:title="@string/nc_common_copy" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/share"
|
||||||
|
android:title="@string/share" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/send_mail"
|
||||||
|
android:title="@string/send_email" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/create_issue"
|
||||||
|
android:title="@string/create_issue" />
|
||||||
|
</menu>
|
|
@ -5,7 +5,7 @@
|
||||||
~ @author Mario Danic
|
~ @author Mario Danic
|
||||||
~ @author Marcel Hibbe
|
~ @author Marcel Hibbe
|
||||||
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||||
~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
~ Copyright (C) 2022-2023 Marcel Hibbe <dev@mhibbe.de>
|
||||||
~
|
~
|
||||||
~ 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
|
||||||
|
@ -43,6 +43,7 @@
|
||||||
<string name="nc_privacy_url" translatable="false">https://nextcloud.com/privacy/</string>
|
<string name="nc_privacy_url" translatable="false">https://nextcloud.com/privacy/</string>
|
||||||
<string name="nc_gpl3_url" translatable="false">https://www.gnu.org/licenses/gpl-3.0.en.html</string>
|
<string name="nc_gpl3_url" translatable="false">https://www.gnu.org/licenses/gpl-3.0.en.html</string>
|
||||||
<string name="nc_source_code_url" translatable="false">https://github.com/nextcloud/talk-android</string>
|
<string name="nc_source_code_url" translatable="false">https://github.com/nextcloud/talk-android</string>
|
||||||
|
<string name="nc_talk_android_issues_url" translatable="false">https://github.com/nextcloud/talk-android/issues</string>
|
||||||
<string name="nc_providers_url" translatable="false">https://nextcloud.com/providers</string>
|
<string name="nc_providers_url" translatable="false">https://nextcloud.com/providers</string>
|
||||||
|
|
||||||
<!-- Package name from which to import accounts - if empty, won't ever be shown -->
|
<!-- Package name from which to import accounts - if empty, won't ever be shown -->
|
||||||
|
@ -61,6 +62,9 @@
|
||||||
<string name="google_crash_reporting_api_key" translatable="false">AIzaSyAWIyOcLafaFp8PFL61h64cy1NNZW2cU_s</string>
|
<string name="google_crash_reporting_api_key" translatable="false">AIzaSyAWIyOcLafaFp8PFL61h64cy1NNZW2cU_s</string>
|
||||||
<string name="google_storage_bucket" translatable="false">nextcloud-a7dea.appspot.com</string>
|
<string name="google_storage_bucket" translatable="false">nextcloud-a7dea.appspot.com</string>
|
||||||
|
|
||||||
|
<string name="notification_checklist_url" translatable="false">https://github.com/nextcloud/talk-android/blob/master/docs/notifications.md</string>
|
||||||
|
<string name="dontkillmyapp_url" translatable="false">https://dontkillmyapp.com/</string>
|
||||||
|
|
||||||
<!-- Map and Geocoding -->
|
<!-- Map and Geocoding -->
|
||||||
<string name="osm_tile_server_url" translatable="false">https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png</string>
|
<string name="osm_tile_server_url" translatable="false">https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png</string>
|
||||||
<string name="osm_tile_server_attributation" translatable="false">OpenStreetMap contributors</string>
|
<string name="osm_tile_server_attributation" translatable="false">OpenStreetMap contributors</string>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
~ @author Marcel Hibbe
|
~ @author Marcel Hibbe
|
||||||
~ @author Tim Krüger
|
~ @author Tim Krüger
|
||||||
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||||
~ Copyright (C) 2022-2023 Marcel Hibbe <dev@mhibbe.de>
|
~ Copyright (C) 2022-2024 Marcel Hibbe <dev@mhibbe.de>
|
||||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||||
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||||
~
|
~
|
||||||
|
@ -50,6 +50,10 @@ How to translate with transifex:
|
||||||
<string name="nc_common_dismiss">Dismiss</string>
|
<string name="nc_common_dismiss">Dismiss</string>
|
||||||
<string name="nc_common_error_sorry">Sorry, something went wrong!</string>
|
<string name="nc_common_error_sorry">Sorry, something went wrong!</string>
|
||||||
<string name="nc_common_create">Create</string>
|
<string name="nc_common_create">Create</string>
|
||||||
|
<string name="nc_common_unknown">Unknown</string>
|
||||||
|
<string name="nc_common_disabled">Disabled</string>
|
||||||
|
<string name="nc_common_copy">Copy</string>
|
||||||
|
<string name="nc_common_copy_success">Copied to clipboard</string>
|
||||||
|
|
||||||
<!-- Bottom Navigation -->
|
<!-- Bottom Navigation -->
|
||||||
<string name="nc_settings">Settings</string>
|
<string name="nc_settings">Settings</string>
|
||||||
|
@ -180,6 +184,63 @@ How to translate with transifex:
|
||||||
|
|
||||||
<string name="nc_profile_personal_info_title">Personal Info</string>
|
<string name="nc_profile_personal_info_title">Personal Info</string>
|
||||||
|
|
||||||
|
<string name="nc_settings_diagnose_title">Diagnose</string>
|
||||||
|
<string name="nc_settings_diagnose_subtitle">Open Diagnose screen to check settings or create bug report</string>
|
||||||
|
|
||||||
|
<string name="nc_settings_notifications_granted">Notifications are granted</string>
|
||||||
|
<string name="nc_settings_notifications_declined">Notifications are declined</string>
|
||||||
|
<string name="nc_settings_notifications_declined_hint">Notifications are declined. Please allow notifications in android settings</string>
|
||||||
|
|
||||||
|
<!-- Diagnose -->
|
||||||
|
<string name="nc_notifications_troubleshooting_dialog_title">Notification troubleshooting</string>
|
||||||
|
<string name="nc_notifications_troubleshooting_dialog_text">Notification permission and battery settings are correctly set up to receive notifications. If you have problems to receive notifications anyway, please check if the notification channels for calls and messages are enabled. Further help can be found at DontKillMyApp.com or at the troubleshooting checklist. If this does not help, please go to diagnose screen and send a bug report.</string>
|
||||||
|
<string name="nc_diagnose_dialog_open_checklist">Open troubleshooting checklist</string>
|
||||||
|
<string name="nc_diagnose_dialog_open_dontkillmyapp_website">Open dontkillmyapp.com</string>
|
||||||
|
<string name="nc_diagnose_dialog_open_diagnose">Open diagnose screen</string>
|
||||||
|
|
||||||
|
<string name="nc_ignore_battery_optimization_dialog_title">Ignore battery optimization</string>
|
||||||
|
<string name="nc_ignore_battery_optimization_dialog_text">Battery optimization is not ignored. This should be changed to make sure that notifications work in the background! Please click OK and select \"All apps\" -> %1$s -> Don\'t optimize</string>
|
||||||
|
|
||||||
|
<string name="nc_diagnose_meta_category_title">Meta information</string>
|
||||||
|
<string name="nc_diagnose_meta_system_report_date">Generation of system report</string>
|
||||||
|
<string name="nc_diagnose_phone_category_title">Phone</string>
|
||||||
|
<string name="nc_diagnose_device_name_title">Device</string>
|
||||||
|
<string name="nc_diagnose_android_version_title">Android version</string>
|
||||||
|
<string name="nc_diagnose_app_category_title">App</string>
|
||||||
|
<string name="nc_diagnose_app_name_title">App name</string>
|
||||||
|
<string name="nc_diagnose_app_version_title">App version</string>
|
||||||
|
<string name="nc_diagnose_app_users_amount">Registered users</string>
|
||||||
|
<string name="nc_diagnose_gplay_available_title">Google play services</string>
|
||||||
|
<string name="nc_diagnose_build_flavor">Build flavor</string>
|
||||||
|
<string name="nc_diagnose_gplay_available_yes">Google play services are available</string>
|
||||||
|
<string name="nc_diagnose_gplay_available_no">Google play services are not available. Notifications are not supported</string>
|
||||||
|
<string name="nc_diagnose_battery_optimization_title">Battery settings</string>
|
||||||
|
<string name="nc_diagnose_battery_optimization_not_ignored">Battery optimization is not ignored. This should be changed!</string>
|
||||||
|
<string name="nc_diagnose_battery_optimization_ignored">Battery optimization is ignored, all fine</string>
|
||||||
|
<string name="nc_diagnose_notification_permission">Notification permissions</string>
|
||||||
|
<string name="nc_diagnose_notification_calls_channel_permission">Calls notification channel enabled?</string>
|
||||||
|
<string name="nc_diagnose_notification_messages_channel_permission">Messages notification channel enabled?</string>
|
||||||
|
<string name="nc_diagnose_firebase_push_token_title">Firebase push token</string>
|
||||||
|
<string name="nc_diagnose_firebase_push_token_latest_generated">Latest firebase push token generation</string>
|
||||||
|
<string name="nc_diagnose_firebase_push_token_latest_fetch">Latest firebase push token fetch</string>
|
||||||
|
<string name="nc_diagnose_firebase_push_token_missing">No firebase push token set. Please create a bug report.</string>
|
||||||
|
<string name="nc_diagnose_account_category_title">Current account</string>
|
||||||
|
<string name="nc_diagnose_account_server">Server</string>
|
||||||
|
<string name="nc_diagnose_account_user_name">User</string>
|
||||||
|
<string name="nc_diagnose_account_server_notification_app">Server notification app installed?</string>
|
||||||
|
<string name="nc_diagnose_account_user_status_enabled">User status enabled?</string>
|
||||||
|
<string name="nc_diagnose_latest_push_registration_at_server">Latest push registration at server</string>
|
||||||
|
<string name="nc_diagnose_latest_push_registration_at_server_fail">Not yet registered at server</string>
|
||||||
|
<string name="nc_diagnose_latest_push_registration_at_push_proxy">Latest push registration at push proxy</string>
|
||||||
|
<string name="nc_diagnose_latest_push_registration_at_push_proxy_fail">Not yet registered at push proxy</string>
|
||||||
|
<string name="nc_diagnose_server_version">Server version</string>
|
||||||
|
<string name="nc_diagnose_server_talk_version">Server Talk version</string>
|
||||||
|
<string name="nc_diagnose_signaling_mode_title">Signaling Mode</string>
|
||||||
|
<string name="nc_diagnose_signaling_mode_intern">Internal</string>
|
||||||
|
<string name="nc_diagnose_signaling_mode_extern">External</string>
|
||||||
|
<string name="send_email">Send email</string>
|
||||||
|
<string name="create_issue">Create issue</string>
|
||||||
|
|
||||||
<!-- Conversation menu -->
|
<!-- Conversation menu -->
|
||||||
<string name="nc_leave">Leave conversation</string>
|
<string name="nc_leave">Leave conversation</string>
|
||||||
<string name="nc_clear_history">Delete all messages</string>
|
<string name="nc_clear_history">Delete all messages</string>
|
||||||
|
|
Loading…
Reference in a new issue