diff options
author | 2025-01-21 04:43:21 -0800 | |
---|---|---|
committer | 2025-01-21 04:43:21 -0800 | |
commit | d1c1c260eeaba2ccab33e65b001a70df88930d87 (patch) | |
tree | 8db84fe48454f017f8aa2ba1eeb63ac88a2fd0b1 /tools | |
parent | 11611d23ba70335c849c505975e809c697e9b7e5 (diff) | |
parent | 27047a29c96791777415a21f27bd6107d2144e91 (diff) |
Merge "Adding multiuser pendingintent linting to default Android linting" into main am: ddef60b612 am: 27047a29c9
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/3416160
Change-Id: I17b1e4c9077fc60d700132441e00cacc2cea0f30
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Diffstat (limited to 'tools')
3 files changed, 282 insertions, 0 deletions
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index af753e5963a3..b62843ca3ff4 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -19,6 +19,7 @@ package com.google.android.lint import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API +import com.google.android.lint.multiuser.PendingIntentGetActivityDetector import com.google.android.lint.parcel.SaferParcelChecker import com.google.auto.service.AutoService @@ -40,6 +41,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, FeatureAutomotiveDetector.ISSUE, + PendingIntentGetActivityDetector.ISSUE_PENDING_INTENT_GET_ACTIVITY, ) override val api: Int diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetector.kt new file mode 100644 index 000000000000..b9f22ebfa8ec --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetector.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2025 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.google.android.lint.multiuser + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiMethod +import java.util.EnumSet +import org.jetbrains.uast.UCallExpression + +/** + * Detector for flagging potential multiuser issues in `PendingIntent.getActivity()` calls. + * + * This detector checks for calls to `PendingIntent#getActivity()` and + * reports a warning if such a call is found, suggesting that the + * default user 0 context might not be the right one. + */ +class PendingIntentGetActivityDetector : Detector(), SourceCodeScanner { + + companion object { + + val description = """Flags potential multiuser issue in PendingIntent.getActivity() calls.""" + + val EXPLANATION = + """ + **Problem:** + + Calling `PendingIntent.getActivity()` in the `system_server` often accidentally uses the user 0 context. Moreover, since there's no explicit user parameter in the `getActivity` method, it can be hard to tell which user the `PendingIntent` activity is associated with, making the code error-prone and less readable. + + **Solution:** + + Always use the user aware methods to refer the correct user context. You can achieve this by: + + * **Using `PendingIntent.getActivityAsUser(...)`:** This API allows you to explicitly specify the user for the activity. + + ```java + PendingIntent.getActivityAsUser( + mContext, /*requestCode=*/0, intent, + PendingIntent.FLAG_IMMUTABLE, /*options=*/null, + UserHandle.of(mUserId)); + ``` + + **When to Ignore this Warning:** + + You can safely ignore this warning if you are certain that: + + * You've confirmed that the `PendingIntent` activity you're targeting is the correct one and is **rightly** associated with the context parameter passed into the `PendingIntent.getActivity` method. + + **Note:** If you are unsure about the user context, it's best to err on the side of caution and explicitly specify the user using the method specified above. + + **For any further questions, please reach out to go/multiuser-help.** + """.trimIndent() + + val ISSUE_PENDING_INTENT_GET_ACTIVITY: Issue = + Issue.create( + id = "PendingIntent#getActivity", + briefDescription = description, + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 8, + severity = Severity.WARNING, + implementation = + Implementation( + PendingIntentGetActivityDetector::class.java, + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES), + ), + ) + } + + override fun getApplicableMethodNames() = listOf("getActivity") + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + // Check if the method call is PendingIntent.getActivity + if ( + context.evaluator.isMemberInClass(method, "android.app.PendingIntent") && + method.name == "getActivity" + ) { + context.report( + ISSUE_PENDING_INTENT_GET_ACTIVITY, + node, + context.getLocation(node), + "Using `PendingIntent.getActivity(...)` might not be multiuser-aware. " + + "Consider using the user aware method `PendingIntent.getActivityAsUser(...)`.", + ) + } + } +} diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetectorTest.kt new file mode 100644 index 000000000000..401055055232 --- /dev/null +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetectorTest.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2025 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.google.android.lint.multiuser + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class PendingIntentGetActivityDetectorTest : LintDetectorTest() { + + override fun getDetector(): Detector = PendingIntentGetActivityDetector() + + override fun getIssues(): List<Issue> = + listOf(PendingIntentGetActivityDetector.ISSUE_PENDING_INTENT_GET_ACTIVITY) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun testPendingIntentGetActivity() { + lint() + .files( + java( + """ + package test.pkg; + + import android.app.PendingIntent; + import android.content.Context; + import android.content.Intent; + + public class TestClass { + private Context mContext; + + public void testMethod(Intent intent) { + PendingIntent.getActivity( + mContext, /*requestCode=*/0, intent, + PendingIntent.FLAG_IMMUTABLE, /*options=*/null + ); + } + } + """ + ) + .indented(), + *stubs, + ) + .issues(PendingIntentGetActivityDetector.ISSUE_PENDING_INTENT_GET_ACTIVITY) + .run() + .expect( + """ + src/test/pkg/TestClass.java:11: Warning: Using PendingIntent.getActivity(...) might not be multiuser-aware. Consider using the user aware method PendingIntent.getActivityAsUser(...). [PendingIntent#getActivity] + PendingIntent.getActivity( + ^ + 0 errors, 1 warnings + """ + ) + } + + fun testPendingIntentGetActivityAsUser() { + lint() + .files( + java( + """ + package test.pkg; + + import android.app.PendingIntent; + import android.content.Context; + import android.content.Intent; + import android.os.UserHandle; + + public class TestClass { + private Context mContext; + + public void testMethod(Intent intent) { + PendingIntent.getActivityAsUser( + mContext, /*requestCode=*/0, intent, + 0, /*options=*/null, + UserHandle.CURRENT + ); + } + } + """ + ) + .indented(), + *stubs, + ) + .issues(PendingIntentGetActivityDetector.ISSUE_PENDING_INTENT_GET_ACTIVITY) + .run() + .expectClean() + } + + private val pendingIntentStub: TestFile = + java( + """ + package android.app; + + import android.content.Context; + import android.content.Intent; + import android.os.UserHandle; + + public class PendingIntent { + public static boolean getActivity(Context context, int requestCode, Intent intent, int flags) { + return true; + } + + public static boolean getActivityAsUser( + Context context, + int requestCode, + Intent intent, + int flags, + UserHandle userHandle + ) { + return true; + } + } + """ + ) + + private val contxtStub: TestFile = + java( + """ + package android.content; + + import android.os.UserHandle; + + public class Context { + + public Context createContextAsUser(UserHandle userHandle, int flags) { + return this; + } + } + + """ + ) + + private val userHandleStub: TestFile = + java( + """ + package android.os; + + public class UserHandle { + + } + + """ + ) + + private val intentStub: TestFile = + java( + """ + package android.content; + + public class Intent { + + } + """ + ) + + private val stubs = arrayOf(pendingIntentStub, contxtStub, userHandleStub, intentStub) +} |