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

Commit 56d14cd7 authored by Peter Kalauskas's avatar Peter Kalauskas
Browse files

New detector for getCurrentUser and getUserInfo

Detect slow calls to ActivityManager.getCurrentUser() or
UserManager.getUserInfo() and suggest using UserTracker instead.
For more info, see: http://go/multi-user-in-systemui-slides.

Test: manual
Bug: 238923086
Change-Id: I04a79ab3836a35a687d31af9e566a28d89aaeae6
parent f8f1306e
Loading
Loading
Loading
Loading
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.internal.systemui.lint

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 org.jetbrains.uast.UCallExpression

/**
 * Checks for slow calls to ActivityManager.getCurrentUser() or UserManager.getUserInfo() and
 * suggests using UserTracker instead. For more info, see: http://go/multi-user-in-systemui-slides.
 */
@Suppress("UnstableApiUsage")
class SlowUserQueryDetector : Detector(), SourceCodeScanner {

    override fun getApplicableMethodNames(): List<String> {
        return listOf("getCurrentUser", "getUserInfo")
    }

    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
        val evaluator = context.evaluator
        if (
            evaluator.isStatic(method) &&
                method.name == "getCurrentUser" &&
                method.containingClass?.qualifiedName == "android.app.ActivityManager"
        ) {
            context.report(
                ISSUE_SLOW_USER_ID_QUERY,
                method,
                context.getNameLocation(node),
                "ActivityManager.getCurrentUser() is slow. " +
                    "Use UserTracker.getUserId() instead."
            )
        }
        if (
            !evaluator.isStatic(method) &&
                method.name == "getUserInfo" &&
                method.containingClass?.qualifiedName == "android.os.UserManager"
        ) {
            context.report(
                ISSUE_SLOW_USER_INFO_QUERY,
                method,
                context.getNameLocation(node),
                "UserManager.getUserInfo() is slow. " + "Use UserTracker.getUserInfo() instead."
            )
        }
    }

    companion object {
        @JvmField
        val ISSUE_SLOW_USER_ID_QUERY: Issue =
            Issue.create(
                id = "SlowUserIdQuery",
                briefDescription = "User ID queried using ActivityManager instead of UserTracker.",
                explanation =
                    "ActivityManager.getCurrentUser() makes a binder call and is slow. " +
                        "Instead, inject a UserTracker and call UserTracker.getUserId(). For " +
                        "more info, see: http://go/multi-user-in-systemui-slides",
                category = Category.PERFORMANCE,
                priority = 8,
                severity = Severity.WARNING,
                implementation =
                    Implementation(SlowUserQueryDetector::class.java, Scope.JAVA_FILE_SCOPE)
            )

        @JvmField
        val ISSUE_SLOW_USER_INFO_QUERY: Issue =
            Issue.create(
                id = "SlowUserInfoQuery",
                briefDescription = "User info queried using UserManager instead of UserTracker.",
                explanation =
                    "UserManager.getUserInfo() makes a binder call and is slow. " +
                        "Instead, inject a UserTracker and call UserTracker.getUserInfo(). For " +
                        "more info, see: http://go/multi-user-in-systemui-slides",
                category = Category.PERFORMANCE,
                priority = 8,
                severity = Severity.WARNING,
                implementation =
                    Implementation(SlowUserQueryDetector::class.java, Scope.JAVA_FILE_SCOPE)
            )
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ class SystemUIIssueRegistry : IssueRegistry() {
        get() = listOf(
                BindServiceViaContextDetector.ISSUE,
                BroadcastSentViaContextDetector.ISSUE,
                SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
                SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY,
                GetMainLooperViaContextDetector.ISSUE,
                RegisterReceiverViaContextDetector.ISSUE,
                SoftwareBitmapDetector.ISSUE,
+194 −0
Original line number Diff line number Diff line
package com.android.internal.systemui.lint

import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test

class SlowUserQueryDetectorTest : LintDetectorTest() {

    override fun getDetector(): Detector = SlowUserQueryDetector()
    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)

    override fun getIssues(): List<Issue> =
        listOf(
            SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
            SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
        )

    @Test
    fun testGetCurrentUser() {
        lint()
            .files(
                TestFiles.java(
                        """
                        package test.pkg;
                        import android.app.ActivityManager;

                        public class TestClass1 {
                            public void slewlyGetCurrentUser() {
                                ActivityManager.getCurrentUser();
                            }
                        }
                        """
                    )
                    .indented(),
                *stubs
            )
            .issues(
                SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
                SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
            )
            .run()
            .expectWarningCount(1)
            .expectContains(
                "ActivityManager.getCurrentUser() is slow. " +
                    "Use UserTracker.getUserId() instead."
            )
    }

    @Test
    fun testGetUserInfo() {
        lint()
            .files(
                TestFiles.java(
                        """
                        package test.pkg;
                        import android.os.UserManager;

                        public class TestClass2 {
                            public void slewlyGetUserInfo(UserManager userManager) {
                                userManager.getUserInfo();
                            }
                        }
                        """
                    )
                    .indented(),
                *stubs
            )
            .issues(
                SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
                SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
            )
            .run()
            .expectWarningCount(1)
            .expectContains(
                "UserManager.getUserInfo() is slow. " + "Use UserTracker.getUserInfo() instead."
            )
    }

    @Test
    fun testUserTrackerGetUserId() {
        lint()
            .files(
                TestFiles.java(
                        """
                        package test.pkg;
                        import com.android.systemui.settings.UserTracker;

                        public class TestClass3 {
                            public void quicklyGetUserId(UserTracker userTracker) {
                                userTracker.getUserId();
                            }
                        }
                        """
                    )
                    .indented(),
                *stubs
            )
            .issues(
                SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
                SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
            )
            .run()
            .expectClean()
    }

    @Test
    fun testUserTrackerGetUserInfo() {
        lint()
            .files(
                TestFiles.java(
                        """
                        package test.pkg;
                        import com.android.systemui.settings.UserTracker;

                        public class TestClass4 {
                            public void quicklyGetUserId(UserTracker userTracker) {
                                userTracker.getUserInfo();
                            }
                        }
                        """
                    )
                    .indented(),
                *stubs
            )
            .issues(
                SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
                SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
            )
            .run()
            .expectClean()
    }

    private val activityManagerStub: TestFile =
        java(
            """
            package android.app;

            public class ActivityManager {
                public static int getCurrentUser() {};
            }
            """
        )

    private val userManagerStub: TestFile =
        java(
            """
            package android.os;
            import android.content.pm.UserInfo;
            import android.annotation.UserIdInt;

            public class UserManager {
                public UserInfo getUserInfo(@UserIdInt int userId) {};
            }
            """
        )

    private val userIdIntStub: TestFile =
        java(
            """
            package android.annotation;

            public @interface UserIdInt {}
            """
        )

    private val userInfoStub: TestFile =
        java(
            """
            package android.content.pm;

            public class UserInfo {}
            """
        )

    private val userTrackerStub: TestFile =
        java(
            """
            package com.android.systemui.settings;
            import android.content.pm.UserInfo;

            public interface UserTracker {
                public int getUserId();
                public UserInfo getUserInfo();
            }
            """
        )

    private val stubs =
        arrayOf(activityManagerStub, userManagerStub, userIdIntStub, userInfoStub, userTrackerStub)
}