summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/Android.bp12
-rw-r--r--core/java/android/content/Context.java6
-rw-r--r--core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl37
-rw-r--r--core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl33
-rw-r--r--core/java/android/os/instrumentation/MethodDescriptor.aidl37
-rw-r--r--core/java/android/os/instrumentation/TargetProcess.aidl29
-rw-r--r--core/res/Android.bp1
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--data/etc/platform.xml2
-rw-r--r--native/android/Android.bp2
-rw-r--r--native/android/dynamic_instrumentation_manager.cpp173
-rw-r--r--native/android/include_platform/android/dynamic_instrumentation_manager.h132
-rw-r--r--native/android/libandroid.map.txt9
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java135
-rw-r--r--services/java/com/android/server/SystemServer.java8
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/Android.bp44
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml27
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING7
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java24
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java23
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java40
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java33
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java23
-rw-r--r--services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java143
28 files changed, 996 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
index 26d0d65f329c..2024c8afb5be 100644
--- a/Android.bp
+++ b/Android.bp
@@ -399,6 +399,7 @@ java_defaults {
"com.android.sysprop.foldlockbehavior",
"com.android.sysprop.view",
"framework-internal-utils",
+ "dynamic_instrumentation_manager_aidl-java",
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
// in favor of an API stubs dependency in java_library "framework" below.
"mimemap",
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2a01ca082832..2535d2dda003 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -138,6 +138,7 @@ package android {
field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
+ field @FlaggedApi("com.android.art.flags.executable_method_file_offsets") public static final String DYNAMIC_INSTRUMENTATION = "android.permission.DYNAMIC_INSTRUMENTATION";
field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 9875efe04361..81ed11ae9af0 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -28,6 +28,7 @@ filegroup {
exclude_srcs: [
"android/os/*MessageQueue/**/*.java",
"android/ranging/**/*.java",
+ ":dynamic_instrumentation_manager_aidl_sources",
],
visibility: ["//frameworks/base"],
}
@@ -120,6 +121,17 @@ filegroup {
}
filegroup {
+ name: "dynamic_instrumentation_manager_aidl_sources",
+ srcs: ["android/os/instrumentation/*.aidl"],
+}
+
+aidl_interface {
+ name: "dynamic_instrumentation_manager_aidl",
+ srcs: [":dynamic_instrumentation_manager_aidl_sources"],
+ unstable: true,
+}
+
+filegroup {
name: "framework-internal-display-sources",
srcs: ["com/android/internal/display/BrightnessSynchronizer.java"],
visibility: ["//frameworks/base/services/tests/mockingservicestests"],
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 186f7b3e111c..6086f2455a31 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6802,6 +6802,12 @@ public abstract class Context {
public static final String MEDIA_QUALITY_SERVICE = "media_quality";
/**
+ * Service to perform operations needed for dynamic instrumentation.
+ * @hide
+ */
+ public static final String DYNAMIC_INSTRUMENTATION_SERVICE = "dynamic_instrumentation";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl
new file mode 100644
index 000000000000..dbe54891b0f2
--- /dev/null
+++ b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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 android.os.instrumentation;
+
+/**
+ * Represents the location of the code for a compiled method within a process'
+ * memory.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable ExecutableMethodFileOffsets {
+ /**
+ * The OS path of the containing file (could be virtual).
+ */
+ @utf8InCpp String containerPath;
+ /**
+ * The offset of the containing file within the process' memory.
+ */
+ long containerOffset;
+ /**
+ * The offset of the method within the containing file.
+ */
+ long methodOffset;
+}
diff --git a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
new file mode 100644
index 000000000000..c45c51d15cc9
--- /dev/null
+++ b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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 android.os.instrumentation;
+
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.TargetProcess;
+
+/**
+ * System private API for managing the dynamic attachment of instrumentation.
+ *
+ * {@hide}
+ */
+interface IDynamicInstrumentationManager {
+ /** Provides ART metadata about the described compiled method within the target process */
+ @PermissionManuallyEnforced
+ @nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets(
+ in TargetProcess targetProcess, in MethodDescriptor methodDescriptor);
+}
diff --git a/core/java/android/os/instrumentation/MethodDescriptor.aidl b/core/java/android/os/instrumentation/MethodDescriptor.aidl
new file mode 100644
index 000000000000..055d0ecb66e4
--- /dev/null
+++ b/core/java/android/os/instrumentation/MethodDescriptor.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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 android.os.instrumentation;
+
+/**
+ * Represents a JVM method, where class fields that make up its signature.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable MethodDescriptor {
+ /**
+ * Fully qualified class in reverse.domain.Naming
+ */
+ @utf8InCpp String fullyQualifiedClassName;
+ /**
+ * Name of the method.
+ */
+ @utf8InCpp String methodName;
+ /**
+ * Fully qualified types of method parameters, or string representations if primitive e.g. "int".
+ */
+ @utf8InCpp String[] fullyQualifiedParameters;
+}
diff --git a/core/java/android/os/instrumentation/TargetProcess.aidl b/core/java/android/os/instrumentation/TargetProcess.aidl
new file mode 100644
index 000000000000..e90780d07ef2
--- /dev/null
+++ b/core/java/android/os/instrumentation/TargetProcess.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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 android.os.instrumentation;
+
+/**
+ * Addresses a process that would run on the device.
+ * Helps disambiguate targeted processes in cases of pid re-use.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable TargetProcess {
+ int uid;
+ int pid;
+ @utf8InCpp String processName;
+}
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 66c2e12f7cdf..cdf88f7ee33c 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -171,6 +171,7 @@ android_app {
"android.security.flags-aconfig",
"com.android.hardware.input.input-aconfig",
"aconfig_trade_in_mode_flags",
+ "art-aconfig-flags",
"ranging_aconfig_flags",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5913992004b8..a79ad4aa4b89 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8482,6 +8482,16 @@
android:protectionLevel="signature|privileged"
android:featureFlag="com.android.tradeinmode.flags.enable_trade_in_mode" />
+ <!-- @SystemApi
+ @FlaggedApi(com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS)
+ Ability to read program metadata and attach dynamic instrumentation.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.DYNAMIC_INSTRUMENTATION"
+ android:protectionLevel="signature"
+ android:featureFlag="com.android.art.flags.executable_method_file_offsets" />
+
<!--
@TestApi
Signature permission reserved for testing. This should never be used to
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 7b96699f7f71..857df1038df1 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -213,6 +213,8 @@
<assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="gpu_service" />
<assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="keystore" />
+ <assign-permission name="android.permission.DYNAMIC_INSTRUMENTATION" uid="uprobestats" />
+
<split-permission name="android.permission.ACCESS_FINE_LOCATION">
<new-permission name="android.permission.ACCESS_COARSE_LOCATION" />
</split-permission>
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 3eb99c3387f7..da29c49f9d7b 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -55,6 +55,7 @@ cc_library_shared {
"surface_control_input_receiver.cpp",
"choreographer.cpp",
"configuration.cpp",
+ "dynamic_instrumentation_manager.cpp",
"hardware_buffer_jni.cpp",
"input.cpp",
"input_transfer_token.cpp",
@@ -100,6 +101,7 @@ cc_library_shared {
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
"android.os.flags-aconfig-cc",
+ "dynamic_instrumentation_manager_aidl-cpp",
"libnativedisplay",
"libfmq",
],
diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp
new file mode 100644
index 000000000000..d9bacb116f96
--- /dev/null
+++ b/native/android/dynamic_instrumentation_manager.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#define LOG_TAG "ADynamicInstrumentationManager"
+#include <android/dynamic_instrumentation_manager.h>
+#include <android/os/instrumentation/ExecutableMethodFileOffsets.h>
+#include <android/os/instrumentation/IDynamicInstrumentationManager.h>
+#include <android/os/instrumentation/MethodDescriptor.h>
+#include <android/os/instrumentation/TargetProcess.h>
+#include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+#include <utils/Log.h>
+
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace android::dynamicinstrumentationmanager {
+
+// Global instance of IDynamicInstrumentationManager, service is obtained only on first use.
+static std::mutex mLock;
+static sp<os::instrumentation::IDynamicInstrumentationManager> mService;
+
+sp<os::instrumentation::IDynamicInstrumentationManager> getService() {
+ std::lock_guard<std::mutex> scoped_lock(mLock);
+ if (mService == nullptr || !IInterface::asBinder(mService)->isBinderAlive()) {
+ sp<IBinder> binder =
+ defaultServiceManager()->waitForService(String16("dynamic_instrumentation"));
+ mService = interface_cast<os::instrumentation::IDynamicInstrumentationManager>(binder);
+ }
+ return mService;
+}
+
+} // namespace android::dynamicinstrumentationmanager
+
+using namespace android;
+using namespace dynamicinstrumentationmanager;
+
+struct ADynamicInstrumentationManager_TargetProcess {
+ uid_t uid;
+ uid_t pid;
+ std::string processName;
+
+ ADynamicInstrumentationManager_TargetProcess(uid_t uid, pid_t pid, const char* processName)
+ : uid(uid), pid(pid), processName(processName) {}
+};
+
+ADynamicInstrumentationManager_TargetProcess* ADynamicInstrumentationManager_TargetProcess_create(
+ uid_t uid, pid_t pid, const char* processName) {
+ return new ADynamicInstrumentationManager_TargetProcess(uid, pid, processName);
+}
+
+void ADynamicInstrumentationManager_TargetProcess_destroy(
+ ADynamicInstrumentationManager_TargetProcess* instance) {
+ delete instance;
+}
+
+struct ADynamicInstrumentationManager_MethodDescriptor {
+ std::string fqcn;
+ std::string methodName;
+ std::vector<std::string> fqParameters;
+
+ ADynamicInstrumentationManager_MethodDescriptor(const char* fqcn, const char* methodName,
+ const char* fullyQualifiedParameters[],
+ size_t numParameters)
+ : fqcn(fqcn), methodName(methodName) {
+ std::vector<std::string> fqParameters;
+ fqParameters.reserve(numParameters);
+ std::copy_n(fullyQualifiedParameters, numParameters, std::back_inserter(fqParameters));
+ this->fqParameters = std::move(fqParameters);
+ }
+};
+
+ADynamicInstrumentationManager_MethodDescriptor*
+ADynamicInstrumentationManager_MethodDescriptor_create(const char* fullyQualifiedClassName,
+ const char* methodName,
+ const char* fullyQualifiedParameters[],
+ size_t numParameters) {
+ return new ADynamicInstrumentationManager_MethodDescriptor(fullyQualifiedClassName, methodName,
+ fullyQualifiedParameters,
+ numParameters);
+}
+
+void ADynamicInstrumentationManager_MethodDescriptor_destroy(
+ ADynamicInstrumentationManager_MethodDescriptor* instance) {
+ delete instance;
+}
+
+struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets {
+ std::string containerPath;
+ uint64_t containerOffset;
+ uint64_t methodOffset;
+};
+
+ADynamicInstrumentationManager_ExecutableMethodFileOffsets*
+ADynamicInstrumentationManager_ExecutableMethodFileOffsets_create() {
+ return new ADynamicInstrumentationManager_ExecutableMethodFileOffsets();
+}
+
+const char* ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ return instance->containerPath.c_str();
+}
+
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ return instance->containerOffset;
+}
+
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ return instance->methodOffset;
+}
+
+void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+ delete instance;
+}
+
+int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
+ const ADynamicInstrumentationManager_TargetProcess* targetProcess,
+ const ADynamicInstrumentationManager_MethodDescriptor* methodDescriptor,
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets** out) {
+ android::os::instrumentation::TargetProcess targetProcessParcel;
+ targetProcessParcel.uid = targetProcess->uid;
+ targetProcessParcel.pid = targetProcess->pid;
+ targetProcessParcel.processName = targetProcess->processName;
+
+ android::os::instrumentation::MethodDescriptor methodDescriptorParcel;
+ methodDescriptorParcel.fullyQualifiedClassName = methodDescriptor->fqcn;
+ methodDescriptorParcel.methodName = methodDescriptor->methodName;
+ methodDescriptorParcel.fullyQualifiedParameters = methodDescriptor->fqParameters;
+
+ sp<os::instrumentation::IDynamicInstrumentationManager> service = getService();
+ if (service == nullptr) {
+ return INVALID_OPERATION;
+ }
+
+ std::optional<android::os::instrumentation::ExecutableMethodFileOffsets> offsets;
+ binder_status_t result =
+ service->getExecutableMethodFileOffsets(targetProcessParcel, methodDescriptorParcel,
+ &offsets)
+ .exceptionCode();
+ if (result != OK) {
+ return result;
+ }
+
+ if (offsets != std::nullopt) {
+ auto* value = new ADynamicInstrumentationManager_ExecutableMethodFileOffsets();
+ value->containerPath = offsets->containerPath;
+ value->containerOffset = offsets->containerOffset;
+ value->methodOffset = offsets->methodOffset;
+ *out = value;
+ } else {
+ *out = nullptr;
+ }
+
+ return result;
+} \ No newline at end of file
diff --git a/native/android/include_platform/android/dynamic_instrumentation_manager.h b/native/android/include_platform/android/dynamic_instrumentation_manager.h
new file mode 100644
index 000000000000..6c46288954bf
--- /dev/null
+++ b/native/android/include_platform/android/dynamic_instrumentation_manager.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef __ADYNAMICINSTRUMENTATIONMANAGER_H__
+#define __ADYNAMICINSTRUMENTATIONMANAGER_H__
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct ADynamicInstrumentationManager_MethodDescriptor;
+typedef struct ADynamicInstrumentationManager_MethodDescriptor
+ ADynamicInstrumentationManager_MethodDescriptor;
+
+struct ADynamicInstrumentationManager_TargetProcess;
+typedef struct ADynamicInstrumentationManager_TargetProcess
+ ADynamicInstrumentationManager_TargetProcess;
+
+struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets;
+typedef struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets;
+
+/**
+ * Initializes an ADynamicInstrumentationManager_TargetProcess. Caller must clean up when they are
+ * done with ADynamicInstrumentationManager_TargetProcess_destroy.
+ *
+ * @param uid of targeted process.
+ * @param pid of targeted process.
+ * @param processName to disambiguate from corner cases that may arise from pid reuse.
+ */
+ADynamicInstrumentationManager_TargetProcess* _Nonnull
+ ADynamicInstrumentationManager_TargetProcess_create(
+ uid_t uid, pid_t pid, const char* _Nonnull processName) __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_TargetProcess.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_TargetProcess_create.
+ */
+void ADynamicInstrumentationManager_TargetProcess_destroy(
+ ADynamicInstrumentationManager_TargetProcess* _Nonnull instance) __INTRODUCED_IN(36);
+
+/**
+ * Initializes an ADynamicInstrumentationManager_MethodDescriptor. Caller must clean up when they
+ * are done with ADynamicInstrumentationManager_MethodDescriptor_Destroy.
+ *
+ * @param fullyQualifiedClassName fqcn of class containing the method.
+ * @param methodName
+ * @param fullyQualifiedParameters fqcn of parameters of the method's signature, or e.g. "int" for
+ * primitives.
+ * @param numParameters length of `fullyQualifiedParameters` array.
+ */
+ADynamicInstrumentationManager_MethodDescriptor* _Nonnull
+ ADynamicInstrumentationManager_MethodDescriptor_create(
+ const char* _Nonnull fullyQualifiedClassName, const char* _Nonnull methodName,
+ const char* _Nonnull fullyQualifiedParameters[_Nonnull], size_t numParameters)
+ __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_MethodDescriptor.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_MethodDescriptor_create.
+ */
+void ADynamicInstrumentationManager_MethodDescriptor_destroy(
+ ADynamicInstrumentationManager_MethodDescriptor* _Nonnull instance) __INTRODUCED_IN(36);
+
+/**
+ * Get the containerPath calculated by
+ * ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The OS path of the containing file.
+ */
+const char* _Nullable ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ __INTRODUCED_IN(36);
+/**
+ * Get the containerOffset calculated by
+ * ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The offset of the containing file within the process' memory.
+ */
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ __INTRODUCED_IN(36);
+/**
+ * Get the methodOffset calculated by ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The offset of the method within the containing file.
+ */
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_ExecutableMethodFileOffsets.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ */
+void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+ __INTRODUCED_IN(36);
+/**
+ * Provides ART metadata about the described java method within the target process.
+ *
+ * @param targetProcess describes for which process the data is requested.
+ * @param methodDescriptor describes the targeted method.
+ * @param out will be populated with the data if successful. A nullptr combined
+ * with an OK status means that the program method is defined, but the offset
+ * info was unavailable because it is not AOT compiled.
+ * @return status indicating success or failure. The values correspond to the `binder_exception_t`
+ * enum values from <android/binder_status.h>.
+ */
+int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
+ const ADynamicInstrumentationManager_TargetProcess* _Nonnull targetProcess,
+ const ADynamicInstrumentationManager_MethodDescriptor* _Nonnull methodDescriptor,
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull* _Nullable out)
+ __INTRODUCED_IN(36);
+
+__END_DECLS
+
+#endif // __ADYNAMICINSTRUMENTATIONMANAGER_H__
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b025cb880ee7..a0460572abfc 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -4,6 +4,15 @@ LIBANDROID {
AActivityManager_removeUidImportanceListener; # systemapi introduced=31
AActivityManager_isUidActive; # systemapi introduced=31
AActivityManager_getUidImportance; # systemapi introduced=31
+ ADynamicInstrumentationManager_TargetProcess_create; # systemapi
+ ADynamicInstrumentationManager_TargetProcess_destroy; # systemapi
+ ADynamicInstrumentationManager_MethodDescriptor_create; # systemapi
+ ADynamicInstrumentationManager_MethodDescriptor_destroy; # systemapi
+ ADynamicInstrumentationManager_getExecutableMethodFileOffsets; # systemapi
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath; # systemapi
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset; # systemapi
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset; # systemapi
+ ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy; # systemapi
AAssetDir_close;
AAssetDir_getNextFileName;
AAssetDir_rewind;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2c8c261fa8f8..bacdec100890 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -957,6 +957,9 @@
<!-- Permission required for CTS test - CtsTelephonyTestCases -->
<uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
+ <!-- Permission required for ExecutableMethodFileOffsetsTest -->
+ <uses-permission android:name="android.permission.DYNAMIC_INSTRUMENTATION" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 6cfd44bb2d1a..b4d58806b5d8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -158,6 +158,7 @@ java_library_static {
"android.hardware.gnss-V2-java",
"android.hardware.vibrator-V3-java",
"app-compat-annotations",
+ "art_exported_aconfig_flags_lib",
"framework-tethering.stubs.module_lib",
"keepanno-annotations",
"service-art.stubs.system_server",
diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
new file mode 100644
index 000000000000..8ec716077f46
--- /dev/null
+++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 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.server.os.instrumentation;
+
+import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION;
+import static android.content.Context.DYNAMIC_INSTRUMENTATION_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.PermissionManuallyEnforced;
+import android.content.Context;
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.IDynamicInstrumentationManager;
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.TargetProcess;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import dalvik.system.VMDebug;
+
+import java.lang.reflect.Method;
+
+/**
+ * System private implementation of the {@link IDynamicInstrumentationManager interface}.
+ */
+public class DynamicInstrumentationManagerService extends SystemService {
+ public DynamicInstrumentationManagerService(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(DYNAMIC_INSTRUMENTATION_SERVICE, new BinderService());
+ }
+
+ private final class BinderService extends IDynamicInstrumentationManager.Stub {
+ @Override
+ @PermissionManuallyEnforced
+ public @Nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets(
+ @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor) {
+ if (!com.android.art.flags.Flags.executableMethodFileOffsets()) {
+ throw new UnsupportedOperationException();
+ }
+ getContext().enforceCallingOrSelfPermission(
+ DYNAMIC_INSTRUMENTATION, "Caller must have DYNAMIC_INSTRUMENTATION permission");
+
+ if (targetProcess.processName == null
+ || !targetProcess.processName.equals("system_server")) {
+ throw new UnsupportedOperationException(
+ "system_server is the only supported target process");
+ }
+
+ Method method = parseMethodDescriptor(
+ getClass().getClassLoader(), methodDescriptor);
+ VMDebug.ExecutableMethodFileOffsets location =
+ VMDebug.getExecutableMethodFileOffsets(method);
+
+ if (location == null) {
+ return null;
+ }
+
+ ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets();
+ ret.containerPath = location.getContainerPath();
+ ret.containerOffset = location.getContainerOffset();
+ ret.methodOffset = location.getMethodOffset();
+ return ret;
+ }
+ }
+
+ @VisibleForTesting
+ static Method parseMethodDescriptor(ClassLoader classLoader,
+ @NonNull MethodDescriptor descriptor) {
+ try {
+ Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName);
+ Class<?>[] parameters = new Class[descriptor.fullyQualifiedParameters.length];
+ for (int i = 0; i < descriptor.fullyQualifiedParameters.length; i++) {
+ String typeName = descriptor.fullyQualifiedParameters[i];
+ boolean isArrayType = typeName.endsWith("[]");
+ if (isArrayType) {
+ typeName = typeName.substring(0, typeName.length() - 2);
+ }
+ switch (typeName) {
+ case "boolean":
+ parameters[i] = isArrayType ? boolean.class.arrayType() : boolean.class;
+ break;
+ case "byte":
+ parameters[i] = isArrayType ? byte.class.arrayType() : byte.class;
+ break;
+ case "char":
+ parameters[i] = isArrayType ? char.class.arrayType() : char.class;
+ break;
+ case "short":
+ parameters[i] = isArrayType ? short.class.arrayType() : short.class;
+ break;
+ case "int":
+ parameters[i] = isArrayType ? int.class.arrayType() : int.class;
+ break;
+ case "long":
+ parameters[i] = isArrayType ? long.class.arrayType() : long.class;
+ break;
+ case "float":
+ parameters[i] = isArrayType ? float.class.arrayType() : float.class;
+ break;
+ case "double":
+ parameters[i] = isArrayType ? double.class.arrayType() : double.class;
+ break;
+ default:
+ parameters[i] = isArrayType ? classLoader.loadClass(typeName).arrayType()
+ : classLoader.loadClass(typeName);
+ }
+ }
+
+ return javaClass.getDeclaredMethod(descriptor.methodName, parameters);
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ "The specified method cannot be found. Is this descriptor valid? "
+ + descriptor, e);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3805c02d1bb9..221b8481a30c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -205,6 +205,7 @@ import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
import com.android.server.os.SchedulingPolicyService;
+import com.android.server.os.instrumentation.DynamicInstrumentationManagerService;
import com.android.server.pdb.PersistentDataBlockService;
import com.android.server.people.PeopleService;
import com.android.server.permission.access.AccessCheckingService;
@@ -2890,6 +2891,13 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(TracingServiceProxy.class);
t.traceEnd();
+ // UprobeStats DynamicInstrumentationManager
+ if (com.android.art.flags.Flags.executableMethodFileOffsets()) {
+ t.traceBegin("StartDynamicInstrumentationManager");
+ mSystemServiceManager.startService(DynamicInstrumentationManagerService.class);
+ t.traceEnd();
+ }
+
// It is now time to start up the app processes...
t.traceBegin("MakeLockSettingsServiceReady");
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
new file mode 100644
index 000000000000..2c2e5fdb68d9
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2024 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"],
+ default_team: "trendy_team_system_performance",
+}
+
+android_test {
+ name: "DynamicInstrumentationManagerServiceTests",
+ srcs: ["src/**/*.java"],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "hamcrest-library",
+ "platform-test-annotations",
+ "services.core",
+ "testables",
+ "truth",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ ],
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml
new file mode 100644
index 000000000000..4913d706a72d
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.os.instrumentation" >
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.os.instrumentation"
+ android:label="DynamicInstrumentationmanagerService Unit Tests"/>
+</manifest> \ No newline at end of file
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING
new file mode 100644
index 000000000000..33defed0eae6
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "DynamicInstrumentationManagerServiceTests"
+ }
+ ]
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java
new file mode 100644
index 000000000000..04073fab2059
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.server;
+
+public abstract class TestAbstractClass {
+ abstract void abstractMethod();
+
+ void concreteMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java
new file mode 100644
index 000000000000..2c25e7a52f73
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.server;
+
+public class TestAbstractClassImpl extends TestAbstractClass {
+ @Override
+ void abstractMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
new file mode 100644
index 000000000000..085f5953f0e5
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.server;
+
+public class TestClass {
+ void primitiveParams(boolean a, boolean[] b, byte c, byte[] d, char e, char[] f, short g,
+ short[] h, int i, int[] j, long k, long[] l, float m, float[] n, double o, double[] p) {
+ }
+
+ void classParams(String a, String[] b) {
+ }
+
+ private void privateMethod() {
+ }
+
+ /**
+ * docs!
+ */
+ public void publicMethod() {
+ }
+
+ private static class InnerClass {
+ private void innerMethod(InnerClass arg, InnerClass[] argArray) {
+ }
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java
new file mode 100644
index 000000000000..7af4f254ab1c
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.server;
+
+/**
+ * docs!
+ */
+public interface TestInterface {
+ /**
+ * docs!
+ */
+ void interfaceMethod();
+
+ /**
+ * docs!
+ */
+ default void defaultMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java
new file mode 100644
index 000000000000..53aecbc08939
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.server;
+
+public class TestInterfaceImpl implements TestInterface {
+ @Override
+ public void interfaceMethod() {
+ }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
new file mode 100644
index 000000000000..5492ba6b9dd1
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 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.server.os.instrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+import android.os.instrumentation.MethodDescriptor;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.TestAbstractClass;
+import com.android.server.TestAbstractClassImpl;
+import com.android.server.TestClass;
+import com.android.server.TestInterface;
+import com.android.server.TestInterfaceImpl;
+
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * Test class for
+ * {@link DynamicInstrumentationManagerService#parseMethodDescriptor(ClassLoader,
+ * MethodDescriptor)}.
+ * <p>
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:ParseMethodDescriptorTest
+ */
+@Presubmit
+@SmallTest
+public class ParseMethodDescriptorTest {
+ private static final String[] PRIMITIVE_PARAMS = new String[]{
+ "boolean", "boolean[]", "byte", "byte[]", "char", "char[]", "short", "short[]", "int",
+ "int[]", "long", "long[]", "float", "float[]", "double", "double[]"};
+ private static final String[] CLASS_PARAMS =
+ new String[]{"java.lang.String", "java.lang.String[]"};
+
+ @Test
+ public void primitiveParams() {
+ assertNotNull(parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
+ PRIMITIVE_PARAMS));
+ }
+
+ @Test
+ public void classParams() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "classParams", CLASS_PARAMS));
+ }
+
+ @Test
+ public void publicMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "publicMethod"));
+ }
+
+ @Test
+ public void privateMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName(), "privateMethod"));
+ }
+
+ @Test
+ public void innerClass() {
+ assertNotNull(
+ parseMethodDescriptor(TestClass.class.getName() + "$InnerClass", "innerMethod",
+ new String[]{TestClass.class.getName() + "$InnerClass",
+ TestClass.class.getName() + "$InnerClass[]"}));
+ }
+
+ @Test
+ public void interface_concreteMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestInterfaceImpl.class.getName(), "interfaceMethod"));
+ }
+
+ @Test
+ public void interface_defaultMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestInterface.class.getName(), "defaultMethod"));
+ }
+
+ @Test
+ public void abstractClassImpl_abstractMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestAbstractClassImpl.class.getName(), "abstractMethod"));
+ }
+
+ @Test
+ public void abstractClass_concreteMethod() {
+ assertNotNull(
+ parseMethodDescriptor(TestAbstractClass.class.getName(), "concreteMethod"));
+ }
+
+ @Test
+ public void notFound_illegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> parseMethodDescriptor("foo", "bar"));
+ assertThrows(IllegalArgumentException.class,
+ () -> parseMethodDescriptor(TestClass.class.getName(), "bar"));
+ assertThrows(IllegalArgumentException.class,
+ () -> parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
+ new String[]{"int"}));
+ }
+
+ private Method parseMethodDescriptor(String fqcn, String methodName) {
+ return DynamicInstrumentationManagerService.parseMethodDescriptor(
+ getClass().getClassLoader(),
+ getMethodDescriptor(fqcn, methodName, new String[]{}));
+ }
+
+ private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) {
+ return DynamicInstrumentationManagerService.parseMethodDescriptor(
+ getClass().getClassLoader(),
+ getMethodDescriptor(fqcn, methodName, fqParameters));
+ }
+
+ private MethodDescriptor getMethodDescriptor(String fqcn, String methodName,
+ String[] fqParameters) {
+ MethodDescriptor methodDescriptor = new MethodDescriptor();
+ methodDescriptor.fullyQualifiedClassName = fqcn;
+ methodDescriptor.methodName = methodName;
+ methodDescriptor.fullyQualifiedParameters = fqParameters;
+ return methodDescriptor;
+ }
+
+
+}