mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-27 12:26:11 +00:00
Refactor network logging
This commit is contained in:
@@ -440,7 +440,10 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
composable<RecommendedGlobalProxy> {
|
composable<RecommendedGlobalProxy> {
|
||||||
RecommendedGlobalProxyScreen(vm::setRecommendedGlobalProxy, ::navigateUp)
|
RecommendedGlobalProxyScreen(vm::setRecommendedGlobalProxy, ::navigateUp)
|
||||||
}
|
}
|
||||||
composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) }
|
composable<NetworkLogging> {
|
||||||
|
NetworkLoggingScreen(vm::getNetworkLoggingEnabled, vm::setNetworkLoggingEnabled,
|
||||||
|
vm::getNetworkLogsCount, vm::exportNetworkLogs, vm::deleteNetworkLogs, ::navigateUp)
|
||||||
|
}
|
||||||
//composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
|
//composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
|
||||||
composable<PreferentialNetworkService> {
|
composable<PreferentialNetworkService> {
|
||||||
PreferentialNetworkServiceScreen(vm::getPnsEnabled, vm::setPnsEnabled, vm.pnsConfigs,
|
PreferentialNetworkServiceScreen(vm::getPnsEnabled, vm::setPnsEnabled, vm.pnsConfigs,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.content.Context
|
|||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
|
||||||
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 2) {
|
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 3) {
|
||||||
override fun onCreate(db: SQLiteDatabase) {
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
db.execSQL("CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
|
db.execSQL("CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
|
||||||
"signature TEXT, permissions TEXT)")
|
"signature TEXT, permissions TEXT)")
|
||||||
@@ -14,5 +14,12 @@ class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 2) {
|
|||||||
db.execSQL("CREATE TABLE security_logs (id INTEGER, tag INTEGER, level INTEGER," +
|
db.execSQL("CREATE TABLE security_logs (id INTEGER, tag INTEGER, level INTEGER," +
|
||||||
"time INTEGER, data TEXT)")
|
"time INTEGER, data TEXT)")
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 3) {
|
||||||
|
db.execSQL(
|
||||||
|
"CREATE TABLE network_logs (id INTEGER, package INTEGER, time INTEGER," +
|
||||||
|
"type TEXT, host TEXT, count INTEGER, addresses TEXT, address TEXT," +
|
||||||
|
"port INTEGER)"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,10 @@ import android.database.DatabaseUtils
|
|||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.database.getIntOrNull
|
||||||
|
import androidx.core.database.getLongOrNull
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
|
import com.bintianqi.owndroid.dpm.NetworkLog
|
||||||
import com.bintianqi.owndroid.dpm.SecurityEvent
|
import com.bintianqi.owndroid.dpm.SecurityEvent
|
||||||
import com.bintianqi.owndroid.dpm.SecurityEventWithData
|
import com.bintianqi.owndroid.dpm.SecurityEventWithData
|
||||||
import com.bintianqi.owndroid.dpm.transformSecurityEventData
|
import com.bintianqi.owndroid.dpm.transformSecurityEventData
|
||||||
@@ -154,4 +157,71 @@ class MyRepository(val dbHelper: MyDbHelper) {
|
|||||||
fun deleteSecurityLogs() {
|
fun deleteSecurityLogs() {
|
||||||
dbHelper.writableDatabase.execSQL("DELETE FROM security_logs")
|
dbHelper.writableDatabase.execSQL("DELETE FROM security_logs")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getNetworkLogsCount(): Long {
|
||||||
|
return DatabaseUtils.queryNumEntries(dbHelper.readableDatabase, "network_logs")
|
||||||
|
}
|
||||||
|
fun writeNetworkLogs(logs: List<NetworkLog>) {
|
||||||
|
val db = dbHelper.writableDatabase
|
||||||
|
val statement = db.compileStatement(
|
||||||
|
"INSERT INTO network_logs VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||||
|
)
|
||||||
|
db.beginTransaction()
|
||||||
|
logs.forEach { event ->
|
||||||
|
if (event.id == null) statement.bindNull(1)
|
||||||
|
else statement.bindLong(1, event.id)
|
||||||
|
statement.bindString(2, event.packageName)
|
||||||
|
statement.bindLong(3, event.time)
|
||||||
|
statement.bindString(4, event.type)
|
||||||
|
if (event.host == null) statement.bindNull(5)
|
||||||
|
else statement.bindString(5, event.host)
|
||||||
|
if (event.count == null) statement.bindNull(6)
|
||||||
|
else statement.bindLong(6, event.count.toLong())
|
||||||
|
if (event.addresses == null) statement.bindNull(7)
|
||||||
|
else statement.bindString(7, event.addresses.joinToString(","))
|
||||||
|
if (event.address == null) statement.bindNull(8)
|
||||||
|
else statement.bindString(8, event.address)
|
||||||
|
if (event.port == null) statement.bindNull(9)
|
||||||
|
else statement.bindLong(9, event.port.toLong())
|
||||||
|
statement.executeInsert()
|
||||||
|
statement.clearBindings()
|
||||||
|
}
|
||||||
|
db.setTransactionSuccessful()
|
||||||
|
db.endTransaction()
|
||||||
|
statement.close()
|
||||||
|
}
|
||||||
|
fun exportNetworkLogs(stream: OutputStream) {
|
||||||
|
val bw = stream.bufferedWriter()
|
||||||
|
val json = Json {
|
||||||
|
explicitNulls = false
|
||||||
|
}
|
||||||
|
var offset = 0
|
||||||
|
var addComma = false
|
||||||
|
bw.write("[")
|
||||||
|
while (true) {
|
||||||
|
val cursor = dbHelper.readableDatabase.rawQuery(
|
||||||
|
"SELECT * FROM network_logs LIMIT ? OFFSET ?",
|
||||||
|
arrayOf(100.toString(), offset.toString())
|
||||||
|
)
|
||||||
|
if (cursor.count == 0) break
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
if (addComma) bw.write(",")
|
||||||
|
addComma = true
|
||||||
|
val log = NetworkLog(
|
||||||
|
cursor.getLongOrNull(0), cursor.getString(1), cursor.getLong(2),
|
||||||
|
cursor.getString(3), cursor.getStringOrNull(4), cursor.getIntOrNull(5),
|
||||||
|
cursor.getStringOrNull(6)?.split(',')?.filter { it.isNotEmpty() },
|
||||||
|
cursor.getStringOrNull(7), cursor.getIntOrNull(8)
|
||||||
|
)
|
||||||
|
bw.write(json.encodeToString(log))
|
||||||
|
offset += 100
|
||||||
|
}
|
||||||
|
cursor.close()
|
||||||
|
}
|
||||||
|
bw.write("]")
|
||||||
|
bw.close()
|
||||||
|
}
|
||||||
|
fun deleteNetworkLogs() {
|
||||||
|
dbHelper.writableDatabase.execSQL("DELETE FROM network_logs")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1753,6 +1753,28 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
fun removeApnConfig(id: Int): Boolean {
|
fun removeApnConfig(id: Int): Boolean {
|
||||||
return DPM.removeOverrideApn(DAR, id)
|
return DPM.removeOverrideApn(DAR, id)
|
||||||
}
|
}
|
||||||
|
@RequiresApi(26)
|
||||||
|
fun getNetworkLoggingEnabled(): Boolean {
|
||||||
|
return DPM.isNetworkLoggingEnabled(DAR)
|
||||||
|
}
|
||||||
|
@RequiresApi(26)
|
||||||
|
fun setNetworkLoggingEnabled(enabled: Boolean) {
|
||||||
|
DPM.setNetworkLoggingEnabled(DAR, enabled)
|
||||||
|
}
|
||||||
|
fun getNetworkLogsCount(): Int {
|
||||||
|
return myRepo.getNetworkLogsCount().toInt()
|
||||||
|
}
|
||||||
|
fun exportNetworkLogs(uri: Uri, callback: () -> Unit) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
application.contentResolver.openOutputStream(uri)?.use {
|
||||||
|
myRepo.exportNetworkLogs(it)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) { callback() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun deleteNetworkLogs() {
|
||||||
|
myRepo.deleteNetworkLogs()
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
fun getPasswordComplexity(): PasswordComplexity {
|
fun getPasswordComplexity(): PasswordComplexity {
|
||||||
|
|||||||
@@ -72,10 +72,15 @@ enum class NotificationType(
|
|||||||
12, R.string.security_logs_collected, R.drawable.description_fill0,
|
12, R.string.security_logs_collected, R.drawable.description_fill0,
|
||||||
MyNotificationChannel.SecurityLogging
|
MyNotificationChannel.SecurityLogging
|
||||||
),
|
),
|
||||||
|
NetworkLogsCollected(
|
||||||
|
13, R.string.network_logs_collected, R.drawable.description_fill0,
|
||||||
|
MyNotificationChannel.NetworkLogging
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MyNotificationChannel(val id: String, val text: Int, val importance: Int) {
|
enum class MyNotificationChannel(val id: String, val text: Int, val importance: Int) {
|
||||||
LockTaskMode("LockTaskMode", R.string.lock_task_mode, NotificationManagerCompat.IMPORTANCE_HIGH),
|
LockTaskMode("LockTaskMode", R.string.lock_task_mode, NotificationManagerCompat.IMPORTANCE_HIGH),
|
||||||
Events("Events", R.string.events, NotificationManagerCompat.IMPORTANCE_LOW),
|
Events("Events", R.string.events, NotificationManagerCompat.IMPORTANCE_LOW),
|
||||||
SecurityLogging("SecurityLogging", R.string.security_logging, NotificationManagerCompat.IMPORTANCE_MIN)
|
SecurityLogging("SecurityLogging", R.string.security_logging, NotificationManagerCompat.IMPORTANCE_MIN),
|
||||||
|
NetworkLogging("NetworkLogging", R.string.network_logging, NotificationManagerCompat.IMPORTANCE_MIN)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ fun AppChooserScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item { Spacer(Modifier.height(60.dp)) }
|
item { Spacer(Modifier.height(BottomPadding)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,9 @@ import android.os.Build.VERSION
|
|||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.os.UserManager
|
import android.os.UserManager
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.bintianqi.owndroid.dpm.handleNetworkLogs
|
|
||||||
import com.bintianqi.owndroid.dpm.handlePrivilegeChange
|
import com.bintianqi.owndroid.dpm.handlePrivilegeChange
|
||||||
|
import com.bintianqi.owndroid.dpm.retrieveNetworkLogs
|
||||||
import com.bintianqi.owndroid.dpm.retrieveSecurityLogs
|
import com.bintianqi.owndroid.dpm.retrieveSecurityLogs
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class Receiver : DeviceAdminReceiver() {
|
class Receiver : DeviceAdminReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
@@ -47,10 +44,8 @@ class Receiver : DeviceAdminReceiver() {
|
|||||||
|
|
||||||
override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
|
override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
|
||||||
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
|
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
|
||||||
if(VERSION.SDK_INT >= 26) {
|
if (VERSION.SDK_INT >= 26) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
retrieveNetworkLogs(context.applicationContext as MyApplication, batchToken)
|
||||||
handleNetworkLogs(context, batchToken)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ fun ApplicationDetailsScreen(
|
|||||||
)
|
)
|
||||||
if(VERSION.SDK_INT >= 28) FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { dialog = 1 }
|
if(VERSION.SDK_INT >= 28) FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { dialog = 1 }
|
||||||
FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 }
|
FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 }
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
if(dialog == 1 && VERSION.SDK_INT >= 28)
|
if(dialog == 1 && VERSION.SDK_INT >= 28)
|
||||||
ClearAppStorageDialog(packageName, vm::clearAppData) { dialog = 0 }
|
ClearAppStorageDialog(packageName, vm::clearAppData) { dialog = 0 }
|
||||||
@@ -606,7 +606,7 @@ fun CredentialManagerPolicyScreen(
|
|||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.apply))
|
Text(stringResource(R.string.apply))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -661,7 +661,7 @@ fun PermittedAsAndImPackages(
|
|||||||
}
|
}
|
||||||
Spacer(Modifier.height(10.dp))
|
Spacer(Modifier.height(10.dp))
|
||||||
Notes(note, HorizontalPadding)
|
Notes(note, HorizontalPadding)
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -771,7 +771,7 @@ fun PackageFunctionScreen(
|
|||||||
Text(stringResource(R.string.add))
|
Text(stringResource(R.string.add))
|
||||||
}
|
}
|
||||||
if (notes != null) Notes(notes, HorizontalPadding)
|
if (notes != null) Notes(notes, HorizontalPadding)
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,12 +29,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.add
|
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import kotlinx.serialization.json.putJsonArray
|
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
@SuppressLint("PrivateApi")
|
||||||
fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? {
|
fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? {
|
||||||
@@ -137,39 +132,37 @@ val runtimePermissions = listOf(
|
|||||||
PermissionItem(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION, R.drawable.history_fill0, true, 29)
|
PermissionItem(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION, R.drawable.history_fill0, true, 29)
|
||||||
).filter { VERSION.SDK_INT >= it.requiresApi }
|
).filter { VERSION.SDK_INT >= it.requiresApi }
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class NetworkLog(
|
||||||
|
val id: Long?, @SerialName("package") val packageName: String, val time: Long, val type: String,
|
||||||
|
val host: String?, val count: Int?, val addresses: List<String>?,
|
||||||
|
val address: String?, val port: Int?
|
||||||
|
)
|
||||||
|
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
fun handleNetworkLogs(context: Context, batchToken: Long) {
|
fun retrieveNetworkLogs(app: MyApplication, token: Long) {
|
||||||
val networkEvents = Privilege.DPM.retrieveNetworkLogs(Privilege.DAR, batchToken) ?: return
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val file = context.filesDir.resolve("NetworkLogs.json")
|
val logs = Privilege.DPM.retrieveNetworkLogs(Privilege.DAR, token)?.mapNotNull {
|
||||||
val fileExist = file.exists()
|
when (it) {
|
||||||
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
|
is DnsEvent -> NetworkLog(
|
||||||
val buffer = file.bufferedWriter()
|
if (VERSION.SDK_INT >= 28) it.id else null, it.packageName, it.timestamp, "dns",
|
||||||
networkEvents.forEachIndexed { index, event ->
|
it.hostname, it.totalResolvedAddressCount,
|
||||||
if(fileExist && index == 0) buffer.write(",")
|
it.inetAddresses.mapNotNull { address -> address.hostAddress }, null, null
|
||||||
val item = buildJsonObject {
|
)
|
||||||
if(VERSION.SDK_INT >= 28) put("id", event.id)
|
is ConnectEvent -> NetworkLog(
|
||||||
put("time", event.timestamp)
|
if (VERSION.SDK_INT >= 28) it.id else null, it.packageName, it.timestamp,
|
||||||
put("package", event.packageName)
|
"connect", null, null, null, it.inetAddress.hostAddress, it.port
|
||||||
if(event is DnsEvent) {
|
)
|
||||||
put("type", "dns")
|
else -> null
|
||||||
put("host", event.hostname)
|
|
||||||
put("count", event.totalResolvedAddressCount)
|
|
||||||
putJsonArray("addresses") {
|
|
||||||
event.inetAddresses.forEach { inetAddresses ->
|
|
||||||
add(inetAddresses.hostAddress)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (logs.isNullOrEmpty()) return@launch
|
||||||
|
app.myRepo.writeNetworkLogs(logs)
|
||||||
|
NotificationUtils.sendBasicNotification(
|
||||||
|
app, NotificationType.NetworkLogsCollected,
|
||||||
|
app.getString(R.string.n_logs_in_total, logs.size)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if(event is ConnectEvent) {
|
|
||||||
put("type", "connect")
|
|
||||||
put("address", event.inetAddress.hostAddress)
|
|
||||||
put("port", event.port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.write(json.encodeToString(item))
|
|
||||||
if(index < networkEvents.size - 1) buffer.write(",")
|
|
||||||
}
|
|
||||||
buffer.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -493,7 +486,8 @@ fun transformSecurityEventData(tag: Int, payload: Any): SecurityEventData? {
|
|||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
fun retrieveSecurityLogs(app: MyApplication) {
|
fun retrieveSecurityLogs(app: MyApplication) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val logs = Privilege.DPM.retrieveSecurityLogs(Privilege.DAR) ?: return@launch
|
val logs = Privilege.DPM.retrieveSecurityLogs(Privilege.DAR)
|
||||||
|
if (logs.isNullOrEmpty()) return@launch
|
||||||
app.myRepo.writeSecurityLogs(logs)
|
app.myRepo.writeSecurityLogs(logs)
|
||||||
NotificationUtils.sendBasicNotification(
|
NotificationUtils.sendBasicNotification(
|
||||||
app, NotificationType.SecurityLogsCollected,
|
app, NotificationType.SecurityLogsCollected,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL
|
|||||||
import android.app.admin.WifiSsidPolicy
|
import android.app.admin.WifiSsidPolicy
|
||||||
import android.app.usage.NetworkStats
|
import android.app.usage.NetworkStats
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Uri
|
||||||
import android.net.wifi.WifiConfiguration
|
import android.net.wifi.WifiConfiguration
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import android.provider.Telephony
|
import android.provider.Telephony
|
||||||
@@ -106,6 +107,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.bintianqi.owndroid.BottomPadding
|
||||||
import com.bintianqi.owndroid.HorizontalPadding
|
import com.bintianqi.owndroid.HorizontalPadding
|
||||||
import com.bintianqi.owndroid.MyViewModel
|
import com.bintianqi.owndroid.MyViewModel
|
||||||
import com.bintianqi.owndroid.Privilege
|
import com.bintianqi.owndroid.Privilege
|
||||||
@@ -116,6 +118,7 @@ import com.bintianqi.owndroid.formatFileSize
|
|||||||
import com.bintianqi.owndroid.adaptiveInsets
|
import com.bintianqi.owndroid.adaptiveInsets
|
||||||
import com.bintianqi.owndroid.popToast
|
import com.bintianqi.owndroid.popToast
|
||||||
import com.bintianqi.owndroid.showOperationResultToast
|
import com.bintianqi.owndroid.showOperationResultToast
|
||||||
|
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
||||||
import com.bintianqi.owndroid.ui.ErrorDialog
|
import com.bintianqi.owndroid.ui.ErrorDialog
|
||||||
import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem
|
import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem
|
||||||
import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem
|
import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem
|
||||||
@@ -135,6 +138,9 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
@Serializable object Network
|
@Serializable object Network
|
||||||
|
|
||||||
@@ -803,7 +809,7 @@ private fun AddNetworkScreen(
|
|||||||
) {
|
) {
|
||||||
Text(stringResource(if (updating) R.string.update else R.string.add))
|
Text(stringResource(if (updating) R.string.update else R.string.add))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(60.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1539,50 +1545,82 @@ fun RecommendedGlobalProxyScreen(
|
|||||||
|
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
@Composable
|
@Composable
|
||||||
fun NetworkLoggingScreen(onNavigateUp: () -> Unit) {
|
fun NetworkLoggingScreen(
|
||||||
|
getEnabled: () -> Boolean, setEnabled: (Boolean) -> Unit, getCount: () -> Int,
|
||||||
|
exportLogs: (Uri, () -> Unit) -> Unit, deleteLogs: () -> Unit, onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val logFile = context.filesDir.resolve("NetworkLogs.json")
|
var enabled by remember { mutableStateOf(false) }
|
||||||
var fileSize by remember { mutableLongStateOf(0) }
|
var count by remember { mutableIntStateOf(0) }
|
||||||
LaunchedEffect(Unit) { fileSize = logFile.length() }
|
var dialog by rememberSaveable { mutableStateOf(false) }
|
||||||
val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
|
var exporting by rememberSaveable { mutableStateOf(false) }
|
||||||
if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
|
LaunchedEffect(Unit) {
|
||||||
outStream.write("[".encodeToByteArray())
|
enabled = getEnabled()
|
||||||
logFile.inputStream().use { it.copyTo(outStream) }
|
count = getCount()
|
||||||
outStream.write("]".encodeToByteArray())
|
}
|
||||||
|
val exportLauncher = rememberLauncherForActivityResult(
|
||||||
|
ActivityResultContracts.CreateDocument("application/json")
|
||||||
|
) { uri ->
|
||||||
|
if (uri != null) {
|
||||||
|
exporting = true
|
||||||
|
exportLogs(uri) {
|
||||||
|
exporting = false
|
||||||
context.showOperationResultToast(true)
|
context.showOperationResultToast(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MyScaffold(R.string.network_logging, onNavigateUp) {
|
}
|
||||||
|
MyScaffold(R.string.network_logging, onNavigateUp, 0.dp) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
R.string.enable,
|
R.string.enable, enabled, {
|
||||||
getState = { Privilege.DPM.isNetworkLoggingEnabled(Privilege.DAR) },
|
setEnabled(it)
|
||||||
onCheckedChange = { Privilege.DPM.setNetworkLoggingEnabled(Privilege.DAR, it) },
|
enabled = it
|
||||||
padding = false
|
}
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.n_logs_in_total, count),
|
||||||
|
Modifier.padding(HorizontalPadding, 5.dp)
|
||||||
)
|
)
|
||||||
Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
|
|
||||||
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
{
|
||||||
exportNetworkLogsLauncher.launch("NetworkLogs.json")
|
val date = SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(Date())
|
||||||
|
exportLauncher.launch("network_logs_$date")
|
||||||
},
|
},
|
||||||
enabled = fileSize > 0,
|
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||||
modifier = Modifier.fillMaxWidth(0.49F)
|
count > 0
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.export_logs))
|
Text(stringResource(R.string.export_logs))
|
||||||
}
|
}
|
||||||
Button(
|
if (count > 0) Button(
|
||||||
onClick = {
|
{
|
||||||
logFile.delete()
|
dialog = true
|
||||||
fileSize = logFile.length()
|
|
||||||
},
|
},
|
||||||
enabled = fileSize > 0,
|
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||||
modifier = Modifier.fillMaxWidth(0.96F)
|
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.delete_logs))
|
Text(stringResource(R.string.delete_logs))
|
||||||
}
|
}
|
||||||
|
Spacer(Modifier.height(10.dp))
|
||||||
|
Notes(R.string.info_network_log, HorizontalPadding)
|
||||||
}
|
}
|
||||||
Notes(R.string.info_network_log)
|
if (exporting) CircularProgressDialog { exporting = false }
|
||||||
|
if (dialog) AlertDialog(
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.delete_logs))
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton({
|
||||||
|
deleteLogs()
|
||||||
|
dialog = false
|
||||||
|
}) {
|
||||||
|
Text(stringResource(R.string.confirm))
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton({ dialog = false }) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissRequest = { dialog = false }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable object PreferentialNetworkService
|
@Serializable object PreferentialNetworkService
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import androidx.compose.ui.text.input.ImeAction
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bintianqi.owndroid.AppInfo
|
import com.bintianqi.owndroid.AppInfo
|
||||||
|
import com.bintianqi.owndroid.BottomPadding
|
||||||
import com.bintianqi.owndroid.DhizukuClientInfo
|
import com.bintianqi.owndroid.DhizukuClientInfo
|
||||||
import com.bintianqi.owndroid.DhizukuPermissions
|
import com.bintianqi.owndroid.DhizukuPermissions
|
||||||
import com.bintianqi.owndroid.HorizontalPadding
|
import com.bintianqi.owndroid.HorizontalPadding
|
||||||
@@ -615,7 +616,7 @@ fun AddDelegatedAdminScreen(
|
|||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.delete))
|
Text(stringResource(R.string.delete))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bintianqi.owndroid.AppInfo
|
import com.bintianqi.owndroid.AppInfo
|
||||||
|
import com.bintianqi.owndroid.BottomPadding
|
||||||
import com.bintianqi.owndroid.HorizontalPadding
|
import com.bintianqi.owndroid.HorizontalPadding
|
||||||
import com.bintianqi.owndroid.MyViewModel
|
import com.bintianqi.owndroid.MyViewModel
|
||||||
import com.bintianqi.owndroid.Privilege
|
import com.bintianqi.owndroid.Privilege
|
||||||
@@ -1281,7 +1282,7 @@ private fun LockTaskPackages(
|
|||||||
Text(stringResource(R.string.add))
|
Text(stringResource(R.string.add))
|
||||||
}
|
}
|
||||||
Notes(R.string.info_lock_task_packages)
|
Notes(R.string.info_lock_task_packages)
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1331,7 +1332,7 @@ private fun LockTaskFeatures(
|
|||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.apply))
|
Text(stringResource(R.string.apply))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
ErrorDialog(errorMessage) { errorMessage = null }
|
ErrorDialog(errorMessage) { errorMessage = null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1417,7 +1418,7 @@ fun CaCertScreen(
|
|||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selectedCaCert != null && (dialog == 1 || dialog == 2)) {
|
if (selectedCaCert != null && (dialog == 1 || dialog == 2)) {
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ fun UserRestrictionEditorScreen(
|
|||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
|
||||||
keyboardActions = KeyboardActions { add() }
|
keyboardActions = KeyboardActions { add() }
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(40.dp))
|
Spacer(Modifier.height(BottomPadding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,6 @@
|
|||||||
<string name="invalid_config">Неверная конфигурация</string>
|
<string name="invalid_config">Неверная конфигурация</string>
|
||||||
<string name="excluded_hosts">Исключить хосты</string>
|
<string name="excluded_hosts">Исключить хосты</string>
|
||||||
<string name="network_logging">Сетевой журнал</string>
|
<string name="network_logging">Сетевой журнал</string>
|
||||||
<string name="log_file_size_is">Размер файла журнала: %1$s</string>
|
|
||||||
<string name="delete_logs">Удалить журналы</string>
|
<string name="delete_logs">Удалить журналы</string>
|
||||||
<string name="export_logs">Экспортировать журналы</string>
|
<string name="export_logs">Экспортировать журналы</string>
|
||||||
<string name="wifi_auth_keypair">Пара ключей Wi-Fi</string>
|
<string name="wifi_auth_keypair">Пара ключей Wi-Fi</string>
|
||||||
|
|||||||
@@ -306,7 +306,6 @@
|
|||||||
<string name="invalid_config">Geçersiz yapılandırma</string>
|
<string name="invalid_config">Geçersiz yapılandırma</string>
|
||||||
<string name="excluded_hosts">Hariç tutulan ana bilgisayarlar</string>
|
<string name="excluded_hosts">Hariç tutulan ana bilgisayarlar</string>
|
||||||
<string name="network_logging">Ağ Kayıtları</string>
|
<string name="network_logging">Ağ Kayıtları</string>
|
||||||
<string name="log_file_size_is">Kayıt dosyası boyutu: %1$s</string>
|
|
||||||
<string name="delete_logs">Kayıtları Sil</string>
|
<string name="delete_logs">Kayıtları Sil</string>
|
||||||
<string name="export_logs">Kayıtları Dışa Aktar</string>
|
<string name="export_logs">Kayıtları Dışa Aktar</string>
|
||||||
<string name="wifi_auth_keypair">Wi-Fi Kimlik Doğrulama Anahtar Çifti</string>
|
<string name="wifi_auth_keypair">Wi-Fi Kimlik Doğrulama Anahtar Çifti</string>
|
||||||
|
|||||||
@@ -291,7 +291,7 @@
|
|||||||
<string name="invalid_config">无效配置</string>
|
<string name="invalid_config">无效配置</string>
|
||||||
<string name="excluded_hosts">排除的主机</string>
|
<string name="excluded_hosts">排除的主机</string>
|
||||||
<string name="network_logging">网络日志</string>
|
<string name="network_logging">网络日志</string>
|
||||||
<string name="log_file_size_is">日志文件大小:%1$s</string>
|
<string name="network_logs_collected">网络日志已收集</string>
|
||||||
<string name="delete_logs">删除日志</string>
|
<string name="delete_logs">删除日志</string>
|
||||||
<string name="export_logs">导出日志</string>
|
<string name="export_logs">导出日志</string>
|
||||||
<string name="wifi_auth_keypair">Wi-Fi密钥对</string>
|
<string name="wifi_auth_keypair">Wi-Fi密钥对</string>
|
||||||
|
|||||||
@@ -325,7 +325,7 @@
|
|||||||
<string name="invalid_config">Invalid config</string>
|
<string name="invalid_config">Invalid config</string>
|
||||||
<string name="excluded_hosts">Excluded hosts</string>
|
<string name="excluded_hosts">Excluded hosts</string>
|
||||||
<string name="network_logging">Network logging</string>
|
<string name="network_logging">Network logging</string>
|
||||||
<string name="log_file_size_is">Log file size: %1$s</string>
|
<string name="network_logs_collected">Network logs collected</string>
|
||||||
<string name="delete_logs">Delete logs</string>
|
<string name="delete_logs">Delete logs</string>
|
||||||
<string name="export_logs">Export logs</string>
|
<string name="export_logs">Export logs</string>
|
||||||
<string name="wifi_auth_keypair">Wi-Fi keypair</string>
|
<string name="wifi_auth_keypair">Wi-Fi keypair</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user