'Disable firebase logging for google ML Kit library in android
Could anyone help me to tell how can I disable the firebase logging in Google ML Kit library for android. For every 15 mins it will POST some information to https://firebaselogging.googleapis.com/v0cc/log/batch?format=json_proto3
I tried using the recommendation from google https://firebase.google.com/docs/perf-mon/disable-sdk?platform=android#kotlin+ktx and I am also not sure whether it is a right way.
Suggestions are welcome.
Solution 1:[1]
Google's guide from the question didn't work for me, so I have looked for alternatives.
The library is obfuscated, so it is hard to be sure, but it appears that the logging is hardcoded in. However, there is a very hacky way to disable it through some fragile reflection:
import android.util.Log
import com.google.mlkit.common.sdkinternal.LazyInstanceMap
import java.lang.reflect.Field
/**
* This class tries to disable MLKit's phoning home/logging.
* This is extremely hacky and will probably break in the next update (obfuscated class names will probably need renaming).
*
* This class exploits the fact, that there are multiple options classes which control this
* (look for "MLKitLoggingOptions" in toString implementation) and for some reason MLKit uses them as keys
* in LazyInstanceMaps which exist as static (usually) variables (which are themselves lazy).
*
* This makes sure that the LazyInstanceMaps exist, then it hijacks their internal HashMap implementation
* and replaces it with a custom map, that creates instances of whatever with logging disabled.
*
* The way to detect which holder classes need renaming, look at the stack trace, for example:
* ```
java.lang.NoClassDefFoundError: Failed resolution of: Lcom/google/android/datatransport/cct/CCTDestination;
at com.google.android.gms.internal.mlkit_vision_barcode.zznu.<init>(com.google.android.gms:play-services-mlkit-barcode-scanning@@18.0.0:1)
at com.google.android.gms.internal.mlkit_vision_barcode.zznf.<init>(com.google.android.gms:play-services-mlkit-barcode-scanning@@18.0.0:3)
at com.google.android.gms.internal.mlkit_vision_barcode.zznw.create(com.google.android.gms:play-services-mlkit-barcode-scanning@@18.0.0:4)
at com.google.mlkit.common.sdkinternal.LazyInstanceMap.get(com.google.mlkit:common@@18.0.0:3)
at com.google.android.gms.internal.mlkit_vision_barcode.zznx.zza(com.google.android.gms:play-services-mlkit-barcode-scanning@@18.0.0:2)
at com.google.android.gms.internal.mlkit_vision_barcode.zznx.zzb(com.google.android.gms:play-services-mlkit-barcode-scanning@@18.0.0:3)
at com.google.mlkit.vision.barcode.internal.zzf.create(com.google.android.gms:play-services-mlkit-barcode-scanning@@18.0.0:3)
at com.google.mlkit.common.sdkinternal.LazyInstanceMap.get(com.google.mlkit:common@@18.0.0:3)
at com.google.mlkit.vision.barcode.internal.zze.zzb(com.google.android.gms:play-services-mlkit-barcode-scanning@@18.0.0:2)
at com.google.mlkit.vision.barcode.BarcodeScanning.getClient(com.google.android.gms:play-services-mlkit-barcode-scanning@@18.0.0:3)
* ```
* here are two LazyInstanceMap lookups, of which only the second one (through trial and error or with debugger)
* uses MLKitLoggingOptions keys. From here we can find that the holder class is com.google.android.gms.internal.mlkit_vision_barcode.zznx .
*/
object MLKitTrickery {
private class mlkit_vision_barcodeLoggingOptions(base: com.google.android.gms.internal.mlkit_vision_barcode.zzne) : com.google.android.gms.internal.mlkit_vision_barcode.zzne() {
private val libraryName: String = base.zzb()
private val firelogEventType: Int = base.zza()
override fun zza(): Int = firelogEventType
override fun zzb(): String = libraryName
override fun zzc(): Boolean = false //enableFirelog
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as mlkit_vision_barcodeLoggingOptions
if (libraryName != other.libraryName) return false
if (firelogEventType != other.firelogEventType) return false
return true
}
override fun hashCode(): Int {
var result = libraryName.hashCode()
result = 31 * result + firelogEventType
return result
}
}
private class mlkit_vision_commonLoggingOptions(base: com.google.android.gms.internal.mlkit_vision_common.zzjn) : com.google.android.gms.internal.mlkit_vision_common.zzjn() {
private val libraryName: String = base.zzb()
private val firelogEventType: Int = base.zza()
override fun zza(): Int = firelogEventType
override fun zzb(): String = libraryName
override fun zzc(): Boolean = false //enableFirelog
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as mlkit_vision_commonLoggingOptions
if (libraryName != other.libraryName) return false
if (firelogEventType != other.firelogEventType) return false
return true
}
override fun hashCode(): Int {
var result = libraryName.hashCode()
result = 31 * result + firelogEventType
return result
}
}
private fun isMLKitLoggingOptions(obj: Any): Boolean {
return obj is com.google.android.gms.internal.mlkit_vision_barcode.zzne
|| obj is com.google.android.gms.internal.mlkit_vision_common.zzjn
}
private fun convertMLKitLoggingOptions(obj: Any): Any? {
if (obj is com.google.android.gms.internal.mlkit_vision_barcode.zzne) {
return mlkit_vision_barcodeLoggingOptions(obj)
}
if (obj is com.google.android.gms.internal.mlkit_vision_common.zzjn) {
return mlkit_vision_commonLoggingOptions(obj)
}
return null
}
@Suppress("UNCHECKED_CAST")
private fun patchLazyMap(lazyMapHolder:Any?, lazyMapHolderClass: Class<*>) {
val holderField = lazyMapHolderClass.declaredFields.find { LazyInstanceMap::class.java.isAssignableFrom(it.type) }!!
var currentLazyInstanceMap = holderField.get(lazyMapHolder)
if (currentLazyInstanceMap == null) {
var lastError: Throwable? = null
for (constructor in holderField.type.declaredConstructors) {
try {
constructor.isAccessible = true
val params = arrayOfNulls<Any?>(constructor.parameterCount)
currentLazyInstanceMap = constructor.newInstance(*params)
holderField.set(lazyMapHolder, currentLazyInstanceMap)
} catch (e:Throwable) {
lastError = e
}
}
if (currentLazyInstanceMap == null) {
throw java.lang.Exception("Failed to initialize LazyInstanceMap "+holderField.type, lastError)
}
}
var mapHolderClass: Class<*> = currentLazyInstanceMap.javaClass
val createMethod = mapHolderClass.getDeclaredMethod("create", Object::class.java)
val mapField: Field
while (true) {
val mapFieldCandidate = mapHolderClass.declaredFields.firstOrNull { Map::class.java.isAssignableFrom(it.type) }
if (mapFieldCandidate != null) {
mapField = mapFieldCandidate
break
}
mapHolderClass = mapHolderClass.superclass ?: error("It appears that ${currentLazyInstanceMap.javaClass} does not have a backing map field")
}
val oldMap = mapField.get(currentLazyInstanceMap) as MutableMap<Any, Any?>
val customMap = object : MutableMap<Any, Any?> by oldMap {
override fun containsKey(key: Any): Boolean {
if (oldMap.containsKey(key)) {
return true
}
if (isMLKitLoggingOptions(key)) {
return true
}
return false
}
override fun get(key: Any): Any? {
val existing = oldMap.get(key)
if (existing != null) {
return existing
}
val convertedKey = convertMLKitLoggingOptions(key)
if (convertedKey != null) {
val created = createMethod.invoke(currentLazyInstanceMap, convertedKey)
oldMap.put(key, created)
return created
}
return null
}
}
mapField.isAccessible = true
mapField.set(currentLazyInstanceMap, customMap)
}
private var initialized = false
/**
* Call this to attempt to disable MLKit logging.
*/
fun init() {
try {
patchLazyMap(null, com.google.android.gms.internal.mlkit_vision_barcode.zznx::class.java)
patchLazyMap(null, com.google.android.gms.internal.mlkit_vision_common.zzkc::class.java)
initialized = true
} catch (e: Throwable) {
Log.e("MLKitTrickery", "Failed to disable MLKit phoning home")
}
}
}
When you also shim out GMS TelemetryLogging with:
@file:Suppress("unused", "UNUSED_PARAMETER")
package com.google.android.gms.common.internal
import android.app.Activity
import android.content.Context
import android.os.Parcel
import com.google.android.gms.tasks.OnFailureListener
import com.google.android.gms.tasks.OnSuccessListener
import com.google.android.gms.tasks.Task
import java.util.concurrent.Executor
class TelemetryLoggingOptions {
class Builder {
fun setApi(api: String?): Builder = this
fun build(): TelemetryLoggingOptions = TelemetryLoggingOptions()
}
companion object {
@JvmStatic
fun builder(): Builder = Builder()
}
}
private object DummyLogTask : Task<Void?>() {
override fun addOnFailureListener(p0: OnFailureListener): Task<Void?> {
// Implemented, because failing tells MLKit to back-off for 30 minutes, which is a win for performance
p0.onFailure(exception)
return this
}
override fun addOnFailureListener(p0: Activity, p1: OnFailureListener): Task<Void?> = addOnFailureListener(p1)
override fun addOnFailureListener(p0: Executor, p1: OnFailureListener): Task<Void?> = addOnFailureListener(p1)
override fun addOnSuccessListener(p0: OnSuccessListener<in Void?>): Task<Void?> = this
override fun addOnSuccessListener(p0: Activity, p1: OnSuccessListener<in Void?>): Task<Void?> = addOnSuccessListener(p1)
override fun addOnSuccessListener(p0: Executor, p1: OnSuccessListener<in Void?>): Task<Void?> = addOnSuccessListener(p1)
override fun getException(): Exception? = exception
override fun getResult(): Void? = null
override fun <X : Throwable?> getResult(p0: Class<X>): Void? = null
override fun isCanceled(): Boolean = false
override fun isComplete(): Boolean = true
override fun isSuccessful(): Boolean = false
private val exception = Exception("Success was never an option")
}
object TelemetryLogging {
@JvmStatic
fun getClient(context: Context): TelemetryLoggingClient {
return object : TelemetryLoggingClient {
override fun log(data: TelemetryData): Task<Void?> {
return DummyLogTask
}
}
}
@JvmStatic
fun getClient(context: Context, options: TelemetryLoggingOptions): TelemetryLoggingClient {
return getClient(context)
}
}
interface TelemetryLoggingClient {
fun log(data: TelemetryData): Task<Void?>
}
class TelemetryData(var1: Int, var2:List<MethodInvocation>?) {
fun writeToParcel(var1: Parcel, var2: Int) {}
}
class MethodInvocation {
constructor(methodKey:Int, resultStatusCode:Int, connectionResultStatusCode:Int,
startTimeMillis:Long, endTimeMillis:Long,
callingModuleId: String?, callingEntryPoint: String?, serviceId:Int)
constructor(methodKey:Int, resultStatusCode:Int, connectionResultStatusCode:Int,
startTimeMillis:Long, endTimeMillis:Long,
callingModuleId: String?, callingEntryPoint: String?,
serviceId:Int, var11:Int)
fun writeToParcel(var1: Parcel, var2: Int) {}
}
it is possible to trim many transitive dependencies and save apk size:
implementation("com.google.mlkit:barcode-scanning:17.0.2") {
exclude("com.google.android.gms", "play-services-base")
exclude("com.google.android.datatransport", "transport-api")
exclude("com.google.android.datatransport", "transport-backend-cct")
exclude("com.google.android.datatransport", "transport-runtime")
exclude("com.google.firebase", "firebase-encoders-json")
exclude("com.google.firebase", "firebase-encoders")
}
However, as noted above, this is very fragile and will probably somehow break after MLKit update. It would be nice if this was not needed.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Darkyen |
