package app.grapheneos.gmscompat import android.Manifest import android.app.ActivityManager import android.content.Intent import android.content.pm.PackageManager import android.ext.PackageId import android.os.BinderDef import android.util.ArrayMap import android.util.ArraySet import android.util.Log import androidx.core.content.edit import app.grapheneos.gmscompat.App.MainProcessPrefs import app.grapheneos.gmscompat.location.GLocationService import com.android.internal.gmscompat.GmsInfo import com.android.internal.gmscompat.GmcBinderDefs.BinderDefStateListener enum class BinderDefGroup(val services: Array) { LOCATION(arrayOf(GLocationService.BinderDef())) } object BinderDefs { private val ifaceNameToGroup = ArrayMap().also { map -> BinderDefGroup.values().forEach { group -> group.services.forEach { map.put(it.ifaceName, group) } } } private val ifaceNameToBinderDef = ArrayMap().also { map -> BinderDefGroup.values().forEach { group -> group.services.forEach { map.put(it.ifaceName, it) } } } fun getFromIfaceNameIfEnabled(callerPkg: String, ifaceName: String): BinderDef? { if (isEnabled(callerPkg, ifaceName)) { return getFromIfaceName(callerPkg, ifaceName) } return null } fun getFromIfaceName(callerPkg: String, ifaceName: String): BinderDef { return ifaceNameToBinderDef.get(ifaceName)!!.get(App.ctx(), callerPkg) } private fun getGroupFromIfaceName(ifaceName: String): BinderDefGroup { return ifaceNameToGroup.get(ifaceName)!! } private fun isEnabled(callerPkg: String, ifaceName: String): Boolean { return isEnabled(getGroupFromIfaceName(ifaceName), callerPkg) } fun isEnabled(group: BinderDefGroup, callerPkg: String? = null): Boolean { val prefs = App.preferences() return when (group) { BinderDefGroup.LOCATION -> { if (callerPkg == PackageId.GMS_CORE_NAME || callerPkg == PackageId.ANDROID_AUTO_NAME) { if (App.ctx().packageManager.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, callerPkg) != PackageManager.PERMISSION_GRANTED) { // GmsCore and Android Auto crash in some cases when the GmsCore location // service is used without holding any location permission. // // Usage of the GmsCompat location service reimplementation avoid this issue. return true } } val k = MainProcessPrefs.LOCATION_REQUEST_REDIRECTION_ENABLED // getLong is used for compatibility with historical setting key val v = prefs.getLong(k, -1L) if (v == -1L) { val isEnabledByDefault = !gmsCoreHasPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) prefs.edit { putLong(k, if (isEnabledByDefault) 1 else 0) } return isEnabledByDefault } v == 1L } } } fun setEnabled(group: BinderDefGroup, enabled: Boolean) { App.preferences().edit { when (group) { BinderDefGroup.LOCATION -> // putLong is used for compatibility with historical setting key putLong(MainProcessPrefs.LOCATION_REQUEST_REDIRECTION_ENABLED, if (enabled) 1 else 0) } } val changedIfaceNames: Array = group.services.map { it.ifaceName }.toTypedArray() val intent = Intent(BinderDefStateListener.INTENT_ACTION).apply { putExtra(BinderDefStateListener.KEY_CHANGED_IFACE_NAMES, changedIfaceNames) } App.ctx().sendBroadcast(intent) } val binderDefs: Set = getIfaceNames(BinderDefGroup.LOCATION) val notableIfaceNames: Map = NotableInterface.values().associateBy { it.ifaceName } fun maybeGetBinderDef(callerPkg: String, processState: Int, ifaceName: String, isFromGms2Gca: Boolean): BinderDef? { // Note that the callerPkg value is not verified at this point, verification is delayed // until the time of first use to reduce perf impact val TAG = "maybeGetBinderDef" if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "caller $callerPkg processState ${ActivityManager.procStateToString(processState)} $ifaceName") } val notableIface = notableIfaceNames.get(ifaceName) if (notableIface != null) { verifyCallerPkg(callerPkg) App.bgExecutor().execute { notableIface.onAcquiredByClient(callerPkg, processState) } } if (binderDefs.contains(ifaceName)) { verifyCallerPkg(callerPkg) return getFromIfaceNameIfEnabled(callerPkg, ifaceName) } return null } fun getIfaceNames(vararg groups: BinderDefGroup): Set { val set = ArraySet() groups.forEach { it.services.forEach { set.add(it.ifaceName) } } return set } }