Introduce Android lint checks around Binder.clearCallingIdentity()

Introduce a lint detector that finds the following issues:

1. Unused token of Binder.clearCallingIdentity()
- The token from Binder.clearCallingIdentity() has to be used in
Binder.restoreCallingIdentity().

2. Non-final token of Binder.clearCallingIdentity()
- The variable assigned to the result of Binder.clearCallingIdentity()
has to be final to prevent it from being overwritten.

3. Nested calls of Binder.clearCallingIdentity()
- The identity can be cleared again once it has been restored with the
result of the first call of Binder.clearCallingIdentity().

4. Binder.restoreCallingIdentity() is not in finally block
- Binder.restoreCallingIdentity() has to be in finally block to prevent
the calling application from running with the system identity.

5. Use of caller-aware methods after Binder.clearCallingIdentity()
- Caller-aware methods use the caller's identity to perform operations,
so after Binder.clearCallingIdentity() these methods will be using the
sysem identity instead of the original caller's identity.

The lint check is enabled on platform_service_defaults, which means it
will be enabled on all "services.XXX" modules. The linter issues
encountered in existing code are reported in the hotlist
"security_checker_bugs" (b/hotlists/3279139).

To compile a lint report, pick a service (e.g services.accessibility),
run the test command and view it as lint-report.html. The lint report
won't be generated if you just build the module (i.e m
services.accessibility won't produce the lint report).

Lint report can be found in out/soong/.intermediates/frameworks/base/services/accessibility/services.accessibility/android_common/lint

All tests pass in gradle, but need to run on Soong when it's implemented
(b/162368644).

Bug: 157626959
Test: m out/soong/.intermediates/frameworks/base/services/accessibility/services.accessibility/android_common/lint/lint-report.html
Test: google-chrome out/soong/.intermediates/frameworks/base/services/accessibility/services.accessibility/android_common/lint/lint-report.html
Test: ./gradlew test
Change-Id: I9814e9fbc36989c816900d900c6adec3e07802f7
diff --git a/tools/lint/Android.bp b/tools/lint/Android.bp
new file mode 100644
index 0000000..dcbc32b
--- /dev/null
+++ b/tools/lint/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+    name: "AndroidFrameworkLintChecker",
+    srcs: ["checks/src/main/java/**/*.kt"],
+    plugins: ["auto_service_plugin"],
+    libs: [
+        "auto_service_annotations",
+        "lint_api",
+    ],
+}
+
+// TODO: (b/162368644) Implement these (working in gradle) Kotlin Tests to run on Soong
+//java_test_host {
+//    name: "AndroidFrameworkLintCheckerTest",
+//    srcs: [
+//     "checks/src/test/java/**/*.kt",
+//     "checks/src/main/java/**/*.kt",
+//    ],
+//    plugins: ["auto_service_plugin"],
+//    static_libs: [
+//        "auto_service_annotations",
+//        "lint_api",
+//    ],
+//}
diff --git a/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenDetector.kt b/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenDetector.kt
new file mode 100644
index 0000000..4e30834
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenDetector.kt
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.lint
+
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_NESTED_CLEAR_IDENTITY_CALLS
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_NON_FINAL_TOKEN
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_UNUSED_TOKEN
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNestedClearIdentityCallsPrimary
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNestedClearIdentityCallsSecondary
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNonFinalToken
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageRestoreIdentityCallNotInFinallyBlock
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageUnusedToken
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Context
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.search.PsiSearchScopeUtil
+import com.intellij.psi.search.SearchScope
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.ULocalVariable
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.USimpleNameReferenceExpression
+import org.jetbrains.uast.UTryExpression
+import org.jetbrains.uast.getParentOfType
+import org.jetbrains.uast.isUastChildOf
+
+/**
+ * Lint Detector that finds issues with improper usages of the token returned by
+ * Binder.clearCallingIdentity()
+ */
+@Suppress("UnstableApiUsage")
+class CallingIdentityTokenDetector : Detector(), SourceCodeScanner {
+    private companion object {
+        const val CLASS_BINDER = "android.os.Binder"
+        const val CLASS_USER_HANDLE = "android.os.UserHandle"
+
+        @JvmField
+        val callerAwareMethods = listOf(
+                Method.BINDER_GET_CALLING_PID,
+                Method.BINDER_GET_CALLING_UID,
+                Method.BINDER_GET_CALLING_UID_OR_THROW,
+                Method.BINDER_GET_CALLING_USER_HANDLE,
+                Method.USER_HANDLE_GET_CALLING_APP_ID,
+                Method.USER_HANDLE_GET_CALLING_USER_ID
+        )
+    }
+
+    /** Map of <Token variable name, Token object> */
+    private val tokensMap = mutableMapOf<String, Token>()
+
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(ULocalVariable::class.java, UQualifiedReferenceExpression::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler =
+            TokenUastHandler(context)
+
+    /** File analysis starts with a clear map */
+    override fun beforeCheckFile(context: Context) {
+        tokensMap.clear()
+    }
+
+    /**
+     * - If tokensMap has tokens after checking the file -> reports all locations as unused token
+     * issue incidents
+     * - File analysis ends with a clear map
+     */
+    override fun afterCheckFile(context: Context) {
+        for (token in tokensMap.values) {
+            context.report(
+                    ISSUE_UNUSED_TOKEN,
+                    token.location,
+                    getIncidentMessageUnusedToken(token.variableName)
+            )
+        }
+        tokensMap.clear()
+    }
+
+    /** UAST handler that analyses elements and reports incidents */
+    private inner class TokenUastHandler(val context: JavaContext) : UElementHandler() {
+        /**
+         * For every variable initialization with Binder.clearCallingIdentity():
+         * - Checks for non-final token issue
+         * - Checks for unused token issue within different scopes
+         * - Checks for nested calls of clearCallingIdentity() issue
+         * - Stores token variable name, scope in the file and its location in tokensMap
+         */
+        override fun visitLocalVariable(node: ULocalVariable) {
+            val rhsExpression = node.uastInitializer as? UQualifiedReferenceExpression ?: return
+            if (!isMethodCall(rhsExpression, Method.BINDER_CLEAR_CALLING_IDENTITY)) return
+            val location = context.getLocation(node as UElement)
+            val variableName = node.getName()
+            if (!node.isFinal) {
+                context.report(
+                        ISSUE_NON_FINAL_TOKEN,
+                        location,
+                        getIncidentMessageNonFinalToken(variableName)
+                )
+            }
+            // If there exists an unused variable with the same name in the map, we can imply that
+            // we left the scope of the previous declaration, so we need to report the unused token
+            val oldToken = tokensMap[variableName]
+            if (oldToken != null) {
+                context.report(
+                        ISSUE_UNUSED_TOKEN,
+                        oldToken.location,
+                        getIncidentMessageUnusedToken(oldToken.variableName)
+                )
+            }
+            // If there exists a token in the same scope as the current new token, it means that
+            // clearCallingIdentity() has been called at least twice without immediate restoration
+            // of identity, so we need to report the nested call of clearCallingIdentity()
+            val firstCallToken = findFirstTokenInScope(node)
+            if (firstCallToken != null) {
+                context.report(
+                        ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+                        createNestedLocation(firstCallToken, location),
+                        getIncidentMessageNestedClearIdentityCallsPrimary(
+                                firstCallToken.variableName,
+                                variableName
+                        )
+                )
+            }
+            tokensMap[variableName] = Token(variableName, node.sourcePsi?.getUseScope(), location)
+        }
+
+        /**
+         * For every class.method():
+         * - Checks use of caller-aware methods issue
+         * For every call of Binder.restoreCallingIdentity(token):
+         * - Checks for restoreCallingIdentity() not in the finally block issue
+         * - Removes token from tokensMap if token is within the scope of the method
+         */
+        override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {
+            val token = findFirstTokenInScope(node)
+            if (isCallerAwareMethod(node) && token != null) {
+                context.report(
+                        ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
+                        context.getLocation(node),
+                        getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
+                                token.variableName,
+                                node.asRenderString()
+                        )
+                )
+                return
+            }
+            if (!isMethodCall(node, Method.BINDER_RESTORE_CALLING_IDENTITY)) return
+            val selector = node.selector as UCallExpression
+            val arg = selector.valueArguments[0] as? USimpleNameReferenceExpression ?: return
+            val variableName = arg.identifier
+            if (!isInFinallyBlock(node)) {
+                context.report(
+                        ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+                        context.getLocation(node),
+                        getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName)
+                )
+            }
+            val originalScope = tokensMap[variableName]?.scope ?: return
+            val psi = arg.sourcePsi ?: return
+            if (PsiSearchScopeUtil.isInScope(originalScope, psi)) {
+                tokensMap.remove(variableName)
+            }
+        }
+
+        private fun isCallerAwareMethod(expression: UQualifiedReferenceExpression): Boolean =
+                callerAwareMethods.any { method -> isMethodCall(expression, method) }
+
+        private fun isMethodCall(
+            expression: UQualifiedReferenceExpression,
+            method: Method
+        ): Boolean {
+            val psiMethod = expression.resolve() as? PsiMethod ?: return false
+            return psiMethod.getName() == method.methodName &&
+                    context.evaluator.methodMatches(
+                            psiMethod,
+                            method.className,
+                            /* allowInherit */ true,
+                            *method.args
+                    )
+        }
+    }
+
+    private fun isInFinallyBlock(expression: UExpression): Boolean {
+        val tryExpression = expression.getParentOfType<UTryExpression>(strict = true)
+                ?: return false
+        return expression.isUastChildOf(tryExpression.finallyClause)
+    }
+
+    private fun findFirstTokenInScope(node: UElement): Token? {
+        val psi = node.sourcePsi ?: return null
+        for (token in tokensMap.values) {
+            if (token.scope != null && PsiSearchScopeUtil.isInScope(token.scope, psi)) {
+                return token
+            }
+        }
+        return null
+    }
+
+    /**
+     * Creates a new instance of the primary location with the secondary location
+     *
+     * Here, secondary location is the helper location that shows where the issue originated
+     *
+     * The detector reports locations as objects, so when we add a secondary location to a location
+     * that has multiple issues, the secondary location gets displayed every time a location is
+     * referenced.
+     *
+     * Example:
+     * 1: final long token1 = Binder.clearCallingIdentity();
+     * 2: long token2 = Binder.clearCallingIdentity();
+     * 3: Binder.restoreCallingIdentity(token1);
+     * 4: Binder.restoreCallingIdentity(token2);
+     *
+     * Explanation:
+     * token2 has 2 issues: NonFinal and NestedCalls
+     *
+     *     Lint report without cloning                        Lint report with cloning
+     * line 2: [NonFinalIssue]                            line 2: [NonFinalIssue]
+     *     line 1: [NestedCallsIssue]
+     * line 2: [NestedCallsIssue]                            line 2: [NestedCallsIssue]
+     *     line 1: [NestedCallsIssue]                           line 1: [NestedCallsIssue]
+     */
+    private fun createNestedLocation(
+        firstCallToken: Token,
+        secondCallTokenLocation: Location
+    ): Location {
+        return cloneLocation(secondCallTokenLocation)
+                .withSecondary(
+                        cloneLocation(firstCallToken.location),
+                        getIncidentMessageNestedClearIdentityCallsSecondary(
+                                firstCallToken.variableName
+                        )
+                )
+    }
+
+    private fun cloneLocation(location: Location): Location {
+        // smart cast of location.start to 'Position' is impossible, because 'location.start' is a
+        // public API property declared in different module
+        val locationStart = location.start
+        return if (locationStart == null) {
+            Location.create(location.file)
+        } else {
+            Location.create(location.file, locationStart, location.end)
+        }
+    }
+
+    private enum class Method(
+        val className: String,
+        val methodName: String,
+        val args: Array<String>
+    ) {
+        BINDER_CLEAR_CALLING_IDENTITY(CLASS_BINDER, "clearCallingIdentity", emptyArray()),
+        BINDER_RESTORE_CALLING_IDENTITY(CLASS_BINDER, "restoreCallingIdentity", arrayOf("long")),
+        BINDER_GET_CALLING_PID(CLASS_BINDER, "getCallingPid", emptyArray()),
+        BINDER_GET_CALLING_UID(CLASS_BINDER, "getCallingUid", emptyArray()),
+        BINDER_GET_CALLING_UID_OR_THROW(CLASS_BINDER, "getCallingUidOrThrow", emptyArray()),
+        BINDER_GET_CALLING_USER_HANDLE(CLASS_BINDER, "getCallingUserHandle", emptyArray()),
+        USER_HANDLE_GET_CALLING_APP_ID(CLASS_USER_HANDLE, "getCallingAppId", emptyArray()),
+        USER_HANDLE_GET_CALLING_USER_ID(CLASS_USER_HANDLE, "getCallingUserId", emptyArray())
+    }
+
+    private data class Token(
+        val variableName: String,
+        val scope: SearchScope?,
+        val location: Location
+    )
+}
diff --git a/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenIssueRegistry.kt b/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenIssueRegistry.kt
new file mode 100644
index 0000000..a0e9249
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenIssueRegistry.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+// TODO: uncomment when lint API in Soong becomes 30.0+
+// import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class CallingIdentityTokenIssueRegistry : IssueRegistry() {
+    override val issues = listOf(
+            ISSUE_UNUSED_TOKEN,
+            ISSUE_NON_FINAL_TOKEN,
+            ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+            ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+            ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
+    )
+
+    override val api: Int
+        get() = CURRENT_API
+
+    override val minApi: Int
+        get() = 8
+
+//    TODO: uncomment when lint API in Soong becomes 30.0+
+//    override val vendor: Vendor = Vendor(
+//            vendorName = "Android Open Source Project",
+//            feedbackUrl = "http://b/issues/new?component=315013",
+//            contact = "brufino@google.com"
+//    )
+
+    companion object {
+        /** Issue: unused token from Binder.clearCallingIdentity() */
+        @JvmField
+        val ISSUE_UNUSED_TOKEN: Issue = Issue.create(
+                id = "UnusedTokenOfOriginalCallingIdentity",
+                briefDescription = "Unused token of Binder.clearCallingIdentity()",
+                explanation = """
+                    You cleared the original calling identity with \
+                    `Binder.clearCallingIdentity()`, but have not used the returned token to \
+                    restore the identity.
+
+                    Call `Binder.restoreCallingIdentity(token)` in the `finally` block, at the end \
+                    of the method or when you need to restore the identity.
+
+                    `token` is the result of `Binder.clearCallingIdentity()`
+                    """,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        CallingIdentityTokenDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        fun getIncidentMessageUnusedToken(variableName: String) = "`$variableName` has not been " +
+                "used to restore the calling identity. Call " +
+                "`Binder.restoreCallingIdentity($variableName)` or remove `$variableName`."
+
+        /** Issue: non-final token from Binder.clearCallingIdentity() */
+        @JvmField
+        val ISSUE_NON_FINAL_TOKEN: Issue = Issue.create(
+                id = "NonFinalTokenOfOriginalCallingIdentity",
+                briefDescription = "Non-final token of Binder.clearCallingIdentity()",
+                explanation = """
+                    You cleared the original calling identity with \
+                    `Binder.clearCallingIdentity()`, but have not made the returned token `final`.
+
+                    The token should be `final` in order to prevent it from being overwritten, \
+                    which can cause problems when restoring the identity with \
+                    `Binder.restoreCallingIdentity(token)`.
+                    """,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        CallingIdentityTokenDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        fun getIncidentMessageNonFinalToken(variableName: String) = "`$variableName` is a " +
+                "non-final token from `Binder.clearCallingIdentity()`. Add `final` keyword to " +
+                "`$variableName`."
+
+        /** Issue: nested calls of Binder.clearCallingIdentity() */
+        @JvmField
+        val ISSUE_NESTED_CLEAR_IDENTITY_CALLS: Issue = Issue.create(
+                id = "NestedClearCallingIdentityCalls",
+                briefDescription = "Nested calls of Binder.clearCallingIdentity()",
+                explanation = """
+                    You cleared the original calling identity with \
+                    `Binder.clearCallingIdentity()` twice without restoring identity with the \
+                    result of the first call.
+
+                    Make sure to restore the identity after each clear identity call.
+                    """,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        CallingIdentityTokenDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        fun getIncidentMessageNestedClearIdentityCallsPrimary(
+            firstCallVariableName: String,
+            secondCallVariableName: String
+        ): String = "The calling identity has already been cleared and returned into " +
+                "`$firstCallVariableName`. Move `$secondCallVariableName` declaration after " +
+                "restoring the calling identity with " +
+                "`Binder.restoreCallingIdentity($firstCallVariableName)`."
+
+        fun getIncidentMessageNestedClearIdentityCallsSecondary(
+            firstCallVariableName: String
+        ): String = "Location of the `$firstCallVariableName` declaration."
+
+        /** Issue: Binder.restoreCallingIdentity() is not in finally block */
+        @JvmField
+        val ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK: Issue = Issue.create(
+                id = "RestoreIdentityCallNotInFinallyBlock",
+                briefDescription = "Binder.restoreCallingIdentity() is not in finally block",
+                explanation = """
+                    You are restoring the original calling identity with \
+                    `Binder.restoreCallingIdentity()`, but the call is not in the `finally` block \
+                    of the `try` statement.
+
+                    Use the following pattern for running operations with your own identity:
+
+                    ```
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        // Code using your own identity
+                    } finally {
+                        Binder.restoreCallingIdentity();
+                    }
+                    ```
+
+                    If you do not surround the code using your identity with the `try` statement \
+                    and call `Binder.restoreCallingIdentity()` in the `finally` block, you may run \
+                    code with your identity that was originally intended to run with the calling \
+                    application's identity.
+                    """,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        CallingIdentityTokenDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        fun getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName: String): String =
+                "`Binder.restoreCallingIdentity($variableName)` is not in the `finally` block. " +
+                        "Surround the call with `finally` block."
+
+        /** Issue: Use of caller-aware methods after Binder.clearCallingIdentity() */
+        @JvmField
+        val ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY: Issue = Issue.create(
+                id = "UseOfCallerAwareMethodsWithClearedIdentity",
+                briefDescription = "Use of caller-aware methods after " +
+                        "Binder.clearCallingIdentity()",
+                explanation = """
+                    You cleared the original calling identity with \
+                    `Binder.clearCallingIdentity()`, but used one of the methods below before \
+                    restoring the identity. These methods will use your own identity instead of \
+                    the caller's identity, so if this is expected replace them with methods that \
+                    explicitly query your own identity such as `Process.myUid()`, \
+                    `Process.myPid()` and `UserHandle.myUserId()`, otherwise move those methods \
+                    out of the `Binder.clearCallingIdentity()` / `Binder.restoreCallingIdentity()` \
+                    section.
+
+                    ```
+                    Binder.getCallingPid()
+                    Binder.getCallingUid()
+                    Binder.getCallingUidOrThrow()
+                    Binder.getCallingUserHandle()
+                    UserHandle.getCallingAppId()
+                    UserHandle.getCallingUserId()
+                    ```
+                    """,
+                category = Category.SECURITY,
+                priority = 6,
+                severity = Severity.WARNING,
+                implementation = Implementation(
+                        CallingIdentityTokenDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE
+                )
+        )
+
+        fun getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
+            variableName: String,
+            methodName: String
+        ): String = "You cleared the original identity with `Binder.clearCallingIdentity()` " +
+                "and returned into `$variableName`, so `$methodName` will be using your own " +
+                "identity instead of the caller's. Either explicitly query your own identity or " +
+                "move it after restoring the identity with " +
+                "`Binder.restoreCallingIdentity($variableName)`."
+    }
+}
diff --git a/tools/lint/checks/src/test/java/com/android/lint/CallingIdentityTokenDetectorTest.kt b/tools/lint/checks/src/test/java/com/android/lint/CallingIdentityTokenDetectorTest.kt
new file mode 100644
index 0000000..8a81609
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/android/lint/CallingIdentityTokenDetectorTest.kt
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class CallingIdentityTokenDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = CallingIdentityTokenDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+            CallingIdentityTokenIssueRegistry.ISSUE_UNUSED_TOKEN,
+            CallingIdentityTokenIssueRegistry.ISSUE_NON_FINAL_TOKEN,
+            CallingIdentityTokenIssueRegistry.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+            CallingIdentityTokenIssueRegistry.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+            CallingIdentityTokenIssueRegistry
+                    .ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
+    )
+
+    /** No issue scenario */
+
+    fun testDoesNotDetectIssuesInCorrectScenario() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod() {
+                            final long token1 = Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                Binder.restoreCallingIdentity(token1);
+                            }
+                            final long token2 = android.os.Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                android.os.Binder.restoreCallingIdentity(token2);
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    /** Unused token issue tests */
+
+    fun testDetectsUnusedTokens() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod1() {
+                            final long token1 = Binder.clearCallingIdentity();
+                        }
+                        private void testMethod2() {
+                            final long token2 = android.os.Binder.clearCallingIdentity();
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:5: Warning: token1 has not been used to \
+                        restore the calling identity. Call Binder.restoreCallingIdentity(token1) \
+                        or remove token1. [UnusedTokenOfOriginalCallingIdentity]
+                                final long token1 = Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:8: Warning: token2 has not been used to \
+                        restore the calling identity. Call Binder.restoreCallingIdentity(token2) \
+                        or remove token2. [UnusedTokenOfOriginalCallingIdentity]
+                                final long token2 = android.os.Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 2 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDetectsUnusedTokensInScopes() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod1() {
+                            final long token = Binder.clearCallingIdentity();
+                        }
+                        private void testMethod2() {
+                            long token = 0;
+                            try {
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:5: Warning: token has not been used to \
+                        restore the calling identity. Call Binder.restoreCallingIdentity(token) \
+                        or remove token. [UnusedTokenOfOriginalCallingIdentity]
+                                final long token = Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDoesNotDetectUsedTokensInScopes() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod1() {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                        private void testMethod2() {
+                            long token = 0;
+                            try {
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    fun testDetectsUnusedTokensWithSimilarNamesInScopes() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod1() {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                            }
+                        }
+                        private void testMethod2() {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:5: Warning: token has not been used to \
+                        restore the calling identity. Call Binder.restoreCallingIdentity(token) \
+                        or remove token. [UnusedTokenOfOriginalCallingIdentity]
+                                final long token = Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:11: Warning: token has not been used to \
+                        restore the calling identity. Call Binder.restoreCallingIdentity(token) \
+                        or remove token. [UnusedTokenOfOriginalCallingIdentity]
+                                final long token = Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 2 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    /** Non-final token issue tests */
+
+    fun testDetectsNonFinalTokens() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod() {
+                            long token1 = Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                Binder.restoreCallingIdentity(token1);
+                            }
+                            long token2 = android.os.Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                android.os.Binder.restoreCallingIdentity(token2);
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:5: Warning: token1 is a non-final token from \
+                        Binder.clearCallingIdentity(). Add final keyword to token1. \
+                        [NonFinalTokenOfOriginalCallingIdentity]
+                                long token1 = Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:10: Warning: token2 is a non-final token from \
+                        Binder.clearCallingIdentity(). Add final keyword to token2. \
+                        [NonFinalTokenOfOriginalCallingIdentity]
+                                long token2 = android.os.Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 2 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    /** Nested clearCallingIdentity() calls issue tests */
+
+    fun testDetectsNestedClearCallingIdentityCallsPattern1() {
+        // Pattern: clear - clear - restore - clear - restore - restore
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod() {
+                            final long token1 = Binder.clearCallingIdentity();
+                            final long token2 = android.os.Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                android.os.Binder.restoreCallingIdentity(token1);
+                            }
+                            final long token3 = android.os.Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                android.os.Binder.restoreCallingIdentity(token2);
+                            }
+                            try {
+                            } finally {
+                                android.os.Binder.restoreCallingIdentity(token3);
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:6: Warning: The calling identity has already \
+                        been cleared and returned into token1. Move token2 declaration after \
+                        restoring the calling identity with Binder.restoreCallingIdentity(token1). \
+                        [NestedClearCallingIdentityCalls]
+                                final long token2 = android.os.Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                            src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
+                        src/test/pkg/TestClass1.java:11: Warning: The calling identity has already \
+                        been cleared and returned into token2. Move token3 declaration after \
+                        restoring the calling identity with Binder.restoreCallingIdentity(token2). \
+                        [NestedClearCallingIdentityCalls]
+                                final long token3 = android.os.Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                            src/test/pkg/TestClass1.java:6: Location of the token2 declaration.
+                        0 errors, 2 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDetectsNestedClearCallingIdentityCallsPattern2() {
+        // Pattern: clear - clear - clear - restore - restore - restore
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod() {
+                            final long token1 = Binder.clearCallingIdentity();
+                            final long token2 = android.os.Binder.clearCallingIdentity();
+                            final long token3 = android.os.Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                Binder.restoreCallingIdentity(token1);
+                            }
+                            try {
+                            } finally {
+                                android.os.Binder.restoreCallingIdentity(token2);
+                            }
+                            try {
+                            } finally {
+                                android.os.Binder.restoreCallingIdentity(token3);
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:6: Warning: The calling identity has already \
+                        been cleared and returned into token1. Move token2 declaration after \
+                        restoring the calling identity with Binder.restoreCallingIdentity(token1). \
+                        [NestedClearCallingIdentityCalls]
+                                final long token2 = android.os.Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                            src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
+                        src/test/pkg/TestClass1.java:7: Warning: The calling identity has already \
+                        been cleared and returned into token1. Move token3 declaration after \
+                        restoring the calling identity with Binder.restoreCallingIdentity(token1). \
+                        [NestedClearCallingIdentityCalls]
+                                final long token3 = android.os.Binder.clearCallingIdentity();
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                            src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
+                        0 errors, 2 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDetectsNestedClearCallingIdentityCallsInScopes() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod() {
+                            final long token1 = Binder.clearCallingIdentity();
+                            try {
+                                final long token2 = android.os.Binder.clearCallingIdentity();
+                                try {
+                                } finally {
+                                    Binder.restoreCallingIdentity(token2);
+                                }
+                            } finally {
+                                android.os.Binder.restoreCallingIdentity(token1);
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:7: Warning: The calling identity has already \
+                        been cleared and returned into token1. Move token2 declaration after \
+                        restoring the calling identity with Binder.restoreCallingIdentity(token1). \
+                        [NestedClearCallingIdentityCalls]
+                                    final long token2 = android.os.Binder.clearCallingIdentity();
+                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                            src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
+                        0 errors, 1 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    /** restoreCallingIdentity() call not in finally block issue tests */
+
+    fun testDetectsRestoreCallingIdentityCallNotInFinally() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod1() {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                            } catch (Exception e) {
+                            }
+                            Binder.restoreCallingIdentity(token);
+                            }
+                            private void testMethod2() {
+                            final long token = android.os.Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                            }
+                            android.os.Binder.restoreCallingIdentity(token);
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:9: Warning: \
+                        Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
+                        the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
+                                Binder.restoreCallingIdentity(token);
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:16: Warning: \
+                        Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
+                        the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
+                                android.os.Binder.restoreCallingIdentity(token);
+                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 2 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDetectsRestoreCallingIdentityCallNotInFinallyInScopes() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod1() {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                            } catch (Exception e) {
+                            }
+                            {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                        private void testMethod2() {
+                            final long token = android.os.Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                            }
+                            {
+                                {
+                                    {
+                                        android.os.Binder.restoreCallingIdentity(token);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:10: Warning: \
+                        Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
+                        the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
+                                    Binder.restoreCallingIdentity(token);
+                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:21: Warning: \
+                        Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
+                        the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
+                                            android.os.Binder.restoreCallingIdentity(token);
+                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 2 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    fun testDoesNotDetectRestoreCallingIdentityCallInFinallyInScopes() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    public class TestClass1 {
+                        private void testMethod() {
+                            final long token1 = Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                {
+                                    {
+                                        Binder.restoreCallingIdentity(token1);
+                                    }
+                                }
+                            }
+                            final long token2 = android.os.Binder.clearCallingIdentity();
+                            try {
+                            } finally {
+                                {
+                                    {
+                                        android.os.Binder.restoreCallingIdentity(token2);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    /** Use of caller-aware methods after clearCallingIdentity() issue tests */
+
+    fun testDetectsUseOfCallerAwareMethodsWithClearedIdentityIssuesInScopes() {
+        lint().files(
+                java(
+                    """
+                    package test.pkg;
+                    import android.os.Binder;
+                    import android.os.UserHandle;
+                    public class TestClass1 {
+                        private void testMethod() {
+                            final long token = Binder.clearCallingIdentity();
+                            int pid1 = Binder.getCallingPid();
+                            int pid2 = android.os.Binder.getCallingPid();
+                            int uid1 = Binder.getCallingUid();
+                            int uid2 = android.os.Binder.getCallingUid();
+                            try {
+                                int uid3 = Binder.getCallingUidOrThrow();
+                                int uid4 = android.os.Binder.getCallingUidOrThrow();
+                                UserHandle uh1 = Binder.getCallingUserHandle();
+                                UserHandle uh2 = android.os.Binder.getCallingUserHandle();
+                                {
+                                    int appId1 = UserHandle.getCallingAppId();
+                                    int appId2 = android.os.UserHandle.getCallingAppId();
+                                    int userId1 = UserHandle.getCallingUserId();
+                                    int userId2 = android.os.UserHandle.getCallingUserId();
+                                }
+                            } finally {
+                            Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                        """
+                        src/test/pkg/TestClass1.java:7: Warning: You cleared the original identity \
+                        with Binder.clearCallingIdentity() and returned into token, so \
+                        Binder.getCallingPid() will be using your own identity instead of the \
+                        caller's. Either explicitly query your own identity or move it after \
+                        restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                int pid1 = Binder.getCallingPid();
+                                           ~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:8: Warning: You cleared the original identity \
+                        with Binder.clearCallingIdentity() and returned into token, so \
+                        android.os.Binder.getCallingPid() will be using your own identity instead \
+                        of the caller's. Either explicitly query your own identity or move it \
+                        after restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                int pid2 = android.os.Binder.getCallingPid();
+                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:9: Warning: You cleared the original identity \
+                        with Binder.clearCallingIdentity() and returned into token, so \
+                        Binder.getCallingUid() will be using your own identity instead of the \
+                        caller's. Either explicitly query your own identity or move it after \
+                        restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                int uid1 = Binder.getCallingUid();
+                                           ~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:10: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        android.os.Binder.getCallingUid() will be using your own identity instead \
+                        of the caller's. Either explicitly query your own identity or move it \
+                        after restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                int uid2 = android.os.Binder.getCallingUid();
+                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:12: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        Binder.getCallingUidOrThrow() will be using your own identity instead of \
+                        the caller's. Either explicitly query your own identity or move it after \
+                        restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                    int uid3 = Binder.getCallingUidOrThrow();
+                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:13: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        android.os.Binder.getCallingUidOrThrow() will be using your own identity \
+                        instead of the caller's. Either explicitly query your own identity or move \
+                        it after restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                    int uid4 = android.os.Binder.getCallingUidOrThrow();
+                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:14: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        Binder.getCallingUserHandle() will be using your own identity instead of \
+                        the caller's. Either explicitly query your own identity or move it after \
+                        restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                    UserHandle uh1 = Binder.getCallingUserHandle();
+                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:15: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        android.os.Binder.getCallingUserHandle() will be using your own identity \
+                        instead of the caller's. Either explicitly query your own identity or move \
+                        it after restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                    UserHandle uh2 = android.os.Binder.getCallingUserHandle();
+                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:17: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        UserHandle.getCallingAppId() will be using your own identity instead of \
+                        the caller's. Either explicitly query your own identity or move it after \
+                        restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                        int appId1 = UserHandle.getCallingAppId();
+                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:18: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        android.os.UserHandle.getCallingAppId() will be using your own identity \
+                        instead of the caller's. Either explicitly query your own identity or move \
+                        it after restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                        int appId2 = android.os.UserHandle.getCallingAppId();
+                                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:19: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        UserHandle.getCallingUserId() will be using your own identity instead of \
+                        the caller's. Either explicitly query your own identity or move it after \
+                        restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                        int userId1 = UserHandle.getCallingUserId();
+                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        src/test/pkg/TestClass1.java:20: Warning: You cleared the original \
+                        identity with Binder.clearCallingIdentity() and returned into token, so \
+                        android.os.UserHandle.getCallingUserId() will be using your own identity \
+                        instead of the caller's. Either explicitly query your own identity or move \
+                        it after restoring the identity with Binder.restoreCallingIdentity(token). \
+                        [UseOfCallerAwareMethodsWithClearedIdentity]
+                                        int userId2 = android.os.UserHandle.getCallingUserId();
+                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                        0 errors, 12 warnings
+                        """.addLineContinuation()
+                )
+    }
+
+    /** Stubs for classes used for testing */
+
+    private val binderStub: TestFile = java(
+            """
+            package android.os;
+            public class Binder {
+                public static final native long clearCallingIdentity() {
+                    return 0;
+                }
+                public static final native void restoreCallingIdentity(long token) {
+                }
+                public static final native int getCallingPid() {
+                    return 0;
+                }
+                public static final native int getCallingUid() {
+                    return 0;
+                }
+                public static final int getCallingUidOrThrow() {
+                    return 0;
+                }
+                public static final @NonNull UserHandle getCallingUserHandle() {
+                    return UserHandle.of(UserHandle.getUserId(getCallingUid()));
+                }
+            }
+            """
+    ).indented()
+
+    private val userHandleStub: TestFile = java(
+            """
+            package android.os;
+            import android.annotation.AppIdInt;
+            import android.annotation.UserIdInt;
+            public class UserHandle {
+                public static @AppIdInt int getCallingAppId() {
+                    return getAppId(Binder.getCallingUid());
+                }
+                public static @UserIdInt int getCallingUserId() {
+                    return getUserId(Binder.getCallingUid());
+                }
+                public static @UserIdInt int getUserId(int uid) {
+                    return 0;
+                }
+                public static @AppIdInt int getAppId(int uid) {
+                    return 0;
+                }
+                public static UserHandle of(@UserIdInt int userId) {
+                    return new UserHandle();
+                }
+            }
+            """
+    ).indented()
+
+    private val userIdIntStub: TestFile = java(
+            """
+            package android.annotation;
+            public @interface UserIdInt {
+            }
+            """
+    ).indented()
+
+    private val appIdIntStub: TestFile = java(
+            """
+            package android.annotation;
+            public @interface AppIdInt {
+            }
+            """
+    ).indented()
+
+    private val stubs = arrayOf(binderStub, userHandleStub, userIdIntStub, appIdIntStub)
+
+    // Substitutes "backslash + new line" with an empty string to imitate line continuation
+    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}