diff --git a/CHANGELOG.md b/CHANGELOG.md index 0482ce60f..64b08830d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Add Binder IPC call instrumentation ([#1159](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1159)) + ## 6.9.0 ### Fixes diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt index eb1b8ca06..864a4bbdc 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt @@ -196,6 +196,7 @@ fun ApplicationAndroidComponentsExtension.configure( extension.includeSourceContext, extension.dexguardEnabled, extension.tracingInstrumentation.appStart.enabled, + extension.tracingInstrumentation.binder.enabled, ) /** * We have to register SentryModulesService as a build event listener, so it will not be @@ -228,6 +229,7 @@ fun ApplicationAndroidComponentsExtension.configure( params.appStartEnabled.setDisallowChanges( extension.tracingInstrumentation.appStart.enabled ) + params.binderEnabled.setDisallowChanges(extension.tracingInstrumentation.binder.enabled) params.tmpDir.set( project.layout.buildDirectory.dir("sentry-logs/instrumentation/${variant.name}") ) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/BinderExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/BinderExtension.kt new file mode 100644 index 000000000..6d1c8fbcb --- /dev/null +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/BinderExtension.kt @@ -0,0 +1,15 @@ +package io.sentry.android.gradle.extensions + +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property + +open class BinderExtension @Inject constructor(objects: ObjectFactory) { + /** + * Enables or disables Binder IPC call instrumentation. Defaults to true. This requires + * sentry-android-core version TODO or above, and needs to be enabled. See + * https://docs.sentry.io/platforms/android/configuration/gradle/#tracing-auto-instrumentation for + * more details. + */ + val enabled: Property = objects.property(Boolean::class.java).convention(true) +} diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt index d51ca26f4..b8e2b32db 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/TracingInstrumentationExtension.kt @@ -71,6 +71,12 @@ open class TracingInstrumentationExtension @Inject constructor(objects: ObjectFa fun appStart(appStartExtensionAction: Action) { appStartExtensionAction.execute(appStart) } + + val binder: BinderExtension = objects.newInstance(BinderExtension::class.java) + + fun binder(binderAction: Action) { + binderAction.execute(binder) + } } enum class InstrumentationFeature(val integrationName: String) { diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt index fd4699f9c..66475fd77 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt @@ -13,6 +13,7 @@ import io.sentry.android.gradle.instrumentation.androidx.sqlite.database.Android import io.sentry.android.gradle.instrumentation.androidx.sqlite.statement.AndroidXSQLiteStatement import io.sentry.android.gradle.instrumentation.appstart.Application import io.sentry.android.gradle.instrumentation.appstart.ContentProvider +import io.sentry.android.gradle.instrumentation.binder.Binder import io.sentry.android.gradle.instrumentation.logcat.Logcat import io.sentry.android.gradle.instrumentation.logcat.LogcatLevel import io.sentry.android.gradle.instrumentation.okhttp.OkHttp @@ -63,6 +64,8 @@ abstract class SpanAddingClassVisitorFactory : @get:Input val logcatEnabled: Property @get:Input val appStartEnabled: Property + + @get:Input val binderEnabled: Property } private val instrumentable: ClassInstrumentable @@ -106,6 +109,7 @@ abstract class SpanAddingClassVisitorFactory : RemappingInstrumentable().takeIf { sentryModulesService.isFileIOInstrEnabled() }, ComposeNavigation().takeIf { sentryModulesService.isComposeInstrEnabled() }, Logcat().takeIf { sentryModulesService.isLogcatInstrEnabled() }, + Binder().takeIf { sentryModulesService.isBinderInstrEnabled() }, Application().takeIf { sentryModulesService.isAppStartInstrEnabled() }, ContentProvider().takeIf { sentryModulesService.isAppStartInstrEnabled() }, ) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/Binder.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/Binder.kt new file mode 100644 index 000000000..af95257ca --- /dev/null +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/Binder.kt @@ -0,0 +1,32 @@ +package io.sentry.android.gradle.instrumentation.binder + +import com.android.build.api.instrumentation.ClassContext +import io.sentry.android.gradle.instrumentation.ClassInstrumentable +import io.sentry.android.gradle.instrumentation.CommonClassVisitor +import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory +import io.sentry.android.gradle.instrumentation.util.isSentryClass +import org.objectweb.asm.ClassVisitor + +class Binder : ClassInstrumentable { + + companion object { + private const val CLASSNAME = "Binder" + } + + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): ClassVisitor { + return CommonClassVisitor( + apiVersion, + originalVisitor, + CLASSNAME, + listOf(BinderMethodInstrumentable()), + parameters, + ) + } + + override fun isInstrumentable(data: ClassContext) = !data.isSentryClass() +} diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodRegistry.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodRegistry.kt new file mode 100644 index 000000000..6354b934d --- /dev/null +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodRegistry.kt @@ -0,0 +1,2099 @@ +package io.sentry.android.gradle.instrumentation.binder + +data class BinderMethodSpec( + val owner: String, + val name: String, + val component: String, + val isStatic: Boolean = false, +) + +object BinderMethodRegistry { + + private val registry: Map> = buildRegistry() + + fun lookup(owner: String, name: String): BinderMethodSpec? { + val specs = registry[owner] ?: return null + return specs.find { it.name == name } + } + + @Suppress("LongMethod") + private fun buildRegistry(): Map> { + val specs = mutableListOf() + + // region content & package management + + specs.addAll( + "android/content/ContentResolver", + "ContentResolver", + "query", + "insert", + "update", + "delete", + "call", + "bulkInsert", + "openInputStream", + "openOutputStream", + "openAssetFileDescriptor", + "openFileDescriptor", + "acquireContentProviderClient", + "registerContentObserver", + "getType", + ) + + specs.addAll( + "android/content/pm/PackageManager", + "PackageManager", + "getInstalledPackages", + "getPackageInfo", + "resolveActivity", + "queryIntentActivities", + "getInstalledApplications", + "resolveService", + "queryIntentServices", + "getApplicationInfo", + "getActivityInfo", + "getServiceInfo", + "getReceiverInfo", + "getProviderInfo", + "checkPermission", + "hasSystemFeature", + "getLaunchIntentForPackage", + "getComponentEnabledSetting", + "setComponentEnabledSetting", + "getPackagesForUid", + "getInstallerPackageName", + "getInstallSourceInfo", + ) + + for (settingsClass in + listOf( + "android/provider/Settings\$Secure", + "android/provider/Settings\$Global", + "android/provider/Settings\$System", + )) { + val component = "Settings." + settingsClass.substringAfterLast("\$") + for (method in listOf("getString", "getInt", "getLong", "getFloat", "putString", "putInt")) { + specs.add(BinderMethodSpec(settingsClass, method, component, isStatic = true)) + } + } + + for (ctx in listOf("android/content/Context", "android/content/ContextWrapper")) { + specs.addAll( + ctx, + "Context", + "startService", + "stopService", + "bindService", + "unbindService", + "sendBroadcast", + "sendOrderedBroadcast", + "startActivity", + "startActivities", + "startForegroundService", + "registerReceiver", + "unregisterReceiver", + "checkSelfPermission", + "checkPermission", + ) + } + + specs.addAll( + "android/content/ClipboardManager", + "ClipboardManager", + "setPrimaryClip", + "clearPrimaryClip", + "getPrimaryClip", + "getPrimaryClipDescription", + "hasPrimaryClip", + "addPrimaryClipChangedListener", + "removePrimaryClipChangedListener", + "hasText", + ) + + specs.addAll( + "android/content/pm/LauncherApps", + "LauncherApps", + "getProfiles", + "getActivityList", + "resolveActivity", + "startMainActivity", + "startPackageInstallerSessionDetailsActivity", + "startAppDetailsActivity", + "getShortcutIntent", + "getShortcutConfigActivityList", + "getShortcutConfigActivityIntent", + "isPackageEnabled", + "getSuspendedPackageLauncherExtras", + "shouldHideFromSuggestions", + "getApplicationInfo", + "isActivityEnabled", + "hasShortcutHostPermission", + "getShortcuts", + "pinShortcuts", + "startShortcut", + "registerCallback", + "unregisterCallback", + "setArchiveCompatibility", + "registerPackageInstallerSessionCallback", + "getAllPackageInstallerSessions", + ) + specs.addAll( + "android/content/pm/LauncherApps\$PinItemRequest", + "PinItemRequest", + "getShortcutInfo", + "getAppWidgetProviderInfo", + "getExtras", + "isValid", + "accept", + ) + + specs.addAll( + "android/content/pm/ShortcutManager", + "ShortcutManager", + "setDynamicShortcuts", + "getDynamicShortcuts", + "getManifestShortcuts", + "getShortcuts", + "addDynamicShortcuts", + "removeDynamicShortcuts", + "removeAllDynamicShortcuts", + "removeLongLivedShortcuts", + "getPinnedShortcuts", + "updateShortcuts", + "disableShortcuts", + "enableShortcuts", + "getMaxShortcutCountPerActivity", + "isRateLimitingActive", + "getIconMaxWidth", + "getIconMaxHeight", + "reportShortcutUsed", + "isRequestPinShortcutSupported", + "requestPinShortcut", + "createShortcutResultIntent", + "pushDynamicShortcut", + ) + + specs.addAll( + "android/content/RestrictionsManager", + "RestrictionsManager", + "getApplicationRestrictions", + "getApplicationRestrictionsPerAdmin", + "hasRestrictionsProvider", + "requestPermission", + "createLocalApprovalIntent", + "notifyPermissionResponse", + ) + + specs.addAll("android/content/om/OverlayManager", "OverlayManager", "commit") + + specs.addAll( + "android/content/pm/PackageInstaller", + "PackageInstaller", + "createSession", + "openSession", + "updateSessionAppIcon", + "updateSessionAppLabel", + "abandonSession", + "getSessionInfo", + "getAllSessions", + "getMySessions", + "getStagedSessions", + "uninstall", + "installExistingPackage", + "uninstallExistingPackage", + "installPackageArchived", + "registerSessionCallback", + "unregisterSessionCallback", + "checkInstallConstraints", + "waitForInstallConstraints", + "commitSessionAfterInstallConstraintsAreMet", + "requestArchive", + "requestUnarchive", + "reportUnarchivalStatus", + "reportUnarchivalState", + ) + specs.addAll( + "android/content/pm/PackageInstaller\$Session", + "PackageInstaller.Session", + "setStagingProgress", + "openWrite", + "getNames", + "openRead", + "removeSplit", + "setChecksums", + "requestChecksums", + "commit", + "transfer", + "close", + "abandon", + "isMultiPackage", + "isStaged", + "getParentSessionId", + "getChildSessionIds", + "addChildSessionId", + "removeChildSessionId", + "getAppMetadata", + "setAppMetadata", + "requestUserPreapproval", + "isApplicationEnabledSettingPersistent", + "isRequestUpdateOwnership", + ) + + specs.addAll( + "android/content/pm/CrossProfileApps", + "CrossProfileApps", + "startMainActivity", + "startActivity", + "getTargetUserProfiles", + "isProfile", + "isManagedProfile", + "getProfileSwitchingLabel", + "getProfileSwitchingIconDrawable", + "canRequestInteractAcrossProfiles", + "canInteractAcrossProfiles", + "createRequestInteractAcrossProfilesIntent", + ) + + // endregion + + // region activity, jobs & app lifecycle + + specs.addAll( + "android/app/ActivityManager", + "ActivityManager", + "addAppTask", + "addApplicationStartInfoCompletionListener", + "addStartInfoTimestamp", + "appNotResponding", + "clearApplicationUserData", + "clearWatchHeapLimit", + "getAppTaskThumbnailSize", + "getAppTasks", + "getDeviceConfigurationInfo", + "getHistoricalProcessExitReasons", + "getHistoricalProcessStartReasons", + "getLockTaskModeState", + "getMemoryInfo", + "getMyMemoryState", + "getProcessMemoryInfo", + "getProcessesInErrorState", + "getRecentTasks", + "getRunningAppProcesses", + "getRunningServiceControlPanel", + "getRunningServices", + "getRunningTasks", + "isActivityStartAllowedOnDisplay", + "isBackgroundRestricted", + "isInLockTaskMode", + "isLowMemoryKillReportSupported", + "isRunningInTestHarness", + "isRunningInUserTestHarness", + "isUserAMonkey", + "killBackgroundProcesses", + "moveTaskToFront", + "removeApplicationStartInfoCompletionListener", + "restartPackage", + "setProcessStateSummary", + "setVrThread", + "setWatchHeapLimit", + ) + + specs.addAll( + "android/app/job/JobScheduler", + "JobScheduler", + "schedule", + "enqueue", + "cancel", + "cancelAll", + "cancelInAllNamespaces", + "getAllPendingJobs", + "getPendingJobsInAllNamespaces", + "getPendingJob", + "getPendingJobReason", + "getPendingJobReasons", + "getPendingJobReasonsHistory", + ) + + specs.addAll( + "android/app/usage/UsageStatsManager", + "UsageStatsManager", + "queryUsageStats", + "queryConfigurations", + "queryEventStats", + "queryEvents", + "queryEventsForSelf", + "queryAndAggregateUsageStats", + "isAppInactive", + "getAppStandbyBucket", + ) + + specs.addAll( + "android/app/usage/StorageStatsManager", + "StorageStatsManager", + "getTotalBytes", + "getFreeBytes", + "queryStatsForPackage", + "queryStatsForUid", + "queryStatsForUser", + "queryExternalStatsForUser", + ) + + specs.addAll( + "android/app/people/PeopleManager", + "PeopleManager", + "addOrUpdateStatus", + "clearStatus", + "clearStatuses", + "getStatuses", + ) + + specs.addAll("android/app/PendingIntent", "PendingIntent", "send") + + // endregion + + // region notifications, alarms & background scheduling + + specs.addAll( + "android/app/NotificationManager", + "NotificationManager", + "notify", + "notifyAsPackage", + "cancel", + "cancelAll", + "setNotificationDelegate", + "getNotificationDelegate", + "canNotifyAsPackage", + "canUseFullScreenIntent", + "canPostPromotedNotifications", + "createNotificationChannelGroup", + "createNotificationChannelGroups", + "createNotificationChannel", + "createNotificationChannels", + "getNotificationChannel", + "getNotificationChannels", + "deleteNotificationChannel", + "getNotificationChannelGroup", + "getNotificationChannelGroups", + "deleteNotificationChannelGroup", + "getConsolidatedNotificationPolicy", + "areAutomaticZenRulesUserManaged", + "getAutomaticZenRules", + "getAutomaticZenRule", + "addAutomaticZenRule", + "updateAutomaticZenRule", + "getAutomaticZenRuleState", + "setAutomaticZenRuleState", + "removeAutomaticZenRule", + "getImportance", + "areNotificationsEnabled", + "areBubblesAllowed", + "areBubblesEnabled", + "getBubblePreference", + "areNotificationsPaused", + "isNotificationPolicyAccessGranted", + "isNotificationListenerAccessGranted", + "shouldHideSilentStatusBarIcons", + "getNotificationPolicy", + "setNotificationPolicy", + "getActiveNotifications", + "getCurrentInterruptionFilter", + "setInterruptionFilter", + ) + + specs.addAll( + "android/app/AlarmManager", + "AlarmManager", + "set", + "setRepeating", + "setWindow", + "setExact", + "setAlarmClock", + "setInexactRepeating", + "setAndAllowWhileIdle", + "setExactAndAllowWhileIdle", + "cancel", + "cancelAll", + "setTime", + "setTimeZone", + "canScheduleExactAlarms", + "getNextAlarmClock", + ) + + specs.addAll( + "android/app/blob/BlobStoreManager", + "BlobStoreManager", + "createSession", + "openSession", + "abandonSession", + "openBlob", + "acquireLease", + "releaseLease", + "getRemainingLeaseQuotaBytes", + "getLeasedBlobs", + ) + specs.addAll( + "android/app/blob/BlobStoreManager\$Session", + "BlobStoreManager.Session", + "openWrite", + "openRead", + "getSize", + "close", + "abandon", + "allowPackageAccess", + "isPackageAccessAllowed", + "allowSameSignatureAccess", + "isSameSignatureAccessAllowed", + "allowPublicAccess", + "isPublicAccessAllowed", + "commit", + ) + + // endregion + + // region accounts, credentials & permissions + + specs.addAll( + "android/accounts/AccountManager", + "AccountManager", + "getAccounts", + "getPassword", + "getAuthenticatorTypes", + "getAccountsByTypeForPackage", + "getAccountsByType", + "hasFeatures", + "getAccountsByTypeAndFeatures", + "addAccountExplicitly", + "getPackagesAndVisibilityForAccount", + "getAccountsAndVisibilityForPackage", + "setAccountVisibility", + "getAccountVisibility", + "notifyAccountAuthenticated", + "renameAccount", + "getPreviousName", + "removeAccount", + "removeAccountExplicitly", + "invalidateAuthToken", + "peekAuthToken", + "setPassword", + "clearPassword", + "setUserData", + "setAuthToken", + "blockingGetAuthToken", + "getAuthToken", + "addAccount", + "confirmCredentials", + "updateCredentials", + "editProperties", + "getAuthTokenByFeatures", + "addOnAccountsUpdatedListener", + "removeOnAccountsUpdatedListener", + "startAddAccountSession", + "startUpdateCredentialsSession", + "finishSession", + "isCredentialsUpdateSuggested", + ) + + specs.addAll( + "android/credentials/CredentialManager", + "CredentialManager", + "getCredential", + "prepareGetCredential", + "createCredential", + "clearCredentialState", + "isEnabledCredentialProviderService", + "registerCredentialDescription", + "unregisterCredentialDescription", + ) + + specs.addAll( + "android/app/AppOpsManager", + "AppOpsManager", + "checkOp", + "unsafeCheckOp", + "checkOpNoThrow", + "unsafeCheckOpNoThrow", + "checkOpRawNoThrow", + "unsafeCheckOpRaw", + "unsafeCheckOpRawNoThrow", + "noteOp", + "noteOpNoThrow", + "noteProxyOp", + "noteProxyOpNoThrow", + "startOp", + "startOpNoThrow", + "startProxyOp", + "startProxyOpNoThrow", + "finishOp", + "finishProxyOp", + "isOpActive", + "checkPackage", + "startWatchingMode", + "stopWatchingMode", + "startWatchingActive", + "stopWatchingActive", + "setOnOpNotedCallback", + ) + + specs.addAll("android/app/role/RoleManager", "RoleManager", "isRoleAvailable", "isRoleHeld") + + // endregion + + // region device policy & enterprise + + specs.addAll( + "android/app/admin/DevicePolicyManager", + "DevicePolicyManager", + "acknowledgeDeviceCompliant", + "addCrossProfileIntentFilter", + "addCrossProfileWidgetProvider", + "addOverrideApn", + "addPersistentPreferredActivity", + "addUserRestriction", + "addUserRestrictionGlobally", + "bindDeviceAdminServiceAsUser", + "canAdminGrantSensorsPermissions", + "canUsbDataSignalingBeDisabled", + "clearApplicationUserData", + "clearCrossProfileIntentFilters", + "clearDeviceOwnerApp", + "clearPackagePersistentPreferredActivities", + "clearProfileOwner", + "clearResetPasswordToken", + "clearUserRestriction", + "createAdminSupportIntent", + "enableSystemApp", + "generateKeyPair", + "getApplicationRestrictionsManagingPackage", + "getAutoTimeEnabled", + "getAutoTimeRequired", + "getAutoTimeZoneEnabled", + "getBluetoothContactSharingDisabled", + "getCameraDisabled", + "getCrossProfileCallerIdDisabled", + "getCrossProfileContactsSearchDisabled", + "getCurrentFailedPasswordAttempts", + "getDeviceOwnerComponentOnAnyUser", + "getDeviceOwnerLockScreenInfo", + "getDeviceOwnerNameOnAnyUser", + "getEndUserSessionMessage", + "getGlobalPrivateDnsMode", + "getManagedProfileMaximumTimeOff", + "getManagedSubscriptionsPolicy", + "getMaximumFailedPasswordsForWipe", + "getMaximumTimeToLock", + "getOverrideApns", + "getPasswordComplexity", + "getPasswordExpiration", + "getPasswordExpirationTimeout", + "getPasswordHistoryLength", + "getPasswordMinimumLength", + "getPasswordMinimumLetters", + "getPasswordMinimumLowerCase", + "getPasswordMinimumNonLetter", + "getPasswordMinimumNumeric", + "getPasswordMinimumSymbols", + "getPasswordMinimumUpperCase", + "getPasswordQuality", + "getPermissionPolicy", + "getRequiredPasswordComplexity", + "getRequiredStrongAuthTimeout", + "getScreenCaptureDisabled", + "getSecondaryUsers", + "getShortSupportMessage", + "getStartUserSessionMessage", + "getStorageEncryption", + "getStorageEncryptionStatus", + "getSubscriptionIds", + "getTransferOwnershipBundle", + "getUserControlDisabledPackages", + "getUserProvisioningState", + "getWifiSsidPolicy", + "grantKeyPairToApp", + "grantKeyPairToWifiAuth", + "hasCaCertInstalled", + "hasGrantedPolicy", + "hasKeyPair", + "hasLockdownAdminConfiguredNetworks", + "installCaCert", + "installExistingPackage", + "installKeyPair", + "installSystemUpdate", + "isActivePasswordSufficient", + "isActivePasswordSufficientForDeviceRequirement", + "isAdminActive", + "isAffiliatedUser", + "isAlwaysOnVpnLockdownEnabled", + "isApplicationHidden", + "isBackupServiceEnabled", + "isCallerApplicationRestrictionsManagingPackage", + "isCommonCriteriaModeEnabled", + "isComplianceAcknowledgementRequired", + "isDeviceFinanced", + "isDeviceOwnerApp", + "isEphemeralUser", + "isFinancedDevice", + "isKeyPairGrantedToWifiAuth", + "isLockTaskPermitted", + "isLogoutEnabled", + "isManagedKiosk", + "isManagedProfile", + "isMasterVolumeMuted", + "isOverrideApnEnabled", + "isPackageAllowedToAccessCalendar", + "isPackageSuspended", + "isProfileOwnerApp", + "isProvisioningAllowed", + "isResetPasswordTokenActive", + "isSafeOperation", + "isSecurityLoggingEnabled", + "isStatusBarDisabled", + "isUnattendedManagedKiosk", + "isUninstallBlocked", + "isUsbDataSignalingEnabled", + "isUsingUnifiedPassword", + "listForegroundAffiliatedUsers", + "lockNow", + "provisionFullyManagedDevice", + "reboot", + "removeActiveAdmin", + "removeCrossProfileWidgetProvider", + "removeKeyPair", + "removeOverrideApn", + "removeUser", + "requestBugreport", + "resetPassword", + "resetPasswordWithToken", + "revokeKeyPairFromApp", + "revokeKeyPairFromWifiAuth", + "setAccountManagementDisabled", + "setActiveAdmin", + "setAffiliationIds", + "setAlwaysOnVpnPackage", + "setAppFunctionsPolicy", + "setApplicationHidden", + "setApplicationRestrictions", + "setApplicationRestrictionsManagingPackage", + "setAutoTimeEnabled", + "setAutoTimePolicy", + "setAutoTimeRequired", + "setAutoTimeZoneEnabled", + "setAutoTimeZonePolicy", + "setBackupServiceEnabled", + "setBluetoothContactSharingDisabled", + "setCameraDisabled", + "setCertInstallerPackage", + "setCommonCriteriaModeEnabled", + "setConfiguredNetworksLockdownState", + "setContentProtectionPolicy", + "setCredentialManagerPolicy", + "setCrossProfileCalendarPackages", + "setCrossProfileCallerIdDisabled", + "setCrossProfileContactsSearchDisabled", + "setCrossProfilePackages", + "setDefaultDialerApplication", + "setDefaultSmsApplication", + "setDeviceOwnerLockScreenInfo", + "setEndUserSessionMessage", + "setFactoryResetProtectionPolicy", + "setGlobalSetting", + "setKeepUninstalledPackages", + "setKeyPairCertificate", + "setKeyguardDisabled", + "setKeyguardDisabledFeatures", + "setLocationEnabled", + "setLockTaskFeatures", + "setLockTaskPackages", + "setLogoutEnabled", + "setLongSupportMessage", + "setManagedProfileCallerIdAccessPolicy", + "setManagedProfileContactsAccessPolicy", + "setManagedProfileMaximumTimeOff", + "setManagedSubscriptionsPolicy", + "setMasterVolumeMuted", + "setMaximumFailedPasswordsForWipe", + "setMaximumTimeToLock", + "setMinimumRequiredWifiSecurityLevel", + "setMtePolicy", + "setNearbyAppStreamingPolicy", + "setNearbyNotificationStreamingPolicy", + "setNetworkLoggingEnabled", + "setOrganizationColor", + "setOrganizationId", + "setOrganizationName", + "setOverrideApnsEnabled", + "setPackagesSuspended", + "setPasswordExpirationTimeout", + "setPasswordHistoryLength", + "setPasswordMinimumLength", + "setPasswordMinimumLetters", + "setPasswordMinimumLowerCase", + "setPasswordMinimumNonLetter", + "setPasswordMinimumNumeric", + "setPasswordMinimumSymbols", + "setPasswordMinimumUpperCase", + "setPasswordQuality", + "setPermissionGrantState", + "setPermissionPolicy", + "setPermittedAccessibilityServices", + "setPermittedCrossProfileNotificationListeners", + "setPermittedInputMethods", + "setPersonalAppsSuspended", + "setPreferentialNetworkServiceConfigs", + "setPreferentialNetworkServiceEnabled", + "setProfileEnabled", + "setProfileName", + "setProfileOwnerCanAccessDeviceIds", + "setProfileOwnerOnOrganizationOwnedDevice", + "setRecommendedGlobalProxy", + "setRequiredPasswordComplexity", + "setRequiredStrongAuthTimeout", + "setResetPasswordToken", + "setRestrictionsProvider", + "setScreenCaptureDisabled", + "setSecureSetting", + "setSecurityLoggingEnabled", + "setShortSupportMessage", + "setStartUserSessionMessage", + "setStatusBarDisabled", + "setStorageEncryption", + "setSystemSetting", + "setSystemUpdatePolicy", + "setTime", + "setTimeZone", + "setTrustAgentConfiguration", + "setUninstallBlocked", + "setUsbDataSignalingEnabled", + "setUserControlDisabledPackages", + "setUserIcon", + "setWifiSsidPolicy", + "switchUser", + "transferOwnership", + "uninstallAllUserCaCerts", + "uninstallCaCert", + "updateOverrideApn", + "wipeData", + "wipeDevice", + ) + + // endregion + + // region UI, launcher & companion + + specs.addAll( + "android/app/KeyguardManager", + "KeyguardManager", + "isKeyguardLocked", + "isKeyguardSecure", + "inKeyguardRestrictedInputMode", + "isDeviceLocked", + "isDeviceSecure", + "requestDismissKeyguard", + "exitKeyguardSecurely", + "addKeyguardLockedStateListener", + "removeKeyguardLockedStateListener", + "addDeviceLockedStateListener", + "removeDeviceLockedStateListener", + ) + specs.addAll( + "android/app/KeyguardManager\$KeyguardLock", + "KeyguardManager.KeyguardLock", + "disableKeyguard", + "reenableKeyguard", + ) + + specs.addAll( + "android/app/SearchManager", + "SearchManager", + "getGlobalSearchActivity", + "getSearchableInfo", + "getSearchablesInGlobalSearch", + ) + + specs.addAll( + "android/app/UiModeManager", + "UiModeManager", + "enableCarMode", + "disableCarMode", + "getCurrentModeType", + "setNightMode", + "setApplicationNightMode", + "getNightMode", + "getCustomNightModeStart", + "setCustomNightModeStart", + "getCustomNightModeEnd", + "setCustomNightModeEnd", + "getContrast", + "addContrastChangeListener", + "removeContrastChangeListener", + ) + + specs.addAll("android/app/GameManager", "GameManager", "getGameMode", "setGameState") + + specs.addAll( + "android/app/LocaleManager", + "LocaleManager", + "setApplicationLocales", + "getApplicationLocales", + "getSystemLocales", + "setOverrideLocaleConfig", + "getOverrideLocaleConfig", + ) + + specs.addAll( + "android/app/GrammaticalInflectionManager", + "GrammaticalInflectionManager", + "setRequestedApplicationGrammaticalGender", + "getSystemGrammaticalGender", + ) + + specs.addAll( + "android/app/StatusBarManager", + "StatusBarManager", + "requestAddTileService", + "canLaunchCaptureContentActivityForNote", + ) + + specs.addAll( + "android/appwidget/AppWidgetManager", + "AppWidgetManager", + "updateAppWidget", + "updateAppWidgetOptions", + "getAppWidgetOptions", + "partiallyUpdateAppWidget", + "updateAppWidgetProviderInfo", + "notifyAppWidgetViewDataChanged", + "getInstalledProvidersForProfile", + "getInstalledProvidersForPackage", + "getInstalledProviders", + "getAppWidgetInfo", + "bindAppWidgetIdIfAllowed", + "getAppWidgetIds", + "isRequestPinAppWidgetSupported", + "requestPinAppWidget", + "setWidgetPreview", + "getWidgetPreview", + "removeWidgetPreview", + ) + + specs.addAll( + "android/companion/CompanionDeviceManager", + "CompanionDeviceManager", + "associate", + "buildAssociationCancellationIntent", + "enableSystemDataSyncForTypes", + "disableSystemDataSyncForTypes", + "getMyAssociations", + "disassociate", + "requestNotificationAccess", + "hasNotificationAccess", + "removeBond", + "startObservingDevicePresence", + "stopObservingDevicePresence", + "attachSystemDataTransport", + "detachSystemDataTransport", + "buildPermissionTransferUserConsentIntent", + "isPermissionTransferUserConsented", + "startSystemDataTransfer", + "setDeviceId", + ) + + specs.addAll( + "android/companion/virtual/VirtualDeviceManager", + "VirtualDeviceManager", + "getVirtualDevices", + "getVirtualDevice", + "registerVirtualDeviceListener", + "unregisterVirtualDeviceListener", + ) + + specs.addAll( + "android/app/WallpaperManager", + "WallpaperManager", + "getDrawable", + "getBuiltInDrawable", + "peekDrawable", + "getFastDrawable", + "peekFastDrawable", + "getWallpaperFile", + "addOnColorsChangedListener", + "removeOnColorsChangedListener", + "getWallpaperColors", + "getWallpaperInfo", + "getWallpaperId", + "setResource", + "setBitmap", + "setStream", + "hasResourceWallpaper", + "getDesiredMinimumWidth", + "getDesiredMinimumHeight", + "suggestDesiredDimensions", + "setDisplayPadding", + "clearWallpaper", + "setWallpaperOffsets", + "sendWallpaperCommand", + "isWallpaperSupported", + "isSetWallpaperAllowed", + "clearWallpaperOffsets", + "clear", + ) + + // endregion + + // region connectivity + + specs.addAll( + "android/net/ConnectivityManager", + "ConnectivityManager", + "getActiveNetworkInfo", + "getActiveNetwork", + "getNetworkInfo", + "getAllNetworkInfo", + "getAllNetworks", + "getLinkProperties", + "getNetworkCapabilities", + "startUsingNetworkFeature", + "stopUsingNetworkFeature", + "requestRouteToHost", + "addDefaultNetworkActiveListener", + "removeDefaultNetworkActiveListener", + "isDefaultNetworkActive", + "reportBadNetwork", + "reportNetworkConnectivity", + "getDefaultProxy", + "isActiveNetworkMetered", + "requestNetwork", + "reserveNetwork", + "releaseNetworkRequest", + "registerNetworkCallback", + "registerDefaultNetworkCallback", + "registerBestMatchingNetworkCallback", + "requestBandwidthUpdate", + "unregisterNetworkCallback", + "getMultipathPreference", + "bindProcessToNetwork", + "setProcessDefaultNetwork", + "getRestrictBackgroundStatus", + "getNetworkWatchlistConfigHash", + "getConnectionOwnerUid", + ) + + specs.addAll( + "android/app/usage/NetworkStatsManager", + "NetworkStatsManager", + "querySummaryForDevice", + "querySummaryForUser", + "querySummary", + "queryDetailsForUid", + "queryDetailsForUidTag", + "queryDetailsForUidTagState", + "queryDetails", + "registerUsageCallback", + "unregisterUsageCallback", + ) + + specs.addAll( + "android/net/nsd/NsdManager", + "NsdManager", + "registerService", + "unregisterService", + "discoverServices", + "stopServiceDiscovery", + "resolveService", + "stopServiceResolution", + "registerServiceInfoCallback", + "unregisterServiceInfoCallback", + ) + + specs.addAll( + "android/net/wifi/WifiManager", + "WifiManager", + "getConfiguredNetworks", + "getCallerConfiguredNetworks", + "addNetworkPrivileged", + "addNetwork", + "updateNetwork", + "addOrUpdatePasspointConfiguration", + "removePasspointConfiguration", + "getPasspointConfigurations", + "removeNetwork", + "removeNonCallerConfiguredNetworks", + "enableNetwork", + "disableNetwork", + "disconnect", + "reconnect", + "reassociate", + "addNetworkSuggestions", + "removeNetworkSuggestions", + "getNetworkSuggestions", + "isPreferredNetworkOffloadSupported", + "is24GHzBandSupported", + "is5GHzBandSupported", + "is60GHzBandSupported", + "is6GHzBandSupported", + "isWifiStandardSupported", + "getConnectionInfo", + "isScanAlwaysAvailable", + "getChannelData", + "startScan", + "getScanResults", + "getDhcpInfo", + "setWifiEnabled", + "isWifiEnabled", + "pingSupplicant", + "registerSubsystemRestartTrackingCallback", + "unregisterSubsystemRestartTrackingCallback", + "getWifiState", + "addWifiStateChangedListener", + "removeWifiStateChangedListener", + "calculateSignalLevel", + "validateSoftApConfiguration", + "startLocalOnlyHotspot", + "setTdlsEnabled", + "setTdlsEnabledWithMacAddress", + "isTdlsOperationCurrentlyAvailable", + "getMaxSupportedConcurrentTdlsSessions", + "getNumberOfEnabledTdlsSessions", + "getWifiApConfiguration", + "setWifiApConfiguration", + "allowAutojoinGlobal", + "queryAutojoinGlobal", + "registerScanResultsCallback", + "unregisterScanResultsCallback", + "addSuggestionConnectionStatusListener", + "removeSuggestionConnectionStatusListener", + "addLocalOnlyConnectionFailureListener", + "removeLocalOnlyConnectionFailureListener", + "isScanThrottleEnabled", + "isAutoWakeupEnabled", + "isCarrierNetworkOffloadEnabled", + "addSuggestionUserApprovalStatusListener", + "removeSuggestionUserApprovalStatusListener", + "flushPasspointAnqpCache", + "getAllowedChannels", + "getUsableChannels", + "isWifiPasspointEnabled", + "reportCreateInterfaceImpact", + "getMaxNumberOfChannelsPerNetworkSpecifierRequest", + "setSendDhcpHostnameRestriction", + "querySendDhcpHostnameRestriction", + "setPerSsidRoamingMode", + "removePerSsidRoamingMode", + "getPerSsidRoamingModes", + "disallowCurrentSuggestedNetwork", + ) + specs.addAll( + "android/net/wifi/WifiManager\$WifiLock", + "WifiManager.WifiLock", + "acquire", + "release", + "setWorkSource", + ) + specs.addAll( + "android/net/wifi/WifiManager\$MulticastLock", + "WifiManager.MulticastLock", + "acquire", + "release", + ) + + specs.addAll( + "android/net/wifi/p2p/WifiP2pManager", + "WifiP2pManager", + "initialize", + "setWfdInfo", + "removeClient", + "isSetVendorElementsSupported", + "isChannelConstrainedDiscoverySupported", + "isGroupClientRemovalSupported", + "isGroupOwnerIPv6LinkLocalAddressProvided", + "isWiFiDirectR2Supported", + "isPccModeSupported", + "registerWifiP2pListener", + "unregisterWifiP2pListener", + ) + + specs.addAll( + "android/nfc/NfcAdapter", + "NfcAdapter", + "isEnabled", + "enable", + "disable", + "isReaderModeAnnotationSupported", + "isObserveModeSupported", + "isObserveModeEnabled", + "setObserveModeEnabled", + "enableForegroundDispatch", + "disableForegroundDispatch", + "setDiscoveryTechnology", + "isSecureNfcSupported", + "getNfcAntennaInfo", + "isSecureNfcEnabled", + "isReaderOptionSupported", + "isReaderOptionEnabled", + "ignore", + "isWlcEnabled", + "getWlcListenerDeviceInfo", + "isTagIntentAppPreferenceSupported", + "isTagIntentAllowed", + ) + + specs.addAll( + "android/net/VpnService", + "VpnService", + "prepare", + "setUnderlyingNetworks", + "isAlwaysOn", + "isLockdownEnabled", + ) + + specs.addAll( + "android/net/wifi/aware/WifiAwareManager", + "WifiAwareManager", + "isAvailable", + "isDeviceAttached", + "isSetChannelOnDataPathSupported", + "isInstantCommunicationModeEnabled", + "getCharacteristics", + "getAvailableAwareResources", + "attach", + "setOpportunisticModeEnabled", + "isOpportunisticModeEnabled", + "resetPairedDevices", + "removePairedDevice", + "getPairedDevices", + ) + + specs.addAll( + "android/net/wifi/rtt/WifiRttManager", + "WifiRttManager", + "isAvailable", + "startRanging", + "cancelRanging", + "getRttCharacteristics", + ) + + specs.addAll( + "android/net/ConnectivityDiagnosticsManager", + "ConnectivityDiagnosticsManager", + "registerConnectivityDiagnosticsCallback", + "unregisterConnectivityDiagnosticsCallback", + ) + + // endregion + + // region bluetooth + + specs.addAll( + "android/bluetooth/BluetoothAdapter", + "BluetoothAdapter", + "isEnabled", + "getState", + "getName", + "getAddress", + "getBondedDevices", + "startDiscovery", + "cancelDiscovery", + "isDiscovering", + "enable", + "disable", + "getScanMode", + "setScanMode", + ) + + specs.addAll( + "android/bluetooth/BluetoothDevice", + "BluetoothDevice", + "getName", + "getBondState", + "getType", + "createBond", + "removeBond", + "connectGatt", + "getBatteryLevel", + "getUuids", + ) + + specs.addAll( + "android/bluetooth/BluetoothGatt", + "BluetoothGatt", + "connect", + "disconnect", + "discoverServices", + "readCharacteristic", + "writeCharacteristic", + "readDescriptor", + "writeDescriptor", + "readRemoteRssi", + "requestMtu", + ) + + specs.addAll( + "android/bluetooth/BluetoothManager", + "BluetoothManager", + "getConnectedDevices", + "getConnectionState", + "getDevicesMatchingConnectionStates", + "openGattServer", + ) + + // endregion + + // region telephony + + specs.addAll( + "android/telephony/TelephonyManager", + "TelephonyManager", + "getDeviceSoftwareVersion", + "getDeviceId", + "getImei", + "getTypeAllocationCode", + "getMeid", + "getManufacturerCode", + "getNai", + "getCellLocation", + "getNeighboringCellInfo", + "getPhoneType", + "getNetworkOperator", + "getNetworkOperatorName", + "getNetworkCountryIso", + "getNetworkType", + "getDataNetworkType", + "getVoiceNetworkType", + "getSimOperator", + "getSimOperatorName", + "hasIccCard", + "getSimState", + "getSimSerialNumber", + "getCardIdForDefaultEuicc", + "getUiccCardsInfo", + "getSubscriberId", + "uploadCallComposerPicture", + "getGroupIdLevel1", + "getLine1Number", + "setLine1NumberForDisplay", + "getVoiceMailNumber", + "setVoiceMailNumber", + "getVisualVoicemailPackageName", + "setVisualVoicemailSmsFilterSettings", + "sendVisualVoicemailSms", + "getVoiceMailAlphaTag", + "sendDialerSpecialCode", + "getCallState", + "getCallStateForSubscription", + "getDataActivity", + "getDataState", + "listen", + "getAllCellInfo", + "requestCellInfoUpdate", + "getMmsUserAgent", + "getMmsUAProfUrl", + "iccOpenLogicalChannel", + "iccCloseLogicalChannel", + "iccTransmitApduLogicalChannel", + "iccTransmitApduBasicChannel", + "iccExchangeSimIO", + "sendEnvelopeWithStatus", + "rebootModem", + "getIccAuthentication", + "getForbiddenPlmns", + "setForbiddenPlmns", + "setNetworkSelectionModeAutomatic", + "requestNetworkScan", + "setNetworkSelectionModeManual", + "getNetworkSelectionMode", + "getManualNetworkSelectionPlmn", + "setAllowedNetworkTypesForReason", + "getAllowedNetworkTypesForReason", + "setPreferredNetworkTypeToGlobal", + "hasCarrierPrivileges", + "setOperatorBrandOverride", + "setCallComposerStatus", + "getCallComposerStatus", + "sendUssdRequest", + "isConcurrentVoiceAndDataSupported", + "setDataEnabled", + "isDataEnabled", + "isDataRoamingEnabled", + "canChangeDtmfToneLength", + "isWorldPhone", + "isRttSupported", + "isHearingAidCompatibilitySupported", + "getPhoneAccountHandle", + "getServiceState", + "getVoicemailRingtoneUri", + "setVoicemailRingtoneUri", + "isVoicemailVibrationEnabled", + "setVoicemailVibrationEnabled", + "getSimCarrierId", + "getSimCarrierIdName", + "getSimSpecificCarrierId", + "getSimSpecificCarrierIdName", + "getCarrierIdFromSimMccMnc", + "getCarrierRestrictionStatus", + "setDataEnabledForReason", + "isDataEnabledForReason", + "isManualNetworkSelectionAllowed", + "getSignalStrength", + "isDataConnectionAllowed", + "getSupportedRadioAccessFamily", + "getEmergencyNumberList", + "isEmergencyNumber", + "setPreferredOpportunisticDataSubscription", + "getPreferredOpportunisticDataSubscription", + "updateAvailableNetworks", + "isModemEnabledForSlot", + "isMultiSimSupported", + "switchMultiSimConfig", + "doesSwitchMultiSimConfigTriggerReboot", + "getEquivalentHomePlmns", + "isRadioInterfaceCapabilitySupported", + "registerTelephonyCallback", + "unregisterTelephonyCallback", + "setSignalStrengthUpdateRequest", + "clearSignalStrengthUpdateRequest", + "getNetworkSlicingConfiguration", + "isPremiumCapabilityAvailableForPurchase", + "purchasePremiumCapability", + "getPrimaryImei", + ) + + specs.addAll( + "android/telephony/SubscriptionManager", + "SubscriptionManager", + "getActiveSubscriptionInfo", + "getActiveSubscriptionInfoForSimSlotIndex", + "getAllSubscriptionInfoList", + "getActiveSubscriptionInfoList", + "getAccessibleSubscriptionInfoList", + "getActiveSubscriptionInfoCount", + "getActiveSubscriptionInfoCountMax", + "getSlotIndex", + "getSubscriptionId", + "getSubscriptionIds", + "getDefaultSubscriptionId", + "getDefaultVoiceSubscriptionId", + "getDefaultSmsSubscriptionId", + "getDefaultDataSubscriptionId", + "isNetworkRoaming", + "getOpportunisticSubscriptions", + "setOpportunistic", + "createSubscriptionGroup", + "addSubscriptionsIntoGroup", + "removeSubscriptionsFromGroup", + "getSubscriptionsInGroup", + "setDeviceToDeviceStatusSharingPreference", + "setDeviceToDeviceStatusSharingContacts", + "getActiveDataSubscriptionId", + "getPhoneNumber", + "setCarrierPhoneNumber", + "isSubscriptionAssociatedWithUser", + ) + + specs.addAll( + "android/telephony/CarrierConfigManager", + "CarrierConfigManager", + "getConfigForSubId", + "getConfig", + "notifyConfigChangedForSubId", + "getConfigByComponentForSubId", + ) + + specs.addAll( + "android/telecom/TelecomManager", + "TelecomManager", + "getDefaultOutgoingPhoneAccount", + "getUserSelectedOutgoingPhoneAccount", + "getSimCallManager", + "getSimCallManagerForSubscription", + "getCallCapablePhoneAccounts", + "getCallCapablePhoneAccountsAcrossProfiles", + "getSelfManagedPhoneAccounts", + "getOwnSelfManagedPhoneAccounts", + "getRegisteredPhoneAccounts", + "getPhoneAccount", + "registerPhoneAccount", + "unregisterPhoneAccount", + "getDefaultDialerPackage", + "getSystemDialerPackage", + "isVoiceMailNumber", + "getVoiceMailNumber", + "getLine1Number", + "isInCall", + "hasManageOngoingCallsPermission", + "isInManagedCall", + "endCall", + "acceptRingingCall", + "silenceRinger", + "isTtySupported", + "addNewIncomingCall", + "addNewIncomingConference", + "handleMmi", + "getAdnUriForPhoneAccount", + "cancelMissedCallsNotification", + "showInCallScreen", + "placeCall", + "startConference", + "createManageBlockedNumbersIntent", + "isIncomingCallPermitted", + "isOutgoingCallPermitted", + "acceptHandover", + "addCall", + ) + + specs.addAll( + "android/telephony/SmsManager", + "SmsManager", + "sendTextMessage", + "sendTextMessageWithoutPersisting", + "injectSmsPdu", + "sendMultipartTextMessage", + "sendDataMessage", + "getSubscriptionId", + "getDefaultSmsSubscriptionId", + "getSmsCapacityOnIcc", + "sendMultimediaMessage", + "downloadMultimediaMessage", + "getCarrierConfigValues", + "createAppSpecificSmsToken", + "createAppSpecificSmsTokenWithPackageInfo", + "getSmscAddress", + "setSmscAddress", + ) + + specs.addAll( + "android/telephony/euicc/EuiccManager", + "EuiccManager", + "isEnabled", + "getEid", + "getAvailableMemoryInBytes", + "downloadSubscription", + "continueOperation", + "getEuiccInfo", + "deleteSubscription", + "switchToSubscription", + "updateSubscriptionNickname", + "isSimPortAvailable", + ) + + // endregion + + // region hardware + + specs.addAll( + "android/hardware/camera2/CameraManager", + "CameraManager", + "getCameraIdList", + "getCameraCharacteristics", + "openCamera", + ) + + specs.addAll( + "android/hardware/ConsumerIrManager", + "ConsumerIrManager", + "hasIrEmitter", + "transmit", + "getCarrierFrequencies", + ) + + specs.addAll( + "android/hardware/biometrics/BiometricManager", + "BiometricManager", + "canAuthenticate", + "getLastAuthenticationTime", + ) + specs.addAll( + "android/hardware/biometrics/BiometricManager\$Strings", + "BiometricManager.Strings", + "getButtonLabel", + "getPromptMessage", + "getSettingName", + ) + + specs.addAll( + "android/hardware/fingerprint/FingerprintManager", + "FingerprintManager", + "authenticate", + "hasEnrolledFingerprints", + "isHardwareDetected", + ) + + specs.addAll( + "android/hardware/usb/UsbManager", + "UsbManager", + "getDeviceList", + "openDevice", + "getAccessoryList", + "openAccessory", + "hasPermission", + "requestPermission", + ) + + specs.addAll( + "android/hardware/display/DisplayManager", + "DisplayManager", + "getDisplays", + "getDisplay", + ) + + specs.addAll( + "android/hardware/input/InputManager", + "InputManager", + "getInputDevice", + "getInputDeviceViewBehavior", + "getInputDeviceIds", + "registerInputDeviceListener", + "unregisterInputDeviceListener", + "verifyInputEvent", + "getHostUsiVersion", + ) + + // endregion + + // region os & power + + specs.addAll( + "android/os/PowerManager", + "PowerManager", + "goToSleep", + "wakeUp", + "isWakeLockLevelSupported", + "isInteractive", + "reboot", + "isPowerSaveMode", + "getBatteryDischargePrediction", + "isBatteryDischargePredictionPersonalized", + "getLocationPowerSaveMode", + "isDeviceIdleMode", + "isDeviceLightIdleMode", + "isLowPowerStandbyEnabled", + "isExemptFromLowPowerStandby", + "isAllowedInLowPowerStandby", + "getCurrentThermalStatus", + "addThermalStatusListener", + "removeThermalStatusListener", + "addThermalHeadroomListener", + "removeThermalHeadroomListener", + "getThermalHeadroom", + "getThermalHeadroomThresholds", + ) + specs.addAll( + "android/os/PowerManager\$WakeLock", + "PowerManager.WakeLock", + "acquire", + "release", + "setWorkSource", + "setStateListener", + ) + + specs.addAll( + "android/os/UserManager", + "UserManager", + "getUserProfiles", + "isUserUnlocked", + "isAdminUser", + "isForegroundUserAdmin", + "isDemoUser", + "isUserForeground", + "isProfile", + "getUserName", + "getUserRestrictions", + "setUserRestriction", + "isQuietModeEnabled", + "getApplicationRestrictions", + "getUserCreationTime", + "getSerialNumberForUser", + "getUserForSerialNumber", + "getUserCount", + ) + + specs.addAll( + "android/os/storage/StorageManager", + "StorageManager", + "getStorageVolumes", + "getPrimaryStorageVolume", + "getAllocatableBytes", + "getCacheSizeBytes", + "registerStorageVolumeCallback", + "unregisterStorageVolumeCallback", + "mountObb", + "getManageSpaceActivityIntent", + "unmountObb", + "isObbMounted", + "getMountedObbPath", + "getUuidForPath", + "getCacheQuotaBytes", + "isCheckpointSupported", + ) + + specs.addAll( + "android/os/BatteryManager", + "BatteryManager", + "isCharging", + "getIntProperty", + "getLongProperty", + "getStringProperty", + ) + + specs.addAll( + "android/os/DropBoxManager", + "DropBoxManager", + "addData", + "addFile", + "isTagEnabled", + ) + + specs.addAll( + "android/os/health/SystemHealthManager", + "SystemHealthManager", + "takeUidSnapshot", + "takeUidSnapshots", + ) + + specs.addAll( + "android/os/Vibrator", + "Vibrator", + "vibrate", + "cancel", + "hasVibrator", + "hasAmplitudeControl", + ) + + specs.addAll( + "android/os/VibratorManager", + "VibratorManager", + "getVibratorIds", + "getDefaultVibrator", + "vibrate", + "cancel", + ) + + val strictMode = "android/os/StrictMode" + for (method in + listOf( + "setThreadPolicy", + "setThreadPolicyMask", + "allowThreadDiskWrites", + "allowThreadDiskReads", + "allowThreadViolations", + )) { + specs.add(BinderMethodSpec(strictMode, method, "StrictMode", isStatic = true)) + } + + // endregion + + // region accessibility, input & text + + specs.addAll( + "android/view/accessibility/AccessibilityManager", + "AccessibilityManager", + "sendAccessibilityEvent", + "interrupt", + "getAccessibilityServiceList", + "getInstalledAccessibilityServiceList", + "getEnabledAccessibilityServiceList", + ) + + specs.addAll( + "android/view/autofill/AutofillManager", + "AutofillManager", + "requestAutofill", + "notifyViewEntered", + "notifyVirtualViewsReady", + "notifyViewExited", + "notifyViewVisibilityChanged", + "notifyValueChanged", + "notifyViewClicked", + "commit", + "cancel", + "disableAutofillServices", + "hasEnabledAutofillServices", + "getAutofillServiceComponentName", + "setUserData", + "isFieldClassificationEnabled", + "getDefaultFieldClassificationAlgorithm", + "getAvailableFieldClassificationAlgorithms", + "isAutofillSupported", + "registerCallback", + "unregisterCallback", + "showAutofillDialog", + ) + + specs.addAll( + "android/view/textservice/TextServicesManager", + "TextServicesManager", + "newSpellCheckerSession", + "getEnabledSpellCheckerInfos", + "getCurrentSpellCheckerInfo", + "isSpellCheckerEnabled", + ) + + specs.addAll( + "android/view/inputmethod/InputMethodManager", + "InputMethodManager", + "isActive", + "getInputMethodList", + "isStylusHandwritingAvailable", + "isConnectionlessStylusHandwritingAvailable", + "getCurrentInputMethodInfo", + "getEnabledInputMethodList", + "getEnabledInputMethodSubtypeList", + "showStatusIcon", + "hideStatusIcon", + "displayCompletions", + "updateExtractedText", + "showSoftInput", + "hideSoftInputFromWindow", + "startStylusHandwriting", + "startConnectionlessStylusHandwriting", + "startConnectionlessStylusHandwritingForDelegation", + "prepareStylusHandwritingDelegation", + "acceptStylusHandwritingDelegation", + "toggleSoftInputFromWindow", + "toggleSoftInput", + "restartInput", + "updateSelection", + "viewClicked", + "updateCursor", + "updateCursorAnchorInfo", + "sendAppPrivateCommand", + "setInputMethod", + "setInputMethodAndSubtype", + "hideSoftInputFromInputMethod", + "showSoftInputFromInputMethod", + "showInputMethodPicker", + "getCurrentInputMethodSubtype", + "setCurrentInputMethodSubtype", + "switchToLastInputMethod", + "switchToNextInputMethod", + "shouldOfferSwitchingToNextInputMethod", + "setAdditionalInputMethodSubtypes", + "setExplicitlyEnabledInputMethodSubtypes", + "getLastInputMethodSubtype", + ) + + specs.addAll( + "android/print/PrintManager", + "PrintManager", + "getPrintJobs", + "print", + "isPrintServiceEnabled", + ) + + // endregion + + // region media & location + + specs.addAll( + "android/media/AudioManager", + "AudioManager", + "isVolumeFixed", + "adjustStreamVolume", + "adjustVolume", + "adjustSuggestedStreamVolume", + "getStreamMaxVolume", + "getStreamVolume", + "getRingerMode", + "setRingerMode", + "setStreamVolume", + "adjustVolumeGroupVolume", + "isVolumeGroupMuted", + "isStreamMute", + "shouldVibrate", + "getVibrateSetting", + "setVibrateSetting", + "requestAudioFocus", + "abandonAudioFocus", + "setAllowedCapturePolicy", + "getAllowedCapturePolicy", + "setSpeakerphoneOn", + "isSpeakerphoneOn", + "startBluetoothSco", + "stopBluetoothSco", + "setBluetoothScoOn", + "isBluetoothScoOn", + "isBluetoothA2dpOn", + "setMicrophoneMute", + "isMicrophoneMute", + "setMode", + "getMode", + "isCallScreeningModeSupported", + "isMusicActive", + "playSoundEffect", + "loadSoundEffects", + "unloadSoundEffects", + "registerAudioPlaybackCallback", + "unregisterAudioPlaybackCallback", + "getActivePlaybackConfigurations", + "registerAudioRecordingCallback", + "unregisterAudioRecordingCallback", + "getActiveRecordingConfigurations", + "getEncodedSurroundMode", + "isSurroundFormatEnabled", + "setCommunicationDevice", + "clearCommunicationDevice", + "getCommunicationDevice", + "getAvailableCommunicationDevices", + ) + + specs.addAll( + "android/media/MediaRouter\$RouteInfo", + "MediaRouter.RouteInfo", + "requestSetVolume", + "requestUpdateVolume", + "getVolumeMax", + ) + + specs.addAll( + "android/media/session/MediaSessionManager", + "MediaSessionManager", + "getActiveSessions", + "getMediaKeyEventSession", + "getMediaKeyEventSessionPackageName", + "addOnActiveSessionsChangedListener", + "removeOnActiveSessionsChangedListener", + "addOnSession2TokensChangedListener", + "removeOnSession2TokensChangedListener", + "isTrustedForMediaControl", + "addOnMediaKeyEventSessionChangedListener", + "removeOnMediaKeyEventSessionChangedListener", + ) + + specs.addAll( + "android/media/session/MediaSession", + "MediaSession", + "setSessionActivity", + "setMediaButtonReceiver", + "setMediaButtonBroadcastReceiver", + "setFlags", + "setPlaybackToLocal", + "setPlaybackToRemote", + "setActive", + "sendSessionEvent", + "release", + "setPlaybackState", + "setMetadata", + "setQueue", + "setQueueTitle", + "setRatingType", + "setExtras", + ) + + specs.addAll( + "android/media/session/MediaController", + "MediaController", + "dispatchMediaButtonEvent", + "getPlaybackState", + "getMetadata", + "getQueue", + "getQueueTitle", + "getExtras", + "getRatingType", + "getFlags", + "getPlaybackInfo", + "getSessionActivity", + "setVolumeTo", + "adjustVolume", + "registerCallback", + "unregisterCallback", + "sendCommand", + "getPackageName", + "getSessionInfo", + "getTag", + ) + specs.addAll( + "android/media/session/MediaController\$TransportControls", + "MediaController.TransportControls", + "prepare", + "prepareFromMediaId", + "prepareFromSearch", + "prepareFromUri", + "play", + "playFromMediaId", + "playFromSearch", + "playFromUri", + "skipToQueueItem", + "pause", + "stop", + "seekTo", + "fastForward", + "skipToNext", + "rewind", + "skipToPrevious", + "setRating", + "setPlaybackSpeed", + "sendCustomAction", + ) + + specs.addAll( + "android/media/MediaRouter2", + "MediaRouter2", + "requestScan", + "cancelScanRequest", + "registerRouteCallback", + "unregisterRouteCallback", + "showSystemOutputSwitcher", + "setRouteListingPreference", + "transferTo", + "stop", + "getControllers", + "setRouteVolume", + ) + specs.addAll( + "android/media/MediaRouter2\$RoutingController", + "MediaRouter2.RoutingController", + "selectRoute", + "deselectRoute", + "setVolume", + "release", + ) + + specs.addAll( + "android/media/Spatializer", + "Spatializer", + "isEnabled", + "isAvailable", + "isHeadTrackerAvailable", + "addOnHeadTrackerAvailableListener", + "removeOnHeadTrackerAvailableListener", + "getImmersiveAudioLevel", + "canBeSpatialized", + "getSpatializedChannelMasks", + "addOnSpatializerStateChangedListener", + "removeOnSpatializerStateChangedListener", + ) + + specs.addAll( + "android/media/midi/MidiManager", + "MidiManager", + "registerDeviceCallback", + "unregisterDeviceCallback", + "getDevices", + "getDevicesForTransport", + "openDevice", + "openBluetoothDevice", + ) + + specs.addAll( + "android/media/tv/TvInputManager", + "TvInputManager", + "getTvInputList", + "getTvInputInfo", + "updateTvInputInfo", + "isParentalControlsEnabled", + "isRatingBlocked", + "getBlockedRatings", + ) + + specs.addAll( + "android/location/LocationManager", + "LocationManager", + "getLastKnownLocation", + "getCurrentLocation", + "requestLocationUpdates", + "requestFlush", + "removeUpdates", + "hasProvider", + "getAllProviders", + "getProviders", + "getBestProvider", + "getProvider", + "getProviderProperties", + "sendExtraCommand", + "addTestProvider", + "removeTestProvider", + "setTestProviderLocation", + "setTestProviderEnabled", + "addProximityAlert", + "removeProximityAlert", + "getGnssCapabilities", + "getGnssYearOfHardware", + "getGnssHardwareModelName", + "getGnssAntennaInfos", + ) + + specs.addAll( + "android/location/Geocoder", + "Geocoder", + "isPresent", + "getFromLocation", + "getFromLocationName", + ) + + // endregion + + // region voice & speech + + specs.addAll( + "android/speech/tts/TextToSpeech", + "TextToSpeech", + "shutdown", + "speak", + "playEarcon", + "playSilentUtterance", + "playSilence", + "getFeatures", + "isSpeaking", + "stop", + "getDefaultLanguage", + "setLanguage", + "getLanguage", + "getAvailableLanguages", + "getVoices", + "setVoice", + "getVoice", + "getDefaultVoice", + "isLanguageAvailable", + "synthesizeToFile", + ) + + specs.addAll( + "android/speech/SpeechRecognizer", + "SpeechRecognizer", + "isRecognitionAvailable", + "setRecognitionListener", + "startListening", + "stopListening", + "cancel", + "checkRecognitionSupport", + "triggerModelDownload", + "destroy", + ) + + // endregion + + // region health + + specs.addAll( + "android/health/connect/HealthConnectManager", + "HealthConnectManager", + "insertRecords", + "aggregate", + "aggregateGroupByDuration", + "aggregateGroupByPeriod", + "deleteRecords", + "getChangeLogs", + "getChangeLogToken", + "readRecords", + "updateRecords", + "upsertMedicalResources", + "readMedicalResources", + "deleteMedicalResources", + "createMedicalDataSource", + "getMedicalDataSources", + "deleteMedicalDataSourceWithData", + ) + + // endregion + + return specs.groupBy { it.owner } + } + + private fun MutableList.addAll( + owner: String, + component: String, + vararg methods: String, + ) { + for (method in methods) { + add(BinderMethodSpec(owner, method, component)) + } + } +} diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodVisitor.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodVisitor.kt new file mode 100644 index 000000000..e4150d999 --- /dev/null +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodVisitor.kt @@ -0,0 +1,157 @@ +package io.sentry.android.gradle.instrumentation.binder + +import io.sentry.android.gradle.instrumentation.MethodContext +import io.sentry.android.gradle.instrumentation.MethodInstrumentable +import io.sentry.android.gradle.instrumentation.SpanAddingClassVisitorFactory +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.objectweb.asm.commons.GeneratorAdapter +import org.objectweb.asm.commons.Method + +class BinderMethodInstrumentable : MethodInstrumentable { + + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters, + ): MethodVisitor = BinderMethodVisitor(apiVersion, originalVisitor, instrumentableContext) + + override fun isInstrumentable(data: MethodContext): Boolean = true +} + +private const val SENTRY_BINDER_ADAPTER = + "io/sentry/android/core/internal/binder/SentryBinderAdapter" + +class BinderMethodVisitor( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, +) : + GeneratorAdapter( + apiVersion, + originalVisitor, + instrumentableContext.access, + instrumentableContext.name, + instrumentableContext.descriptor, + ) { + + override fun visitMethodInsn( + opcode: Int, + owner: String, + name: String, + descriptor: String, + isInterface: Boolean, + ) { + val spec = BinderMethodRegistry.lookup(owner, name) + + // Not a tracked binder call - emit the original instruction untouched. + if (spec == null) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + return + } + + // The registry is keyed by owner + name only, so a lookup can match a call whose invocation + // kind (static vs. instance) differs from the spec - e.g. an unrelated static method that + // happens to share a name with a tracked instance method (many tracked names are generic, + // like commit/cancel/acquire/release). That distinction decides whether a receiver sits on + // the stack below the arguments, so instrumenting a mismatched call would spill the wrong + // number of values and emit invalid bytecode. Skip it and emit the original instruction. + val opcodeMatchesSpec = + if (spec.isStatic) { + opcode == Opcodes.INVOKESTATIC + } else { + opcode == Opcodes.INVOKEVIRTUAL || opcode == Opcodes.INVOKEINTERFACE + } + if (!opcodeMatchesSpec) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + return + } + + // We rewrite `target(args...)` into the equivalent of: + // + // Object token = SentryBinderAdapter.onCallStart(component, name); + // try { + // target(args...); + // SentryBinderAdapter.onCallEnd(token); + // } catch (Throwable t) { + // SentryBinderAdapter.onCallEnd(token); + // throw t; + // } + // + // Adapter calls are emitted via `mv` (the delegate) rather than the GeneratorAdapter helpers + // (push/invokeStatic) on purpose: those helpers route through visitMethodInsn - this very + // override - and would recurse. `super.visitMethodInsn` is reserved for the original target + // call so it still passes through any downstream visitors. + + // The arguments (and the receiver, for instance calls) are already on the stack. Spill them + // into locals so we can run onCallStart first and reload them inside the try block. The stack + // is LIFO, so arguments are popped in reverse order. + val argTypes = Method(name, descriptor).argumentTypes + val argLocals = IntArray(argTypes.size) + for (i in argLocals.size - 1 downTo 0) { + argLocals[i] = newLocal(argTypes[i]) + storeLocal(argLocals[i]) + } + + val receiverLocal = + if (!spec.isStatic) { + newLocal(Type.getObjectType(owner)).also { storeLocal(it) } + } else { + -1 + } + + // Object token = SentryBinderAdapter.onCallStart(component, name); + mv.visitLdcInsn(spec.component) + mv.visitLdcInsn(name) + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + SENTRY_BINDER_ADAPTER, + "onCallStart", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", + false, + ) + val tokenLocal = newLocal(Type.getObjectType("java/lang/Object")) + storeLocal(tokenLocal) + + // SentryBinderAdapter.onCallEnd(token); - emitted on both the normal and the exceptional path. + fun emitOnCallEnd() { + loadLocal(tokenLocal) + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + SENTRY_BINDER_ADAPTER, + "onCallEnd", + "(Ljava/lang/Object;)V", + false, + ) + } + + val tryStart = Label() + val tryEnd = Label() + val catchHandler = Label() + val afterFinally = Label() + mv.visitTryCatchBlock(tryStart, tryEnd, catchHandler, null) + + // try { target(args...); onCallEnd(token); } + mv.visitLabel(tryStart) + if (!spec.isStatic) { + loadLocal(receiverLocal) + } + for (local in argLocals) { + loadLocal(local) + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + mv.visitLabel(tryEnd) + emitOnCallEnd() + mv.visitJumpInsn(Opcodes.GOTO, afterFinally) + + // catch (Throwable t) { onCallEnd(token); throw t; } + mv.visitLabel(catchHandler) + emitOnCallEnd() + mv.visitInsn(Opcodes.ATHROW) + + mv.visitLabel(afterFinally) + } +} diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt index 29ba71bb3..85744674f 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/services/SentryModulesService.kt @@ -44,6 +44,10 @@ abstract class SentryModulesService : features.add("AppStartInstrumentation") } + if (isBinderInstrEnabled()) { + features.add("BinderInstrumentation") + } + if (parameters.sourceContextEnabled.getOrElse(false)) { features.add("SourceContext") } @@ -116,6 +120,8 @@ abstract class SentryModulesService : sentryModules.isAtLeast(SentryModules.SENTRY_ANDROID_CORE, SentryVersions.VERSION_APP_START) && parameters.appStartEnabled.get() + fun isBinderInstrEnabled(): Boolean = parameters.binderEnabled.get() + private fun Map.isAtLeast( module: ModuleIdentifier, minVersion: SemVer, @@ -129,6 +135,7 @@ abstract class SentryModulesService : sourceContextEnabled: Provider, dexguardEnabled: Provider, appStartEnabled: Provider, + binderEnabled: Provider, ): Provider { return project.gradle.sharedServices.registerIfAbsent( getBuildServiceName(SentryModulesService::class.java), @@ -139,6 +146,7 @@ abstract class SentryModulesService : it.parameters.sourceContextEnabled.setDisallowChanges(sourceContextEnabled) it.parameters.dexguardEnabled.setDisallowChanges(dexguardEnabled) it.parameters.appStartEnabled.setDisallowChanges(appStartEnabled) + it.parameters.binderEnabled.setDisallowChanges(binderEnabled) } } } @@ -156,5 +164,7 @@ abstract class SentryModulesService : @get:Input val dexguardEnabled: Property @get:Input val appStartEnabled: Property + + @get:Input val binderEnabled: Property } } diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt index 8485bde93..9015c5073 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/util/Versions.kt @@ -35,6 +35,7 @@ internal object SentryVersions { internal val VERSION_SQLITE = SemVer(6, 21, 0) internal val VERSION_ANDROID_OKHTTP_LISTENER = SemVer(6, 20, 0) internal val VERSION_OKHTTP = SemVer(7, 0, 0) + internal val VERSION_BINDER = SemVer(8, 40, 0) // TODO set right version } internal object SentryModules { diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodRegistryTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodRegistryTest.kt new file mode 100644 index 000000000..171842043 --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodRegistryTest.kt @@ -0,0 +1,43 @@ +package io.sentry.android.gradle.instrumentation.binder + +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import org.junit.Test + +class BinderMethodRegistryTest { + + @Test + fun `lookup returns null for unknown owner`() { + assertNull(BinderMethodRegistry.lookup("com/example/Foo", "bar")) + } + + @Test + fun `lookup returns null for known owner but unknown method`() { + assertNull(BinderMethodRegistry.lookup("android/content/ContentResolver", "unknownMethod")) + } + + @Test + fun `lookup returns spec for known instance binder method`() { + val spec = BinderMethodRegistry.lookup("android/content/ContentResolver", "query") + assertNotNull(spec) + assertEquals("ContentResolver", spec.component) + assertFalse(spec.isStatic) + } + + @Test + fun `lookup returns spec for known static binder method`() { + val spec = BinderMethodRegistry.lookup("android/provider/Settings\$Secure", "getString") + assertNotNull(spec) + assertEquals("Settings.Secure", spec.component) + assertTrue(spec.isStatic) + } + + @Test + fun `lookup covers Context subtypes separately`() { + assertNotNull(BinderMethodRegistry.lookup("android/content/Context", "startService")) + assertNotNull(BinderMethodRegistry.lookup("android/content/ContextWrapper", "startService")) + } +} diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodVisitorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodVisitorTest.kt new file mode 100644 index 000000000..7553d673a --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/BinderMethodVisitorTest.kt @@ -0,0 +1,152 @@ +package io.sentry.android.gradle.instrumentation.binder + +import io.sentry.android.gradle.instrumentation.CommonClassVisitor +import io.sentry.android.gradle.instrumentation.fakes.TestSpanAddingParameters +import java.io.PrintWriter +import java.io.StringWriter +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import org.objectweb.asm.util.Textifier +import org.objectweb.asm.util.TraceClassVisitor + +class BinderMethodVisitorTest { + + @get:Rule val tmpDir = TemporaryFolder() + + private fun instrument(classBytes: ByteArray): ByteArray { + val reader = ClassReader(classBytes) + val writer = ClassWriter(reader, ClassWriter.COMPUTE_MAXS) + val visitor = + CommonClassVisitor( + Opcodes.ASM9, + writer, + "TestClass", + listOf(BinderMethodInstrumentable()), + TestSpanAddingParameters(debugOutput = false, inMemoryDir = tmpDir.root), + ) + reader.accept(visitor, ClassReader.SKIP_FRAMES) + return writer.toByteArray() + } + + private fun disassemble(bytes: ByteArray): String { + val sw = StringWriter() + ClassReader(bytes).accept(TraceClassVisitor(null, Textifier(), PrintWriter(sw)), 0) + return sw.toString() + } + + @Test + fun `wraps known instance binder call with tracer start and end`() { + // Method body: ContentResolver.query(uri, null, null, null, null) + val bytes = + SyntheticClass.build("callQuery", "(Landroid/content/ContentResolver;Landroid/net/Uri;)V") { + visitVarInsn(Opcodes.ALOAD, 0) // resolver + visitVarInsn(Opcodes.ALOAD, 1) // uri + visitInsn(Opcodes.ACONST_NULL) + visitInsn(Opcodes.ACONST_NULL) + visitInsn(Opcodes.ACONST_NULL) + visitInsn(Opcodes.ACONST_NULL) + visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "android/content/ContentResolver", + "query", + "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;", + false, + ) + visitInsn(Opcodes.POP) + visitInsn(Opcodes.RETURN) + } + + val instrumented = instrument(bytes) + val text = disassemble(instrumented) + + assertTrue( + text.contains( + "io/sentry/android/core/internal/binder/SentryBinderAdapter.onCallStart " + + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;" + ), + "onCallStart should be emitted:\n$text", + ) + assertTrue( + text.contains( + "io/sentry/android/core/internal/binder/SentryBinderAdapter.onCallEnd " + + "(Ljava/lang/Object;)V" + ), + "onCallEnd should be emitted:\n$text", + ) + assertTrue( + text.contains("LDC \"ContentResolver\""), + "component constant should be pushed:\n$text", + ) + assertTrue(text.contains("LDC \"query\""), "method name constant should be pushed:\n$text") + assertTrue(text.contains("android/content/ContentResolver.query")) + assertTrue(text.contains("TRYCATCHBLOCK"), "try/catch handler should be emitted:\n$text") + } + + @Test + fun `wraps known static binder call`() { + val bytes = + SyntheticClass.build("callSettings", "()Ljava/lang/String;") { + visitInsn(Opcodes.ACONST_NULL) // resolver + visitLdcInsn("some_key") + visitMethodInsn( + Opcodes.INVOKESTATIC, + "android/provider/Settings\$Secure", + "getString", + "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;", + false, + ) + visitInsn(Opcodes.ARETURN) + } + + val instrumented = instrument(bytes) + val text = disassemble(instrumented) + + assertTrue( + text.contains("io/sentry/android/core/internal/binder/SentryBinderAdapter.onCallStart") + ) + assertTrue(text.contains("LDC \"Settings.Secure\"")) + } + + @Test + fun `does not wrap unknown method calls`() { + val bytes = + SyntheticClass.build("callUnknown", "(Ljava/lang/String;)I") { + visitVarInsn(Opcodes.ALOAD, 1) + visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false) + visitInsn(Opcodes.IRETURN) + } + + val instrumented = instrument(bytes) + val text = disassemble(instrumented) + + assertFalse(text.contains("SentryBinderAdapter"), "unknown calls must not be wrapped:\n$text") + } + + @Test + fun `does not wrap when opcode does not match registry kind`() { + // Registry marks ContentResolver.query as instance-only; emit as INVOKESTATIC and expect no + // wrap + val bytes = + SyntheticClass.build("callStaticQuery", "()V") { + visitMethodInsn( + Opcodes.INVOKESTATIC, + "android/content/ContentResolver", + "query", + "()V", + false, + ) + visitInsn(Opcodes.RETURN) + } + + val instrumented = instrument(bytes) + val text = disassemble(instrumented) + + assertFalse(text.contains("SentryBinderAdapter")) + } +} diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/SyntheticClass.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/SyntheticClass.kt new file mode 100644 index 000000000..7a934c4b8 --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/binder/SyntheticClass.kt @@ -0,0 +1,20 @@ +package io.sentry.android.gradle.instrumentation.binder + +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +internal object SyntheticClass { + + fun build(methodName: String, descriptor: String, body: MethodVisitor.() -> Unit): ByteArray { + val cw = ClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES) + cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "TestClass", null, "java/lang/Object", null) + val mv = cw.visitMethod(Opcodes.ACC_PUBLIC, methodName, descriptor, null, null) + mv.visitCode() + mv.body() + mv.visitMaxs(0, 0) + mv.visitEnd() + cw.visitEnd() + return cw.toByteArray() + } +} diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestSpanAddingParameters.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestSpanAddingParameters.kt index 92624bcce..d21f87517 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestSpanAddingParameters.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/fakes/TestSpanAddingParameters.kt @@ -47,4 +47,7 @@ class TestSpanAddingParameters( override val appStartEnabled: Property get() = TODO() + + override val binderEnabled: Property + get() = TODO() } diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesCollectorTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesCollectorTest.kt index c2f728d81..fe99af692 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesCollectorTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/util/SentryModulesCollectorTest.kt @@ -57,6 +57,7 @@ class SentryModulesCollectorTest { val sourceContextEnabled = fakeProject.provider { true } val dexguardEnabled = fakeProject.provider { true } val appStartEnabled = fakeProject.provider { true } + val binderEnabled = fakeProject.provider { true } val project = spy(fakeProject) whenever(project.logger).thenReturn(logger) @@ -69,6 +70,7 @@ class SentryModulesCollectorTest { sourceContextEnabled, dexguardEnabled, appStartEnabled, + binderEnabled, ) return project