Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d1c1c260 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Adding multiuser pendingintent linting to default Android linting" into...

Merge "Adding multiuser pendingintent linting to default Android linting" into main am: ddef60b6 am: 27047a29

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/3416160



Change-Id: I17b1e4c9077fc60d700132441e00cacc2cea0f30
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 11611d23 27047a29
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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
+107 −0
Original line number Diff line number Diff line
/*
 * 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(...)`.",
        )
    }
  }
}
+173 −0
Original line number Diff line number Diff line
/*
 * 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)
}