mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 01:39:22 +02:00
Disable fallback and enable new app detection by random
This commit is contained in:
parent
72c3da3d0d
commit
c6e1a2b7c1
10 changed files with 34 additions and 532 deletions
|
@ -249,5 +249,5 @@ object ExperimentalFlags {
|
|||
const val ENABLE_SOFT_BLOCKING = 16384L
|
||||
const val SYNC_RELATED_NOTIFICATIONS = 32768L
|
||||
const val INSTANCE_ID_FG_APP_DETECTION = 65536L
|
||||
const val DISABLE_FG_APP_DETECTION_FALLBACK = 131072L
|
||||
// private const val OBSOLETE_DISABLE_FG_APP_DETECTION_FALLBACK = 131072L
|
||||
}
|
|
@ -16,49 +16,25 @@
|
|||
package io.timelimit.android.integration.platform.android.foregroundapp
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import io.timelimit.android.BuildConfig
|
||||
import io.timelimit.android.data.model.ExperimentalFlags
|
||||
import io.timelimit.android.integration.platform.ForegroundApp
|
||||
import io.timelimit.android.integration.platform.android.foregroundapp.usagestats.DirectUsageStatsReader
|
||||
import java.security.SecureRandom
|
||||
|
||||
class QForegroundAppHelper(context: Context): UsageStatsForegroundAppHelper(context) {
|
||||
companion object {
|
||||
private const val LOG_TAG = "QForegroundAppHelper"
|
||||
}
|
||||
|
||||
private val legacy = LollipopForegroundAppHelper(context)
|
||||
private val modern = InstanceIdForegroundAppHelper(context)
|
||||
private var fallbackCounter = 0
|
||||
private val forceNewMethod = SecureRandom().nextBoolean()
|
||||
|
||||
override suspend fun getForegroundApps(
|
||||
queryInterval: Long,
|
||||
experimentalFlags: Long
|
||||
): Set<ForegroundApp> {
|
||||
val useInstanceIdForegroundAppDetection = experimentalFlags and ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION == ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION
|
||||
val disableFallback = experimentalFlags and ExperimentalFlags.DISABLE_FG_APP_DETECTION_FALLBACK == ExperimentalFlags.DISABLE_FG_APP_DETECTION_FALLBACK
|
||||
val canUseModern = DirectUsageStatsReader.instanceIdSupported
|
||||
val didUserRequestModern = experimentalFlags and ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION == ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION
|
||||
val useModern = forceNewMethod || didUserRequestModern
|
||||
|
||||
val result = if (useInstanceIdForegroundAppDetection && disableFallback) {
|
||||
modern.getForegroundApps(queryInterval, experimentalFlags)
|
||||
} else if (useInstanceIdForegroundAppDetection && fallbackCounter == 0) {
|
||||
try {
|
||||
modern.getForegroundApps(queryInterval, experimentalFlags)
|
||||
} catch (ex: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOG_TAG, "falling back to the legacy implementation", ex)
|
||||
}
|
||||
|
||||
fallbackCounter = 100
|
||||
|
||||
legacy.getForegroundApps(queryInterval, experimentalFlags)
|
||||
}
|
||||
} else {
|
||||
legacy.getForegroundApps(queryInterval, experimentalFlags)
|
||||
}
|
||||
|
||||
if (fallbackCounter > 0) {
|
||||
fallbackCounter -= 1
|
||||
}
|
||||
|
||||
return result
|
||||
return if (canUseModern && useModern) modern.getForegroundApps(queryInterval, experimentalFlags)
|
||||
else legacy.getForegroundApps(queryInterval, experimentalFlags)
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||
*
|
||||
* 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 version 3 of the License.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.timelimit.android.integration.platform.android.foregroundapp.usagestats
|
||||
|
||||
import java.lang.RuntimeException
|
||||
|
||||
class MergedUsageStatsReader(private val primary: UsageStatsReader, private val confirmation: UsageStatsReader): UsageStatsReader {
|
||||
override val timestamp = primary.timestamp
|
||||
override val eventType = primary.eventType
|
||||
override val instanceId = primary.instanceId
|
||||
override val packageName = primary.packageName
|
||||
override val className = primary.className
|
||||
|
||||
override fun loadNextEvent(): Boolean {
|
||||
val didReadEvent = kotlin.run {
|
||||
val didReadPrimaryEvent = primary.loadNextEvent()
|
||||
val didReadConfirmationEvent = confirmation.loadNextEvent()
|
||||
|
||||
if (didReadConfirmationEvent != didReadPrimaryEvent) {
|
||||
throw NotMatchingDataException(
|
||||
if (didReadPrimaryEvent) "primary got next event but confirmation not"
|
||||
else "confirmation got next event but primary not"
|
||||
)
|
||||
}
|
||||
|
||||
didReadPrimaryEvent // == didReadNativeEvent
|
||||
}
|
||||
|
||||
if (!didReadEvent) return false
|
||||
|
||||
// check the consistency
|
||||
if (primary.eventType != confirmation.eventType) {
|
||||
throw NotMatchingDataException("got different eventTypes: ${primary.eventType} vs ${confirmation.eventType}")
|
||||
}
|
||||
|
||||
if (primary.timestamp != confirmation.timestamp) {
|
||||
throw NotMatchingDataException("got different timestamps: ${primary.timestamp} vs ${confirmation.timestamp}")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun free() {
|
||||
try {
|
||||
primary.free()
|
||||
} finally {
|
||||
confirmation.free()
|
||||
}
|
||||
}
|
||||
|
||||
class NotMatchingDataException(detail: String): RuntimeException("not matching data: $detail")
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||
*
|
||||
* 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 version 3 of the License.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.timelimit.android.integration.platform.android.foregroundapp.usagestats
|
||||
|
||||
import android.os.Parcel
|
||||
import java.io.FileInputStream
|
||||
|
||||
/**
|
||||
* This class implements a clone of Parcel.readBlob() as this is not a public
|
||||
* interface in all Android versions that support it.
|
||||
*
|
||||
* The wire format is (both as integer): length, useSharedMemory
|
||||
*
|
||||
* useSharedMemory can be 0 or 1
|
||||
*/
|
||||
object ParcelBlob {
|
||||
fun readBlob(parcel: Parcel): ByteArray {
|
||||
val length = parcel.readInt()
|
||||
|
||||
val useSharedMemory = when (parcel.readInt()) {
|
||||
0 -> false
|
||||
1 -> true
|
||||
else -> throw InvalidBooleanException()
|
||||
}
|
||||
|
||||
if (useSharedMemory) {
|
||||
val fd = parcel.readFileDescriptor()
|
||||
val result = ByteArray(length)
|
||||
|
||||
try {
|
||||
FileInputStream(fd.fileDescriptor).use { stream ->
|
||||
var cursor = 0
|
||||
|
||||
while (cursor < length) {
|
||||
val bytesRead = stream.read(result, cursor, length - cursor)
|
||||
|
||||
if (bytesRead == -1) {
|
||||
throw UnexpectedEndOfSharedMemory()
|
||||
} else {
|
||||
cursor += bytesRead
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fd.close()
|
||||
}
|
||||
|
||||
return result
|
||||
} else {
|
||||
val tempParcel = Parcel.obtain()
|
||||
val oldPosition = parcel.dataPosition()
|
||||
|
||||
try {
|
||||
tempParcel.appendFrom(parcel, oldPosition, length)
|
||||
|
||||
parcel.setDataPosition(oldPosition + length)
|
||||
|
||||
val result = tempParcel.marshall()
|
||||
|
||||
if (result.size != length) {
|
||||
throw WrongReturnedDataSize()
|
||||
}
|
||||
|
||||
return result
|
||||
} finally {
|
||||
tempParcel.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class ParcelBlobException: RuntimeException()
|
||||
class InvalidBooleanException: ParcelBlobException()
|
||||
class UnexpectedEndOfSharedMemory: ParcelBlobException()
|
||||
class WrongReturnedDataSize: ParcelBlobException()
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
|
||||
*
|
||||
* 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 version 3 of the License.
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.timelimit.android.integration.platform.android.foregroundapp.usagestats
|
||||
|
||||
import android.app.usage.UsageEvents
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import java.lang.RuntimeException
|
||||
|
||||
/**
|
||||
* This class is/should be a Parcel compatible clone of the UsageEvents
|
||||
* system class. This allows reading fields which the public API does not provide.
|
||||
*/
|
||||
class ParcelUsageStatsReader (private val content: Parcel): UsageStatsReader {
|
||||
companion object {
|
||||
private const val CONFIGURATION_CHANGE = 5
|
||||
private const val SHORTCUT_INVOCATION = 8
|
||||
private const val CHOOSER_ACTION = 9
|
||||
private const val STANDBY_BUCKET_CHANGED = 11
|
||||
private const val NOTIFICATION_INTERRUPTION = 12
|
||||
private const val LOCUS_ID_SET = 30
|
||||
private const val DUMMY_STRING = "null"
|
||||
|
||||
fun getParcel(input: UsageEvents): Parcel {
|
||||
if (Build.VERSION.SDK_INT > 32) {
|
||||
throw UntestedSystemVersionException()
|
||||
}
|
||||
|
||||
val outerParcel = Parcel.obtain()
|
||||
|
||||
val blob = try {
|
||||
input.writeToParcel(outerParcel, 0)
|
||||
outerParcel.setDataPosition(0)
|
||||
|
||||
ParcelBlob.readBlob(outerParcel)
|
||||
} finally {
|
||||
outerParcel.recycle()
|
||||
}
|
||||
|
||||
val innerParcel = Parcel.obtain()
|
||||
|
||||
try {
|
||||
innerParcel.unmarshall(blob, 0, blob.size)
|
||||
innerParcel.setDataPosition(0)
|
||||
|
||||
return innerParcel
|
||||
} catch (ex: Exception) {
|
||||
innerParcel.recycle()
|
||||
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
fun fromUsageEvents(input: UsageEvents): ParcelUsageStatsReader = ParcelUsageStatsReader(
|
||||
getParcel(input)
|
||||
)
|
||||
}
|
||||
|
||||
private var free = false
|
||||
private val length = content.readInt()
|
||||
private var index = content.readInt()
|
||||
private val strings = if (length > 0) content.createStringArray() else null
|
||||
|
||||
init {
|
||||
if (length > 0) {
|
||||
val listByteLength = content.readInt()
|
||||
val positionInParcel = content.readInt()
|
||||
|
||||
content.setDataPosition(content.dataPosition() + positionInParcel)
|
||||
content.setDataSize(content.dataPosition() + listByteLength)
|
||||
} else {
|
||||
free()
|
||||
}
|
||||
}
|
||||
|
||||
private var outputTimestamp = 0L
|
||||
private var outputEventType = 0
|
||||
private var outputInstanceId = 0
|
||||
private var outputPackageName = DUMMY_STRING
|
||||
private var outputClassName = DUMMY_STRING
|
||||
|
||||
override val timestamp get() = outputTimestamp
|
||||
override val eventType get() = outputEventType
|
||||
override val instanceId get() = outputInstanceId
|
||||
override val packageName get() = outputPackageName
|
||||
override val className get() = outputClassName
|
||||
|
||||
override fun loadNextEvent(): Boolean {
|
||||
if (free) return false
|
||||
if (strings == null) throw IllegalStateException()
|
||||
|
||||
val packageIndex = content.readInt()
|
||||
val classIndex = content.readInt()
|
||||
val instanceId = content.readInt()
|
||||
val taskRootPackageIndex = content.readInt()
|
||||
val taskRootClassIndex = content.readInt()
|
||||
val eventType = content.readInt()
|
||||
val timestamp = content.readLong()
|
||||
|
||||
when (eventType) {
|
||||
CONFIGURATION_CHANGE -> {
|
||||
val newConfiguration = Configuration.CREATOR.createFromParcel(content)
|
||||
}
|
||||
SHORTCUT_INVOCATION -> {
|
||||
val shortcutId = content.readString()
|
||||
}
|
||||
CHOOSER_ACTION -> {
|
||||
val action = content.readString()
|
||||
val contentType = content.readString()
|
||||
val contentAnnotations = content.createStringArray()
|
||||
}
|
||||
STANDBY_BUCKET_CHANGED -> {
|
||||
val bucketAndReason = content.readInt()
|
||||
}
|
||||
NOTIFICATION_INTERRUPTION -> {
|
||||
val notificationChannelId = content.readString()
|
||||
}
|
||||
LOCUS_ID_SET -> {
|
||||
val locusId = content.readString()
|
||||
}
|
||||
}
|
||||
val flags = content.readInt()
|
||||
|
||||
outputTimestamp = timestamp
|
||||
outputEventType = eventType
|
||||
outputInstanceId = instanceId
|
||||
outputPackageName = if (packageIndex == -1) DUMMY_STRING else strings[packageIndex]
|
||||
outputClassName = if(classIndex == -1) DUMMY_STRING else strings[classIndex]
|
||||
|
||||
index++; if (index == length) free()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun free() {
|
||||
if (!free) {
|
||||
content.recycle()
|
||||
|
||||
free = true
|
||||
}
|
||||
}
|
||||
|
||||
class UntestedSystemVersionException: RuntimeException("untested system version for the Parcel implementation")
|
||||
}
|
|
@ -201,12 +201,6 @@ data class DiagnoseExperimentalFlagItem(
|
|||
enableFlags = ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION,
|
||||
disableFlags = ExperimentalFlags.INSTANCE_ID_FG_APP_DETECTION,
|
||||
enable = { true }
|
||||
),
|
||||
DiagnoseExperimentalFlagItem(
|
||||
label = R.string.diagnose_exf_fda,
|
||||
enableFlags = ExperimentalFlags.DISABLE_FG_APP_DETECTION_FALLBACK,
|
||||
disableFlags = ExperimentalFlags.DISABLE_FG_APP_DETECTION_FALLBACK,
|
||||
enable = { true }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,14 +16,11 @@
|
|||
package io.timelimit.android.ui.diagnose
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.usage.UsageEvents
|
||||
import android.app.usage.UsageStatsManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.util.Base64
|
||||
import android.util.JsonWriter
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
|
@ -38,7 +35,7 @@ import io.timelimit.android.R
|
|||
import io.timelimit.android.async.Threads
|
||||
import io.timelimit.android.databinding.DiagnoseForegroundAppFragmentBinding
|
||||
import io.timelimit.android.integration.platform.android.foregroundapp.InstanceIdForegroundAppHelper
|
||||
import io.timelimit.android.integration.platform.android.foregroundapp.usagestats.ParcelUsageStatsReader
|
||||
import io.timelimit.android.integration.platform.android.foregroundapp.usagestats.DirectUsageStatsReader
|
||||
import io.timelimit.android.livedata.liveDataFromNonNullValue
|
||||
import io.timelimit.android.livedata.liveDataFromNullableValue
|
||||
import io.timelimit.android.livedata.map
|
||||
|
@ -53,7 +50,6 @@ import java.io.OutputStreamWriter
|
|||
class DiagnoseForegroundAppFragment : Fragment(), FragmentWithCustomTitle {
|
||||
companion object {
|
||||
private const val LOG_TAG = "DiagnoseForegroundApp"
|
||||
private const val REQ_EXPORT_BINARY = 1
|
||||
private const val REQ_EXPORT_TEXT = 2
|
||||
|
||||
private val buttonIntervals = listOf(
|
||||
|
@ -144,71 +140,13 @@ class DiagnoseForegroundAppFragment : Fragment(), FragmentWithCustomTitle {
|
|||
}
|
||||
}
|
||||
|
||||
binding.osUsageStatsBinaryExportButton.isEnabled = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||
binding.osUsageStatsBinaryExportButton.setOnClickListener {
|
||||
if (auth.requestAuthenticationOrReturnTrue()) {
|
||||
try {
|
||||
startActivityForResult(
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/octet-stream")
|
||||
.putExtra(Intent.EXTRA_TITLE, "timelimit-usage-stats-export.bin"),
|
||||
REQ_EXPORT_BINARY
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
Toast.makeText(context, R.string.error_general, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun getCustomTitle(): LiveData<String?> = liveDataFromNullableValue("${getString(R.string.diagnose_fga_title)} < ${getString(R.string.about_diagnose_title)} < ${getString(R.string.main_tab_overview)}")
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == REQ_EXPORT_BINARY) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val context = requireContext().applicationContext
|
||||
|
||||
Thread {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
throw RuntimeException("unsupported os version")
|
||||
}
|
||||
|
||||
try {
|
||||
val now = System.currentTimeMillis()
|
||||
val service = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
|
||||
val currentData = service.queryEvents(now - InstanceIdForegroundAppHelper.START_QUERY_INTERVAL, now)
|
||||
|
||||
val bytes = try {
|
||||
val parcel = ParcelUsageStatsReader.getParcel(currentData)
|
||||
try { parcel.marshall() } finally { parcel.recycle() }
|
||||
} finally {
|
||||
val event = UsageEvents.Event()
|
||||
|
||||
while (currentData.getNextEvent(event)) {/* consume all items */}
|
||||
}
|
||||
|
||||
context.contentResolver.openOutputStream(data!!.data!!)!!.use { stream ->
|
||||
stream.write(bytes)
|
||||
}
|
||||
|
||||
Threads.mainThreadHandler.post {
|
||||
Toast.makeText(context, R.string.diagnose_fga_export_toast_done, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOG_TAG, "could not do export", ex)
|
||||
}
|
||||
|
||||
Threads.mainThreadHandler.post {
|
||||
Toast.makeText(context, R.string.error_general, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
} else if (requestCode == REQ_EXPORT_TEXT) {
|
||||
if (requestCode == REQ_EXPORT_TEXT) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val context = requireContext().applicationContext
|
||||
|
||||
|
@ -221,115 +159,41 @@ class DiagnoseForegroundAppFragment : Fragment(), FragmentWithCustomTitle {
|
|||
val now = System.currentTimeMillis()
|
||||
val service = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
|
||||
val currentData = service.queryEvents(now - InstanceIdForegroundAppHelper.START_QUERY_INTERVAL, now)
|
||||
val event = UsageEvents.Event()
|
||||
val reader = DirectUsageStatsReader(currentData)
|
||||
|
||||
try {
|
||||
var nativeData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val parcel = ParcelUsageStatsReader.getParcel(currentData)
|
||||
val bytes = parcel.marshall()
|
||||
val reader = ParcelUsageStatsReader(parcel)
|
||||
|
||||
NativeEventReader(reader, bytes, parcel)
|
||||
} else null
|
||||
|
||||
try {
|
||||
JsonWriter(
|
||||
BufferedWriter(
|
||||
OutputStreamWriter(
|
||||
context.contentResolver.openOutputStream(
|
||||
data!!.data!!
|
||||
)!!
|
||||
)
|
||||
JsonWriter(
|
||||
BufferedWriter(
|
||||
OutputStreamWriter(
|
||||
context.contentResolver.openOutputStream(
|
||||
data!!.data!!
|
||||
)!!
|
||||
)
|
||||
).use { writer ->
|
||||
writer.setIndent(" ")
|
||||
)
|
||||
).use { writer ->
|
||||
writer.setIndent(" ")
|
||||
|
||||
writer.beginArray()
|
||||
|
||||
while (reader.loadNextEvent()) {
|
||||
writer.beginObject()
|
||||
|
||||
nativeData?.let { native ->
|
||||
writer.name("header").value(Base64.encodeToString(
|
||||
native.bytes, 0, native.parcel.dataPosition(), Base64.NO_WRAP or Base64.NO_PADDING
|
||||
))
|
||||
}
|
||||
writer.name("timestamp").value(reader.timestamp)
|
||||
writer.name("type").value(reader.eventType)
|
||||
writer.name("packageName").value(reader.packageName)
|
||||
writer.name("className").value(reader.className)
|
||||
|
||||
writer.name("events").also {
|
||||
writer.beginArray()
|
||||
val instanceId = try { reader.instanceId } catch (ex: Exception) { null }
|
||||
|
||||
while (currentData.getNextEvent(event)) {
|
||||
writer.beginObject()
|
||||
|
||||
writer.name("timestamp").value(event.timeStamp)
|
||||
writer.name("type").value(event.eventType)
|
||||
writer.name("packageName").value(event.packageName)
|
||||
writer.name("className").value(event.className)
|
||||
|
||||
nativeData?.let { native ->
|
||||
val positionBefore = native.parcel.dataPosition()
|
||||
|
||||
val result: ReadNextItemResult = try {
|
||||
if (native.events.loadNextEvent()) ReadNextItemResult.NextItem
|
||||
else ReadNextItemResult.EndOfData
|
||||
} catch (ex: Exception) {
|
||||
ReadNextItemResult.Error(ex)
|
||||
}
|
||||
|
||||
val positionAfter = native.parcel.dataPosition().let {
|
||||
if (it < positionBefore) native.bytes.size else it
|
||||
}
|
||||
|
||||
writer.name("custom")
|
||||
|
||||
when (result) {
|
||||
ReadNextItemResult.NextItem -> {
|
||||
writer.beginObject()
|
||||
|
||||
writer.name("timestamp").value(native.events.timestamp)
|
||||
writer.name("type").value(native.events.eventType)
|
||||
writer.name("instanceId").value(native.events.instanceId)
|
||||
writer.name("packageName").value(native.events.packageName)
|
||||
writer.name("className").value(native.events.className)
|
||||
writer.name("binary").value(Base64.encodeToString(
|
||||
native.bytes, positionBefore, positionAfter - positionBefore,
|
||||
Base64.NO_WRAP or Base64.NO_PADDING
|
||||
))
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
ReadNextItemResult.EndOfData -> {
|
||||
writer.value("end of data reached")
|
||||
|
||||
native.free()
|
||||
nativeData = null
|
||||
}
|
||||
is ReadNextItemResult.Error -> {
|
||||
writer.beginObject()
|
||||
|
||||
writer.name("error message").value("${result.err}")
|
||||
writer.name("remaining binary data").value(Base64.encodeToString(
|
||||
native.bytes, positionBefore, native.bytes.size - positionBefore,
|
||||
Base64.NO_WRAP or Base64.NO_PADDING
|
||||
))
|
||||
|
||||
writer.endObject()
|
||||
|
||||
native.free()
|
||||
nativeData = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
|
||||
writer.endArray()
|
||||
}
|
||||
if (instanceId != null) writer.name("instanceId").value(instanceId)
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
} finally {
|
||||
nativeData?.free()
|
||||
|
||||
writer.endArray()
|
||||
}
|
||||
} finally {
|
||||
while (currentData.getNextEvent(event)) {/* consume all items */}
|
||||
reader.free()
|
||||
}
|
||||
|
||||
Threads.mainThreadHandler.post {
|
||||
|
@ -348,14 +212,4 @@ class DiagnoseForegroundAppFragment : Fragment(), FragmentWithCustomTitle {
|
|||
}
|
||||
} else super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
internal class NativeEventReader(val events: ParcelUsageStatsReader, val bytes: ByteArray, val parcel: Parcel /* owned by TlUsageEvents */) {
|
||||
fun free() { events.free() }
|
||||
}
|
||||
|
||||
sealed class ReadNextItemResult {
|
||||
object EndOfData: ReadNextItemResult()
|
||||
object NextItem: ReadNextItemResult()
|
||||
class Error(val err: Exception): ReadNextItemResult()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,13 +71,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/os_usage_stats_binary_export_button"
|
||||
style="?materialButtonOutlinedStyle"
|
||||
android:text="@string/diagnose_fga_bin_export_btn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
|
|
|
@ -512,7 +512,6 @@
|
|||
<string name="diagnose_fga_title">Erkennung der aktiven App</string>
|
||||
<string name="diagnose_fga_query_range">Abfragezeitraum</string>
|
||||
<string name="diagnose_fga_query_range_min">Minimal (Standard)</string>
|
||||
<string name="diagnose_fga_bin_export_btn">binäre Nutzungsstatistik vom System exportieren</string>
|
||||
<string name="diagnose_fga_txt_export_btn">Nutzungsstatistik vom System als Text exportieren</string>
|
||||
<string name="diagnose_fga_export_toast_done">Export abgeschlossen</string>
|
||||
|
||||
|
@ -531,7 +530,6 @@
|
|||
<string name="diagnose_exf_esb">Overlay und Home-Button nicht zum Sperren verwenden</string>
|
||||
<string name="diagnose_exf_srn">Toasts zur Synchronisation anzeigen</string>
|
||||
<string name="diagnose_exf_ifd">neue App-Erkennungs-Methode verwenden</string>
|
||||
<string name="diagnose_exf_fda">Fallback bei der App-Erkennung deaktivieren</string>
|
||||
|
||||
<string name="diagnose_bg_task_loop_ex">Hintergrundaufgabenschleifenfehler</string>
|
||||
|
||||
|
|
|
@ -565,7 +565,6 @@
|
|||
<string name="diagnose_fga_title">Foreground-App-Detection</string>
|
||||
<string name="diagnose_fga_query_range">Requested time range</string>
|
||||
<string name="diagnose_fga_query_range_min">Minimum (Default)</string>
|
||||
<string name="diagnose_fga_bin_export_btn">Export binary OS usage stats</string>
|
||||
<string name="diagnose_fga_txt_export_btn">Export text OS usage stats</string>
|
||||
<string name="diagnose_fga_export_toast_done">Export finished</string>
|
||||
|
||||
|
@ -584,7 +583,6 @@
|
|||
<string name="diagnose_exf_esb">Do not use a overlay or the home button for blocking</string>
|
||||
<string name="diagnose_exf_srn">Show sync related toasts</string>
|
||||
<string name="diagnose_exf_ifd">Use new App detection method</string>
|
||||
<string name="diagnose_exf_fda">Disable fallback at the App detection</string>
|
||||
|
||||
<string name="diagnose_bg_task_loop_ex">Background task loop exception</string>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue