Adding dependency_mapper cmd tool.

This will be used for incremental builds in soong.

Bug: 387799000
Flags: NA
Test: m dependency-mapper && atest dependency-mapper-tests
Change-Id: Id0d0f9e4daf1c7ee3896779abdafde679d01ee31
diff --git a/tools/dependency_mapper/Android.bp b/tools/dependency_mapper/Android.bp
new file mode 100644
index 0000000..6763c0e
--- /dev/null
+++ b/tools/dependency_mapper/Android.bp
@@ -0,0 +1,45 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_team: "trendy_team_android_crumpet",
+}
+
+java_binary_host {
+    name: "dependency-mapper",
+    main_class: "com.android.dependencymapper.Main",
+    static_libs: [
+        "dependency-mapper-host-lib",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+java_library_host {
+    name: "dependency-mapper-host-lib",
+    srcs: [
+        "src/**/*.java",
+        "proto/**/*.proto",
+    ],
+    static_libs: [
+        "gson",
+        "ow2-asm",
+    ],
+}
+
+java_test_host {
+    name: "dependency-mapper-tests",
+    srcs: ["tests/src/**/*.java"],
+    static_libs: [
+        "junit",
+        "dependency-mapper-host-lib",
+    ],
+    data: [
+        "tests/res/**/*",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+}
+
+java_library {
+    name: "dependency-mapper-test-data",
+    srcs: ["tests/res/**/*.java"],
+}
diff --git a/tools/dependency_mapper/OWNERS b/tools/dependency_mapper/OWNERS
new file mode 100644
index 0000000..4477269
--- /dev/null
+++ b/tools/dependency_mapper/OWNERS
@@ -0,0 +1 @@
+himanshuz@google.com
\ No newline at end of file
diff --git a/tools/dependency_mapper/README.md b/tools/dependency_mapper/README.md
new file mode 100644
index 0000000..475aef2
--- /dev/null
+++ b/tools/dependency_mapper/README.md
@@ -0,0 +1,26 @@
+# Dependency Mapper
+
+[dependency-mapper] command line tool. This tool finds the usage based dependencies between java
+files by utilizing byte-code and java file analysis.
+
+# Getting Started
+
+## Inputs
+* rsp file, containing list of java files separated by whitespace.
+* jar file, containing class files generated after compiling the contents of rsp file.
+
+## Output
+* proto file, representing the list of dependencies for each java file present in input rsp file,
+represented by [proto/usage.proto]
+
+## Usage
+```
+dependency-mapper --src-path [src-list.rsp] --jar-path [classes.jar] --usage-map-path [usage-map.proto]"
+```
+
+# Notes
+## Dependencies enlisted are only within the java files present in input.
+## Ensure that [SourceFile] is present in the classes present in the jar.
+## To ensure dependencies are listed correctly
+* Classes jar should only contain class files generated from the source rsp files.
+* Classes jar should not exclude any class file that was generated from source rsp files.
\ No newline at end of file
diff --git a/tools/dependency_mapper/proto/dependency.proto b/tools/dependency_mapper/proto/dependency.proto
new file mode 100644
index 0000000..60a88f8
--- /dev/null
+++ b/tools/dependency_mapper/proto/dependency.proto
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+syntax = "proto2";
+
+package com.android.dependencymapper;
+option java_package = "com.android.dependencymapper";
+option java_outer_classname = "DependencyProto";
+
+/**
+ * A com.android.dependencymapper.DependencyProto.FileDependency object.
+ */
+
+message FileDependency {
+
+  // java file path on disk
+  optional string file_path = 1;
+  // if a change in this file warrants recompiling all files
+  optional bool is_dependency_to_all = 2;
+  // class files generated when this java file is compiled
+  repeated string generated_classes = 3;
+  // dependencies of this file.
+  repeated string file_dependencies = 4;
+}
+
+/**
+ * A com.android.dependencymapper.DependencyProto.FileDependencyList object.
+ */
+message FileDependencyList {
+
+  // List of java file usages
+  repeated FileDependency fileDependency = 1;
+}
\ No newline at end of file
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependenciesVisitor.java b/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependenciesVisitor.java
new file mode 100644
index 0000000..ba65145
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependenciesVisitor.java
@@ -0,0 +1,316 @@
+/*
+ * 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.android.dependencymapper;
+
+import org.objectweb.asm.signature.SignatureReader;
+import org.objectweb.asm.signature.SignatureVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.TypePath;
+
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * An ASM based class visitor to analyze and club all dependencies of a java file.
+ * Most of the logic of this class is inspired from
+ * <a href="https://github.com/gradle/gradle/blob/master/platforms/jvm/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/asm/ClassDependenciesVisitor.java">gradle incremental compilation</a>
+ */
+public class ClassDependenciesVisitor extends ClassVisitor {
+
+    private final static int API = Opcodes.ASM9;
+
+    private final Set<String> mClassTypes;
+    private final Set<Object> mConstantsDefined;
+    private final Set<Object> mInlinedUsages;
+    private String mSource;
+    private boolean isAnnotationType;
+    private boolean mIsDependencyToAll;
+    private final RetentionPolicyVisitor retentionPolicyVisitor;
+
+    private final ClassRelevancyFilter mClassFilter;
+
+    private ClassDependenciesVisitor(ClassReader reader, ClassRelevancyFilter filter) {
+        super(API);
+        this.mClassTypes = new HashSet<>();
+        this.mConstantsDefined = new HashSet<>();
+        this.mInlinedUsages =  new HashSet<>();
+        this.retentionPolicyVisitor = new RetentionPolicyVisitor();
+        this.mClassFilter = filter;
+        collectRemainingClassDependencies(reader);
+    }
+
+    public static ClassDependencyData analyze(
+            String className, ClassReader reader, ClassRelevancyFilter filter) {
+        ClassDependenciesVisitor visitor = new ClassDependenciesVisitor(reader, filter);
+        reader.accept(visitor, ClassReader.SKIP_FRAMES);
+        // Sometimes a class may contain references to the same class, we remove such cases to
+        // prevent circular dependency.
+        visitor.getClassTypes().remove(className);
+        return new ClassDependencyData(Utils.buildPackagePrependedClassSource(
+                className, visitor.getSource()), className, visitor.getClassTypes(),
+                visitor.isDependencyToAll(), visitor.getConstantsDefined(),
+                visitor.getInlinedUsages());
+    }
+
+    @Override
+    public void visitSource(String source, String debug) {
+        mSource = source;
+    }
+
+    @Override
+    public void visit(int version, int access, String name, String signature, String superName,
+            String[] interfaces) {
+        isAnnotationType = isAnnotationType(interfaces);
+        maybeAddClassTypesFromSignature(signature, mClassTypes);
+        if (superName != null) {
+            // superName can be null if what we are analyzing is `java.lang.Object`
+            // which can happen when a custom Java SDK is on classpath (typically, android.jar)
+            Type type = Type.getObjectType(superName);
+            maybeAddClassType(mClassTypes, type);
+        }
+        for (String s : interfaces) {
+            Type interfaceType = Type.getObjectType(s);
+            maybeAddClassType(mClassTypes, interfaceType);
+        }
+    }
+
+    // performs a fast analysis of classes referenced in bytecode (method bodies)
+    // avoiding us to implement a costly visitor and potentially missing edge cases
+    private void collectRemainingClassDependencies(ClassReader reader) {
+        char[] charBuffer = new char[reader.getMaxStringLength()];
+        for (int i = 1; i < reader.getItemCount(); i++) {
+            int itemOffset = reader.getItem(i);
+            // see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
+            if (itemOffset > 0 && reader.readByte(itemOffset - 1) == 7) {
+                // A CONSTANT_Class entry, read the class descriptor
+                String classDescriptor = reader.readUTF8(itemOffset, charBuffer);
+                Type type = Type.getObjectType(classDescriptor);
+                maybeAddClassType(mClassTypes, type);
+            }
+        }
+    }
+
+    private void maybeAddClassTypesFromSignature(String signature, Set<String> types) {
+        if (signature != null) {
+            SignatureReader signatureReader = new SignatureReader(signature);
+            signatureReader.accept(new SignatureVisitor(API) {
+                @Override
+                public void visitClassType(String className) {
+                    Type type = Type.getObjectType(className);
+                    maybeAddClassType(types, type);
+                }
+            });
+        }
+    }
+
+    protected void maybeAddClassType(Set<String> types, Type type) {
+        while (type.getSort() == Type.ARRAY) {
+            type = type.getElementType();
+        }
+        if (type.getSort() != Type.OBJECT) {
+            return;
+        }
+        //String name = Utils.classPackageToFilePath(type.getClassName());
+        String name = type.getClassName();
+        if (mClassFilter.test(name)) {
+            types.add(name);
+        }
+    }
+
+    public String getSource() {
+        return mSource;
+    }
+
+    public Set<String> getClassTypes() {
+        return mClassTypes;
+    }
+
+    public Set<Object> getConstantsDefined() {
+        return mConstantsDefined;
+    }
+
+    public Set<Object> getInlinedUsages() {
+        return mInlinedUsages;
+    }
+
+    private boolean isAnnotationType(String[] interfaces) {
+        return interfaces.length == 1 && interfaces[0].equals("java/lang/annotation/Annotation");
+    }
+
+    @Override
+    public FieldVisitor visitField(
+            int access, String name, String desc, String signature, Object value) {
+        maybeAddClassTypesFromSignature(signature, mClassTypes);
+        maybeAddClassType(mClassTypes, Type.getType(desc));
+        if (isAccessibleConstant(access, value)) {
+            mConstantsDefined.add(value);
+        }
+        return new FieldVisitor(mClassTypes);
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+            int access, String name, String desc, String signature, String[] exceptions) {
+        maybeAddClassTypesFromSignature(signature, mClassTypes);
+        Type methodType = Type.getMethodType(desc);
+        maybeAddClassType(mClassTypes, methodType.getReturnType());
+        for (Type argType : methodType.getArgumentTypes()) {
+            maybeAddClassType(mClassTypes, argType);
+        }
+        return new MethodVisitor(mClassTypes);
+    }
+
+    @Override
+    public org.objectweb.asm.AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+        if (isAnnotationType && "Ljava/lang/annotation/Retention;".equals(desc)) {
+            return retentionPolicyVisitor;
+        } else {
+            maybeAddClassType(mClassTypes, Type.getType(desc));
+            return new AnnotationVisitor(mClassTypes);
+        }
+    }
+
+    private static boolean isAccessible(int access) {
+        return (access & Opcodes.ACC_PRIVATE) == 0;
+    }
+
+    private static boolean isAccessibleConstant(int access, Object value) {
+        return isConstant(access) && isAccessible(access) && value != null;
+    }
+
+    private static boolean isConstant(int access) {
+        return (access & Opcodes.ACC_FINAL) != 0 && (access & Opcodes.ACC_STATIC) != 0;
+    }
+
+    public boolean isDependencyToAll() {
+        return mIsDependencyToAll;
+    }
+
+    private class FieldVisitor extends org.objectweb.asm.FieldVisitor {
+        private final Set<String> types;
+
+        public FieldVisitor(Set<String> types) {
+            super(API);
+            this.types = types;
+        }
+
+        @Override
+        public org.objectweb.asm.AnnotationVisitor visitAnnotation(
+                String descriptor, boolean visible) {
+            maybeAddClassType(types, Type.getType(descriptor));
+            return new AnnotationVisitor(types);
+        }
+
+        @Override
+        public org.objectweb.asm.AnnotationVisitor visitTypeAnnotation(int typeRef,
+                TypePath typePath, String descriptor, boolean visible) {
+            maybeAddClassType(types, Type.getType(descriptor));
+            return new AnnotationVisitor(types);
+        }
+    }
+
+    private class MethodVisitor extends org.objectweb.asm.MethodVisitor {
+        private final Set<String> types;
+
+        protected MethodVisitor(Set<String> types) {
+            super(API);
+            this.types = types;
+        }
+
+        @Override
+        public void visitLdcInsn(Object value) {
+            mInlinedUsages.add(value);
+            super.visitLdcInsn(value);
+        }
+
+        @Override
+        public void visitLocalVariable(
+                String name, String desc, String signature, Label start, Label end, int index) {
+            maybeAddClassTypesFromSignature(signature, mClassTypes);
+            maybeAddClassType(mClassTypes, Type.getType(desc));
+            super.visitLocalVariable(name, desc, signature, start, end, index);
+        }
+
+        @Override
+        public org.objectweb.asm.AnnotationVisitor visitAnnotation(
+                String descriptor, boolean visible) {
+            maybeAddClassType(types, Type.getType(descriptor));
+            return new AnnotationVisitor(types);
+        }
+
+        @Override
+        public org.objectweb.asm.AnnotationVisitor visitParameterAnnotation(
+                int parameter, String descriptor, boolean visible) {
+            maybeAddClassType(types, Type.getType(descriptor));
+            return new AnnotationVisitor(types);
+        }
+
+        @Override
+        public org.objectweb.asm.AnnotationVisitor visitTypeAnnotation(
+                int typeRef, TypePath typePath, String descriptor, boolean visible) {
+            maybeAddClassType(types, Type.getType(descriptor));
+            return new AnnotationVisitor(types);
+        }
+    }
+
+    private class RetentionPolicyVisitor extends org.objectweb.asm.AnnotationVisitor {
+        public RetentionPolicyVisitor() {
+            super(ClassDependenciesVisitor.API);
+        }
+
+        @Override
+        public void visitEnum(String name, String desc, String value) {
+            if ("Ljava/lang/annotation/RetentionPolicy;".equals(desc)) {
+                RetentionPolicy policy = RetentionPolicy.valueOf(value);
+                if (policy == RetentionPolicy.SOURCE) {
+                    mIsDependencyToAll = true;
+                }
+            }
+        }
+    }
+
+    private class AnnotationVisitor extends org.objectweb.asm.AnnotationVisitor {
+        private final Set<String> types;
+
+        public AnnotationVisitor(Set<String> types) {
+            super(ClassDependenciesVisitor.API);
+            this.types = types;
+        }
+
+        @Override
+        public void visit(String name, Object value) {
+            if (value instanceof Type) {
+                maybeAddClassType(types, (Type) value);
+            }
+        }
+
+        @Override
+        public org.objectweb.asm.AnnotationVisitor visitArray(String name) {
+            return this;
+        }
+
+        @Override
+        public org.objectweb.asm.AnnotationVisitor visitAnnotation(String name, String descriptor) {
+            maybeAddClassType(types, Type.getType(descriptor));
+            return this;
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependencyAnalyzer.java b/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependencyAnalyzer.java
new file mode 100644
index 0000000..4a37b41
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependencyAnalyzer.java
@@ -0,0 +1,56 @@
+/*
+ * 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.android.dependencymapper;
+
+import org.objectweb.asm.ClassReader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * An utility class that reads each class file present in the classes jar, then analyzes the same,
+ * collecting the dependencies in {@link List<ClassDependencyData>}
+ */
+public class ClassDependencyAnalyzer {
+
+    public static List<ClassDependencyData> analyze(Path classJar, ClassRelevancyFilter classFilter) {
+        List<ClassDependencyData> classAnalysisList = new ArrayList<>();
+        try (JarFile jarFile = new JarFile(classJar.toFile())) {
+            Enumeration<JarEntry> entries = jarFile.entries();
+            while (entries.hasMoreElements()) {
+                JarEntry entry = entries.nextElement();
+                if (entry.getName().endsWith(".class")) {
+                    try (InputStream inputStream = jarFile.getInputStream(entry)) {
+                        String name = Utils.trimAndConvertToPackageBasedPath(entry.getName());
+                        ClassDependencyData classAnalysis = ClassDependenciesVisitor.analyze(name,
+                                new ClassReader(inputStream), classFilter);
+                        classAnalysisList.add(classAnalysis);
+                    }
+                }
+            }
+        } catch (IOException e) {
+            System.err.println("Error reading the jar file at: " + classJar);
+            throw new RuntimeException(e);
+        }
+        return classAnalysisList;
+    }
+}
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependencyData.java b/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependencyData.java
new file mode 100644
index 0000000..58e388f
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/ClassDependencyData.java
@@ -0,0 +1,65 @@
+/*
+ * 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.android.dependencymapper;
+
+import java.util.Set;
+
+/**
+ * Represents the Class Dependency Data collected via ASM analysis.
+ */
+public class ClassDependencyData {
+    private final String mPackagePrependedClassSource;
+    private final String mQualifiedName;
+    private final Set<String> mClassDependencies;
+    private final boolean mIsDependencyToAll;
+    private final Set<Object> mConstantsDefined;
+    private final Set<Object> mInlinedUsages;
+
+    public ClassDependencyData(String packagePrependedClassSource, String className,
+            Set<String> classDependencies, boolean isDependencyToAll, Set<Object> constantsDefined,
+            Set<Object> inlinedUsages) {
+        this.mPackagePrependedClassSource = packagePrependedClassSource;
+        this.mQualifiedName = className;
+        this.mClassDependencies = classDependencies;
+        this.mIsDependencyToAll = isDependencyToAll;
+        this.mConstantsDefined = constantsDefined;
+        this.mInlinedUsages = inlinedUsages;
+    }
+
+    public String getPackagePrependedClassSource() {
+        return mPackagePrependedClassSource;
+    }
+
+    public String getQualifiedName() {
+        return mQualifiedName;
+    }
+
+    public Set<String> getClassDependencies() {
+        return mClassDependencies;
+    }
+
+    public Set<Object> getConstantsDefined() {
+        return mConstantsDefined;
+    }
+
+    public Set<Object> inlinedUsages() {
+        return mInlinedUsages;
+    }
+
+    public boolean isDependencyToAll() {
+        return mIsDependencyToAll;
+    }
+}
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/ClassRelevancyFilter.java b/tools/dependency_mapper/src/com/android/dependencymapper/ClassRelevancyFilter.java
new file mode 100644
index 0000000..c46b53f
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/ClassRelevancyFilter.java
@@ -0,0 +1,36 @@
+/*
+ * 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.android.dependencymapper;
+
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * A filter representing the list of class files which are relevant for dependency analysis.
+ */
+public class ClassRelevancyFilter implements Predicate<String> {
+
+    private final Set<String> mAllowlistedClassNames;
+
+    public ClassRelevancyFilter(Set<String> allowlistedClassNames) {
+        this.mAllowlistedClassNames = allowlistedClassNames;
+    }
+
+    @Override
+    public boolean test(String className) {
+        return mAllowlistedClassNames.contains(className);
+    }
+}
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/DependencyMapper.java b/tools/dependency_mapper/src/com/android/dependencymapper/DependencyMapper.java
new file mode 100644
index 0000000..ecf520c
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/DependencyMapper.java
@@ -0,0 +1,165 @@
+/*
+ * 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.android.dependencymapper;
+
+import com.android.dependencymapper.DependencyProto;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class binds {@link List<ClassDependencyData>} and {@link List<JavaSourceData>} together as a
+ * flat map, which represents dependency related attributes of a java file.
+ */
+public class DependencyMapper {
+    private final List<ClassDependencyData> mClassAnalysisList;
+    private final List<JavaSourceData> mJavaSourceDataList;
+    private final Map<String, String> mClassToSourceMap = new HashMap<>();
+    private final Map<String, Set<String>> mFileDependencies = new HashMap<>();
+    private final Set<String> mDependencyToAll = new HashSet<>();
+    private final Map<String, Set<String>> mSourceToClasses = new HashMap<>();
+
+    public DependencyMapper(List<ClassDependencyData> classAnalysisList, List<JavaSourceData> javaSourceDataList) {
+        this.mClassAnalysisList = classAnalysisList;
+        this.mJavaSourceDataList = javaSourceDataList;
+    }
+
+    public DependencyProto.FileDependencyList buildDependencyMaps() {
+        buildClassDependencyMaps();
+        buildSourceToClassMap();
+        return createFileDependencies();
+    }
+
+    private void buildClassDependencyMaps() {
+        // Create a map between package appended file names and file paths.
+        Map<String, String> sourcePaths = generateSourcePaths();
+        // A map between qualified className and its dependencies
+        Map<String, Set<String>> classDependencies = new HashMap<>();
+        // A map between constant values and the their declarations.
+        Map<Object, Set<String>> constantRegistry = new HashMap<>();
+        // A map between constant values and the their inlined usages.
+        Map<Object, Set<String>> inlinedUsages = new HashMap<>();
+
+        for (ClassDependencyData analysis : mClassAnalysisList) {
+            String className = analysis.getQualifiedName();
+
+            // Compute qualified class name to source path map.
+            String sourceKey = analysis.getPackagePrependedClassSource();
+            String sourcePath = sourcePaths.get(sourceKey);
+            mClassToSourceMap.put(className, sourcePath);
+
+            // compute classDependencies
+            classDependencies.computeIfAbsent(className, k ->
+                    new HashSet<>()).addAll(analysis.getClassDependencies());
+
+            // Compute constantRegistry
+            analysis.getConstantsDefined().forEach(c ->
+                    constantRegistry.computeIfAbsent(c, k -> new HashSet<>()).add(className));
+            // Compute inlinedUsages map.
+            analysis.inlinedUsages().forEach(u ->
+                    inlinedUsages.computeIfAbsent(u, k -> new HashSet<>()).add(className));
+
+            if (analysis.isDependencyToAll()) {
+                mDependencyToAll.add(sourcePath);
+            }
+        }
+        // Finally build file dependencies
+        buildFileDependencies(
+                combineDependencies(classDependencies, inlinedUsages, constantRegistry));
+    }
+
+    private Map<String, String> generateSourcePaths() {
+        Map<String, String> sourcePaths = new HashMap<>();
+        mJavaSourceDataList.forEach(data ->
+                sourcePaths.put(data.getPackagePrependedFileName(), data.getFilePath()));
+        return sourcePaths;
+    }
+
+    private Map<String, Set<String>> combineDependencies(Map<String, Set<String>> classDependencies,
+            Map<Object, Set<String>> inlinedUsages,
+            Map<Object, Set<String>> constantRegistry) {
+        Map<String, Set<String>> combined = new HashMap<>(
+                buildConstantDependencies(inlinedUsages, constantRegistry));
+        classDependencies.forEach((k, v) ->
+                combined.computeIfAbsent(k, key -> new HashSet<>()).addAll(v));
+        return combined;
+    }
+
+    private Map<String, Set<String>> buildConstantDependencies(
+            Map<Object, Set<String>> inlinedUsages, Map<Object, Set<String>> constantRegistry) {
+        Map<String, Set<String>> constantDependencies = new HashMap<>();
+        for (Map.Entry<Object, Set<String>> usageEntry : inlinedUsages.entrySet()) {
+            Object usage = usageEntry.getKey();
+            Set<String> usageClasses = usageEntry.getValue();
+            if (constantRegistry.containsKey(usage)) {
+                Set<String> declarationClasses = constantRegistry.get(usage);
+                for (String usageClass : usageClasses) {
+                    // Sometimes Usage and Declarations are in the same file, we remove such cases
+                    // to prevent circular dependency.
+                    declarationClasses.remove(usageClass);
+                    constantDependencies.computeIfAbsent(usageClass, k ->
+                            new HashSet<>()).addAll(declarationClasses);
+                }
+            }
+        }
+
+        return constantDependencies;
+    }
+
+    private void buildFileDependencies(Map<String, Set<String>> combinedClassDependencies) {
+        combinedClassDependencies.forEach((className, dependencies) -> {
+            String sourceFile = mClassToSourceMap.get(className);
+            if (sourceFile == null) {
+                throw new IllegalArgumentException("Class '" + className
+                        + "' does not have a corresponding source file.");
+            }
+            mFileDependencies.computeIfAbsent(sourceFile, k -> new HashSet<>());
+            dependencies.forEach(dependency -> {
+                String dependencySource = mClassToSourceMap.get(dependency);
+                if (dependencySource == null) {
+                    throw new IllegalArgumentException("Dependency '" + dependency
+                            + "' does not have a corresponding source file.");
+                }
+                mFileDependencies.get(sourceFile).add(dependencySource);
+            });
+        });
+    }
+
+    private void buildSourceToClassMap() {
+        mClassToSourceMap.forEach((className, sourceFile) ->
+                mSourceToClasses.computeIfAbsent(sourceFile, k ->
+                        new HashSet<>()).add(className));
+    }
+
+    private DependencyProto.FileDependencyList createFileDependencies() {
+        List<DependencyProto.FileDependency> fileDependencies = new ArrayList<>();
+        mFileDependencies.forEach((file, dependencies) -> {
+            DependencyProto.FileDependency dependency = DependencyProto.FileDependency.newBuilder()
+                    .setFilePath(file)
+                    .setIsDependencyToAll(mDependencyToAll.contains(file))
+                    .addAllGeneratedClasses(mSourceToClasses.get(file))
+                    .addAllFileDependencies(dependencies)
+                    .build();
+            fileDependencies.add(dependency);
+        });
+        return DependencyProto.FileDependencyList.newBuilder()
+                .addAllFileDependency(fileDependencies).build();
+    }
+}
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/JavaSourceAnalyzer.java b/tools/dependency_mapper/src/com/android/dependencymapper/JavaSourceAnalyzer.java
new file mode 100644
index 0000000..3a4efad
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/JavaSourceAnalyzer.java
@@ -0,0 +1,81 @@
+/*
+ * 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.android.dependencymapper;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An utility class that reads each java file present in the rsp content then analyzes the same,
+ * collecting the analysis in {@link List<JavaSourceData>}
+ */
+public class JavaSourceAnalyzer {
+
+    // Regex that matches against "package abc.xyz.lmn;" declarations in a java file.
+    private static final String PACKAGE_REGEX = "^package\\s+([a-zA-Z_][a-zA-Z0-9_.]*);";
+
+    public static List<JavaSourceData> analyze(Path srcRspFile) {
+        List<JavaSourceData> javaSourceDataList = new ArrayList<>();
+        try (BufferedReader reader = new BufferedReader(new FileReader(srcRspFile.toFile()))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                // Split the line by spaces, tabs, multiple java files can be on a single line.
+                String[] files = line.trim().split("\\s+");
+                for (String file : files) {
+                    Path p = Paths.get("", file);
+                    System.out.println(p.toAbsolutePath().toString());
+                    javaSourceDataList
+                            .add(new JavaSourceData(file, constructPackagePrependedFileName(file)));
+                }
+            }
+        } catch (IOException e) {
+            System.err.println("Error reading rsp file at: " + srcRspFile);
+            throw new RuntimeException(e);
+        }
+        return javaSourceDataList;
+    }
+
+    private static String constructPackagePrependedFileName(String filePath) {
+        String packageAppendedFileName = null;
+        // if the file path is abc/def/ghi/JavaFile.java we extract JavaFile.java
+        String javaFileName = filePath.substring(filePath.lastIndexOf("/") + 1);
+        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+            String line;
+            // Process each line and match against the package regex pattern.
+            while ((line = reader.readLine()) != null) {
+                Pattern pattern = Pattern.compile(PACKAGE_REGEX);
+                Matcher matcher = pattern.matcher(line);
+                if (matcher.find()) {
+                    packageAppendedFileName = matcher.group(1) + "." + javaFileName;
+                    break;
+                }
+            }
+        } catch (IOException e) {
+            System.err.println("Error reading java file at: " + filePath);
+            throw new RuntimeException(e);
+        }
+        // Should not be null
+        assert packageAppendedFileName != null;
+        return packageAppendedFileName;
+    }
+}
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/JavaSourceData.java b/tools/dependency_mapper/src/com/android/dependencymapper/JavaSourceData.java
new file mode 100644
index 0000000..89453d0
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/JavaSourceData.java
@@ -0,0 +1,38 @@
+/*
+ * 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.android.dependencymapper;
+
+/**
+ * POJO representing the data collected from Java Source file analysis.
+ */
+public class JavaSourceData {
+
+    private final String mFilePath;
+    private final String mPackagePrependedFileName;
+
+    public JavaSourceData(String filePath, String packagePrependedFileName) {
+        mFilePath = filePath;
+        mPackagePrependedFileName = packagePrependedFileName;
+    }
+
+    public String getFilePath() {
+        return mFilePath;
+    }
+
+    public String getPackagePrependedFileName() {
+        return mPackagePrependedFileName;
+    }
+}
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/Main.java b/tools/dependency_mapper/src/com/android/dependencymapper/Main.java
new file mode 100644
index 0000000..131c931
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/Main.java
@@ -0,0 +1,123 @@
+/*
+ * 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.android.dependencymapper;
+
+import static com.android.dependencymapper.Utils.listClassesInJar;
+
+import com.android.dependencymapper.DependencyProto;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+
+public class Main {
+
+    public static void main(String[] args) throws IOException, InterruptedException {
+        try {
+            InputData input = parseAndValidateInput(args);
+            generateDependencyMap(input);
+        } catch (IllegalArgumentException e) {
+            System.err.println("Error: " + e.getMessage());
+            showUsage();
+        }
+    }
+
+    private static class InputData {
+        public Path srcList;
+        public Path classesJar;
+        public Path dependencyMapProto;
+
+        public InputData(Path srcList, Path classesJar, Path dependencyMapProto) {
+            this.srcList = srcList;
+            this.classesJar = classesJar;
+            this.dependencyMapProto = dependencyMapProto;
+        }
+    }
+
+    private static InputData parseAndValidateInput(String[] args) {
+        for (String arg : args) {
+            if ("--help".equals(arg)) {
+                showUsage();
+                System.exit(0); // Indicate successful exit after showing help
+            }
+        }
+
+        if (args.length != 6) { // Explicitly check for the correct number of arguments
+            throw new IllegalArgumentException("Incorrect number of arguments");
+        }
+
+        Path srcList = null;
+        Path classesJar = null;
+        Path dependencyMapProto = null;
+
+        for (int i = 0; i < args.length; i += 2) {
+            String arg = args[i].trim();
+            String argValue = args[i + 1].trim();
+
+            switch (arg) {
+                case "--src-path" -> srcList = Path.of(argValue);
+                case "--jar-path" -> classesJar = Path.of(argValue);
+                case "--dependency-map-path" -> dependencyMapProto = Path.of(argValue);
+                default -> throw new IllegalArgumentException("Unknown argument: " + arg);
+            }
+        }
+
+        // Validate file existence and readability
+        validateFile(srcList, "--src-path");
+        validateFile(classesJar, "--jar-path");
+
+        return new InputData(srcList, classesJar, dependencyMapProto);
+    }
+
+    private static void validateFile(Path path, String argName) {
+        if (path == null) {
+            throw new IllegalArgumentException(argName + " is required");
+        }
+        if (!Files.exists(path)) {
+            throw new IllegalArgumentException(argName + " does not exist: " + path);
+        }
+        if (!Files.isReadable(path)) {
+            throw new IllegalArgumentException(argName + " is not readable: " + path);
+        }
+    }
+
+    private static void generateDependencyMap(InputData input) {
+        // First collect all classes in the jar.
+        Set<String> classesInJar = listClassesInJar(input.classesJar);
+        // Perform dependency analysis.
+        List<ClassDependencyData> classDependencyDataList = ClassDependencyAnalyzer
+                .analyze(input.classesJar, new ClassRelevancyFilter(classesInJar));
+        // Perform java source analysis.
+        List<JavaSourceData> javaSourceDataList = JavaSourceAnalyzer.analyze(input.srcList);
+        // Collect all dependencies and map them as DependencyProto.FileDependencyList
+        DependencyMapper dp = new DependencyMapper(classDependencyDataList, javaSourceDataList);
+        DependencyProto.FileDependencyList dependencyList =  dp.buildDependencyMaps();
+
+        // Write the proto to output file
+        Utils.writeContentsToProto(dependencyList, input.dependencyMapProto);
+    }
+
+    private static void showUsage() {
+        System.err.println(
+                "Usage: dependency-mapper "
+                        + "--src-path [src-list.rsp] "
+                        + "--jar-path [classes.jar] "
+                        + "--dependency-map-path [dependency-map.proto]");
+    }
+
+}
\ No newline at end of file
diff --git a/tools/dependency_mapper/src/com/android/dependencymapper/Utils.java b/tools/dependency_mapper/src/com/android/dependencymapper/Utils.java
new file mode 100644
index 0000000..5dd5f35
--- /dev/null
+++ b/tools/dependency_mapper/src/com/android/dependencymapper/Utils.java
@@ -0,0 +1,94 @@
+/*
+ * 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.android.dependencymapper;
+
+import com.android.dependencymapper.DependencyProto;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+public class Utils {
+
+    public static String trimAndConvertToPackageBasedPath(String fileBasedPath) {
+        // Remove ".class" from the fileBasedPath, then replace "/" with "."
+        return fileBasedPath.replaceAll("\\..*", "").replaceAll("/", ".");
+    }
+
+    public static String buildPackagePrependedClassSource(String qualifiedClassPath,
+            String classSource) {
+        // Find the location of the start of classname in the qualifiedClassPath
+        int classNameSt = qualifiedClassPath.lastIndexOf(".") + 1;
+        // Replace the classname in qualifiedClassPath with classSource
+        return qualifiedClassPath.substring(0, classNameSt) + classSource;
+    }
+
+    public static void writeContentsToJson(DependencyProto.FileDependencyList contents, Path jsonOut) {
+        Gson gson = new GsonBuilder().setPrettyPrinting().create();
+        Map<String, Set<String>> jsonMap = new HashMap<>();
+        for (DependencyProto.FileDependency fileDependency : contents.getFileDependencyList()) {
+            jsonMap.putIfAbsent(fileDependency.getFilePath(),
+                    Set.copyOf(fileDependency.getFileDependenciesList()));
+        }
+        String json = gson.toJson(jsonMap);
+        try (FileWriter file = new FileWriter(jsonOut.toFile())) {
+            file.write(json);
+        } catch (IOException e) {
+            System.err.println("Error writing json output to: " + jsonOut);
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void writeContentsToProto(DependencyProto.FileDependencyList usages, Path protoOut) {
+        try {
+            OutputStream outputStream = Files.newOutputStream(protoOut);
+            usages.writeDelimitedTo(outputStream);
+        } catch (IOException e) {
+            System.err.println("Error writing proto output to: " + protoOut);
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Set<String> listClassesInJar(Path classesJarPath) {
+        Set<String> classes = new HashSet<>();
+        try (JarFile jarFile = new JarFile(classesJarPath.toFile())) {
+            Enumeration<JarEntry> entries = jarFile.entries();
+            while (entries.hasMoreElements()) {
+                JarEntry entry = entries.nextElement();
+                if (entry.getName().endsWith(".class")) {
+                    String name = Utils.trimAndConvertToPackageBasedPath(entry.getName());
+                    classes.add(name);
+                }
+            }
+        } catch (IOException e) {
+            System.err.println("Error reading the jar file at: " + classesJarPath);
+            throw new RuntimeException(e);
+        }
+        return classes;
+    }
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/annotation/AnnotationUsage.java b/tools/dependency_mapper/tests/res/testdata/annotation/AnnotationUsage.java
new file mode 100644
index 0000000..bb40776
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/annotation/AnnotationUsage.java
@@ -0,0 +1,30 @@
+/*
+ * 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 res.testdata.annotation;
+
+@res.testdata.annotation.RuntimeAnnotation
+public class AnnotationUsage {
+
+    private final int mSourceAnnField;
+
+    public AnnotationUsage(@res.testdata.annotation.SourceAnnotation int sourceAnnField) {
+        mSourceAnnField = sourceAnnField;
+    }
+
+    public @res.testdata.annotation.SourceAnnotation int getSourceAnnField() {
+        return mSourceAnnField;
+    }
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/annotation/RuntimeAnnotation.java b/tools/dependency_mapper/tests/res/testdata/annotation/RuntimeAnnotation.java
new file mode 100644
index 0000000..99a6074
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/annotation/RuntimeAnnotation.java
@@ -0,0 +1,23 @@
+/*
+ * 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 res.testdata.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RuntimeAnnotation {
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/annotation/SourceAnnotation.java b/tools/dependency_mapper/tests/res/testdata/annotation/SourceAnnotation.java
new file mode 100644
index 0000000..dec3e83
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/annotation/SourceAnnotation.java
@@ -0,0 +1,23 @@
+/*
+ * 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 res.testdata.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.SOURCE)
+public @interface SourceAnnotation {
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/constants/ConstantDefinition.java b/tools/dependency_mapper/tests/res/testdata/constants/ConstantDefinition.java
new file mode 100644
index 0000000..3f0a789
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/constants/ConstantDefinition.java
@@ -0,0 +1,20 @@
+/*
+ * 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 res.testdata.constants;
+
+public class ConstantDefinition {
+    public static final String TEST_CONSTANT = "test_constant";
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/constants/ConstantUsage.java b/tools/dependency_mapper/tests/res/testdata/constants/ConstantUsage.java
new file mode 100644
index 0000000..852e4d5
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/constants/ConstantUsage.java
@@ -0,0 +1,25 @@
+/*
+ * 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 res.testdata.constants;
+
+public class ConstantUsage {
+
+    public ConstantUsage(){}
+
+    public String useConstantInMethodBody() {
+        return res.testdata.constants.ConstantDefinition.TEST_CONSTANT;
+    }
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/inheritance/BaseClass.java b/tools/dependency_mapper/tests/res/testdata/inheritance/BaseClass.java
new file mode 100644
index 0000000..3b11eb1
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/inheritance/BaseClass.java
@@ -0,0 +1,19 @@
+/*
+ * 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 res.testdata.inheritance;
+
+public class BaseClass {
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/inheritance/BaseImpl.java b/tools/dependency_mapper/tests/res/testdata/inheritance/BaseImpl.java
new file mode 100644
index 0000000..7c2698b
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/inheritance/BaseImpl.java
@@ -0,0 +1,21 @@
+/*
+ * 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 res.testdata.inheritance;
+
+public interface BaseImpl {
+
+    void baseImpl();
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/inheritance/InheritanceUsage.java b/tools/dependency_mapper/tests/res/testdata/inheritance/InheritanceUsage.java
new file mode 100644
index 0000000..f892479
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/inheritance/InheritanceUsage.java
@@ -0,0 +1,24 @@
+/*
+ * 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 res.testdata.inheritance;
+
+public class InheritanceUsage extends res.testdata.inheritance.BaseClass implements
+        res.testdata.inheritance.BaseImpl {
+    @Override
+    public void baseImpl() {
+
+    }
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/methods/FieldUsage.java b/tools/dependency_mapper/tests/res/testdata/methods/FieldUsage.java
new file mode 100644
index 0000000..0d97312
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/methods/FieldUsage.java
@@ -0,0 +1,21 @@
+/*
+ * 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 res.testdata.methods;
+
+public class FieldUsage {
+
+    private res.testdata.methods.ReferenceClass1 mReferenceClass1;
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/methods/MethodUsage.java b/tools/dependency_mapper/tests/res/testdata/methods/MethodUsage.java
new file mode 100644
index 0000000..9dd0223
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/methods/MethodUsage.java
@@ -0,0 +1,24 @@
+/*
+ * 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 res.testdata.methods;
+
+public class MethodUsage {
+
+    public void methodReferences(res.testdata.methods.ReferenceClass1 mReferenceClass1) {
+        res.testdata.methods.ReferenceClass2 referenceClass2 =
+                new res.testdata.methods.ReferenceClass2();
+    }
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/methods/ReferenceClass1.java b/tools/dependency_mapper/tests/res/testdata/methods/ReferenceClass1.java
new file mode 100644
index 0000000..f56c0a9
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/methods/ReferenceClass1.java
@@ -0,0 +1,21 @@
+/*
+ * 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 res.testdata.methods;
+
+public class ReferenceClass1 {
+
+    public ReferenceClass1(){}
+}
diff --git a/tools/dependency_mapper/tests/res/testdata/methods/ReferenceClass2.java b/tools/dependency_mapper/tests/res/testdata/methods/ReferenceClass2.java
new file mode 100644
index 0000000..09e7422
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testdata/methods/ReferenceClass2.java
@@ -0,0 +1,20 @@
+/*
+ * 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 res.testdata.methods;
+
+public class ReferenceClass2 {
+    public ReferenceClass2(){}
+}
diff --git a/tools/dependency_mapper/tests/res/testfiles/dependency-mapper-test-data.jar b/tools/dependency_mapper/tests/res/testfiles/dependency-mapper-test-data.jar
new file mode 100644
index 0000000..98f5893
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testfiles/dependency-mapper-test-data.jar
Binary files differ
diff --git a/tools/dependency_mapper/tests/res/testfiles/sources.rsp b/tools/dependency_mapper/tests/res/testfiles/sources.rsp
new file mode 100644
index 0000000..d895033
--- /dev/null
+++ b/tools/dependency_mapper/tests/res/testfiles/sources.rsp
@@ -0,0 +1,12 @@
+tests/res/testdata/annotation/AnnotationUsage.java
+tests/res/testdata/annotation/SourceAnnotation.java
+tests/res/testdata/annotation/RuntimeAnnotation.java
+tests/res/testdata/constants/ConstantDefinition.java
+tests/res/testdata/constants/ConstantUsage.java
+tests/res/testdata/inheritance/InheritanceUsage.java
+tests/res/testdata/inheritance/BaseClass.java
+tests/res/testdata/inheritance/BaseImpl.java
+tests/res/testdata/methods/FieldUsage.java
+tests/res/testdata/methods/MethodUsage.java
+tests/res/testdata/methods/ReferenceClass1.java
+tests/res/testdata/methods/ReferenceClass2.java
\ No newline at end of file
diff --git a/tools/dependency_mapper/tests/src/com/android/dependencymapper/ClassDependencyAnalyzerTest.java b/tools/dependency_mapper/tests/src/com/android/dependencymapper/ClassDependencyAnalyzerTest.java
new file mode 100644
index 0000000..95492c8
--- /dev/null
+++ b/tools/dependency_mapper/tests/src/com/android/dependencymapper/ClassDependencyAnalyzerTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.android.dependencymapper;
+
+import static com.android.dependencymapper.Utils.listClassesInJar;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class ClassDependencyAnalyzerTest {
+
+    private static List<ClassDependencyData> mClassDependencyDataList;
+
+    private static final String CLASSES_JAR_PATH =
+            "tests/res/testfiles/dependency-mapper-test-data.jar";
+
+    @BeforeClass
+    public static void beforeClass() throws URISyntaxException {
+        Path path = Paths.get(CLASSES_JAR_PATH);
+        Set<String> classesInJar = listClassesInJar(path);
+        // Perform dependency analysis.
+        mClassDependencyDataList = ClassDependencyAnalyzer.analyze(path,
+                new ClassRelevancyFilter(classesInJar));
+    }
+
+    @Test
+    public void testAnnotationDeps(){
+        String annoClass = "res.testdata.annotation.AnnotationUsage";
+        String sourceAnno = "res.testdata.annotation.SourceAnnotation";
+        String runTimeAnno = "res.testdata.annotation.RuntimeAnnotation";
+
+        dependencyVerifier(annoClass,
+                new HashSet<>(List.of(runTimeAnno)), new HashSet<>(List.of(sourceAnno)));
+
+        for (ClassDependencyData dep : mClassDependencyDataList) {
+            if (dep.getQualifiedName().equals(sourceAnno)) {
+                assertTrue(sourceAnno + " is not dependencyToAll ", dep.isDependencyToAll());
+            }
+            if (dep.getQualifiedName().equals(runTimeAnno)) {
+                assertFalse(runTimeAnno + " is dependencyToAll ", dep.isDependencyToAll());
+            }
+        }
+    }
+
+    @Test
+    public void testConstantsDeps(){
+        String constDefined = "test_constant";
+        String constDefClass = "res.testdata.constants.ConstantDefinition";
+        String constUsageClass = "res.testdata.constants.ConstantUsage";
+
+        boolean constUsageClassFound = false;
+        boolean constDefClassFound = false;
+        for (ClassDependencyData dep : mClassDependencyDataList) {
+            if (dep.getQualifiedName().equals(constUsageClass)) {
+                constUsageClassFound = true;
+                assertTrue("InlinedUsage of : " + constDefined + " not found",
+                        dep.inlinedUsages().contains(constDefined));
+            }
+            if (dep.getQualifiedName().equals(constDefClass)) {
+                constDefClassFound = true;
+                assertTrue("Constant " + constDefined + " not defined",
+                        dep.getConstantsDefined().contains(constDefined));
+            }
+        }
+        assertTrue("Class " + constUsageClass + " not found", constUsageClassFound);
+        assertTrue("Class " + constDefClass + " not found", constDefClassFound);
+    }
+
+    @Test
+    public void testInheritanceDeps(){
+        String sourceClass = "res.testdata.inheritance.InheritanceUsage";
+        String baseClass = "res.testdata.inheritance.BaseClass";
+        String baseImpl = "res.testdata.inheritance.BaseImpl";
+
+        dependencyVerifier(sourceClass,
+                new HashSet<>(List.of(baseClass, baseImpl)), new HashSet<>());
+    }
+
+
+    @Test
+    public void testMethodDeps(){
+        String fieldUsage = "res.testdata.methods.FieldUsage";
+        String methodUsage = "res.testdata.methods.MethodUsage";
+        String ref1 = "res.testdata.methods.ReferenceClass1";
+        String ref2 = "res.testdata.methods.ReferenceClass2";
+
+        dependencyVerifier(fieldUsage,
+                new HashSet<>(List.of(ref1)), new HashSet<>(List.of(ref2)));
+        dependencyVerifier(methodUsage,
+                new HashSet<>(List.of(ref1, ref2)), new HashSet<>());
+    }
+
+    private void dependencyVerifier(String qualifiedName, Set<String> deps, Set<String> nonDeps) {
+        boolean depFound = false;
+        for (ClassDependencyData classDependencyData : mClassDependencyDataList) {
+            if (classDependencyData.getQualifiedName().equals(qualifiedName)) {
+                depFound = true;
+                for (String dep : deps) {
+                    assertTrue(qualifiedName + " does not depends on " + dep,
+                            classDependencyData.getClassDependencies().contains(dep));
+                }
+                for (String nonDep : nonDeps) {
+                    assertFalse(qualifiedName + " depends on " + nonDep,
+                            classDependencyData.getClassDependencies().contains(nonDep));
+                }
+            }
+        }
+        assertTrue("Class " + qualifiedName + " not found", depFound);
+    }
+}
diff --git a/tools/dependency_mapper/tests/src/com/android/dependencymapper/ClassRelevancyFilterTest.java b/tools/dependency_mapper/tests/src/com/android/dependencymapper/ClassRelevancyFilterTest.java
new file mode 100644
index 0000000..9a80c4b
--- /dev/null
+++ b/tools/dependency_mapper/tests/src/com/android/dependencymapper/ClassRelevancyFilterTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.android.dependencymapper;
+
+import static com.android.dependencymapper.Utils.listClassesInJar;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.dependencymapper.ClassDependencyAnalyzer;
+import com.android.dependencymapper.ClassDependencyData;
+import com.android.dependencymapper.ClassRelevancyFilter;
+
+import org.junit.Test;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Set;
+
+public class ClassRelevancyFilterTest {
+
+    private static final String CLASSES_JAR_PATH =
+            "tests/res/testfiles/dependency-mapper-test-data.jar";
+
+    @Test
+    public void testClassRelevancyFilter() {
+        Path path = Paths.get(CLASSES_JAR_PATH);
+        Set<String> classesInJar = listClassesInJar(path);
+
+        // Add a relevancy filter that skips a class.
+        String skippedClass = "res.testdata.BaseClass";
+        classesInJar.remove(skippedClass);
+
+        // Perform dependency analysis.
+        List<ClassDependencyData> classDependencyDataList =
+                ClassDependencyAnalyzer.analyze(path, new ClassRelevancyFilter(classesInJar));
+
+        // check that the skipped class is not present in classDepsList
+        for (ClassDependencyData dep : classDependencyDataList) {
+            assertNotEquals("SkippedClass " + skippedClass + " is present",
+                    skippedClass, dep.getQualifiedName());
+            assertFalse("SkippedClass " + skippedClass + " is present as dependency of " + dep,
+                    dep.getClassDependencies().contains(skippedClass));
+        }
+    }
+}
diff --git a/tools/dependency_mapper/tests/src/com/android/dependencymapper/DependencyMapperTest.java b/tools/dependency_mapper/tests/src/com/android/dependencymapper/DependencyMapperTest.java
new file mode 100644
index 0000000..9c08e79
--- /dev/null
+++ b/tools/dependency_mapper/tests/src/com/android/dependencymapper/DependencyMapperTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.android.dependencymapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+public class DependencyMapperTest {
+
+    private static final List<JavaSourceData> mJavaSourceData = new ArrayList<>();
+    private static final List<ClassDependencyData> mClassDependencyData = new ArrayList<>();
+
+    private static Map<String, DependencyProto.FileDependency>  mFileDependencyMap;
+
+    public static String AUDIO_CONS = "AUDIO_CONS";
+    public static String AUDIO_CONS_PATH = "frameworks/base/audio/AudioPermission.java";
+    public static String AUDIO_CONS_PACKAGE = "com.android.audio.AudioPermission";
+
+    public static String AUDIO_TONE_CONS_1 = "AUDIO_TONE_CONS_1";
+    public static String AUDIO_TONE_CONS_2 = "AUDIO_TONE_CONS_2";
+    public static String AUDIO_TONE_CONS_PATH = "frameworks/base/audio/Audio$Tones.java";
+    public static String AUDIO_TONE_CONS_PACKAGE = "com.android.audio.Audio$Tones";
+
+    public static String ST_MANAGER_PATH = "frameworks/base/core/storage/StorageManager.java";
+    public static String ST_MANAGER_PACKAGE = "com.android.storage.StorageManager";
+
+    public static String CONST_OUTSIDE_SCOPE = "CONST_OUTSIDE_SCOPE";
+    public static String PERM_MANAGER_PATH =  "frameworks/base/core/permission/PermissionManager.java";
+    public static String PERM_MANAGER_PACKAGE =  "com.android.permission.PermissionManager";
+
+    public static String SOURCE_ANNO_PATH = "frameworks/base/anno/SourceAnno.java";
+    public static String SOURCE_ANNO_PACKAGE = "com.android.anno.SourceAnno";
+
+    public static String PERM_SOURCE_PATH = "frameworks/base/core/permission/PermissionSources.java";
+    public static String PERM_SOURCE_PACKAGE = "com.android.permission.PermissionSources";
+
+    public static String PERM_DATA_PATH = "frameworks/base/core/permission/PermissionSources$Data.java";
+    public static String PERM_DATA_PACKAGE = "com.android.permission.PermissionSources$Data";
+
+    static {
+        JavaSourceData audioConstants = new JavaSourceData(AUDIO_CONS_PATH, AUDIO_CONS_PACKAGE + ".java");
+        JavaSourceData audioToneConstants =
+                new JavaSourceData(AUDIO_TONE_CONS_PATH, AUDIO_TONE_CONS_PACKAGE + ".java"); //f2
+        JavaSourceData stManager = new JavaSourceData( ST_MANAGER_PATH, ST_MANAGER_PACKAGE + ".java");
+        JavaSourceData permManager = new JavaSourceData(PERM_MANAGER_PATH, PERM_MANAGER_PACKAGE + ".java");
+        JavaSourceData permSource = new JavaSourceData(PERM_SOURCE_PATH, PERM_SOURCE_PACKAGE + ".java");
+        JavaSourceData permSourceData = new JavaSourceData(PERM_DATA_PATH, PERM_DATA_PACKAGE + ".java");
+
+        JavaSourceData sourceNotPresentInClass =
+                new JavaSourceData(SOURCE_ANNO_PATH, SOURCE_ANNO_PACKAGE);
+
+        mJavaSourceData.addAll(List.of(audioConstants, audioToneConstants, stManager,
+                permManager, permSource, permSourceData, sourceNotPresentInClass));
+
+        ClassDependencyData audioConstantsDeps =
+                new ClassDependencyData(AUDIO_CONS_PACKAGE + ".java",
+                        AUDIO_CONS_PACKAGE, new HashSet<>(), false,
+                        new HashSet<>(List.of(AUDIO_CONS)), new HashSet<>());
+
+        ClassDependencyData audioToneConstantsDeps =
+                new ClassDependencyData(AUDIO_TONE_CONS_PACKAGE + ".java",
+                        AUDIO_TONE_CONS_PACKAGE, new HashSet<>(), false,
+                        new HashSet<>(List.of(AUDIO_TONE_CONS_1, AUDIO_TONE_CONS_2)),
+                        new HashSet<>());
+
+        ClassDependencyData stManagerDeps =
+                new ClassDependencyData(ST_MANAGER_PACKAGE + ".java",
+                        ST_MANAGER_PACKAGE, new HashSet<>(List.of(PERM_SOURCE_PACKAGE)), false,
+                        new HashSet<>(), new HashSet<>(List.of(AUDIO_CONS, AUDIO_TONE_CONS_1)));
+
+        ClassDependencyData permManagerDeps =
+                new ClassDependencyData(PERM_MANAGER_PACKAGE + ".java", PERM_MANAGER_PACKAGE,
+                        new HashSet<>(List.of(PERM_SOURCE_PACKAGE, PERM_DATA_PACKAGE)), false,
+                        new HashSet<>(), new HashSet<>(List.of(CONST_OUTSIDE_SCOPE)));
+
+        ClassDependencyData permSourceDeps =
+                new ClassDependencyData(PERM_SOURCE_PACKAGE + ".java",
+                        PERM_SOURCE_PACKAGE, new HashSet<>(), false,
+                        new HashSet<>(), new HashSet<>());
+
+        ClassDependencyData permSourceDataDeps =
+                new ClassDependencyData(PERM_DATA_PACKAGE + ".java",
+                        PERM_DATA_PACKAGE, new HashSet<>(), false,
+                        new HashSet<>(), new HashSet<>());
+
+        mClassDependencyData.addAll(List.of(audioConstantsDeps, audioToneConstantsDeps,
+                stManagerDeps, permManagerDeps, permSourceDeps, permSourceDataDeps));
+    }
+
+    @BeforeClass
+    public static void beforeAll(){
+        mFileDependencyMap = buildActualDepsMap(
+                new DependencyMapper(mClassDependencyData, mJavaSourceData).buildDependencyMaps());
+    }
+
+    @Test
+    public void testFileDependencies() {
+        // Test for AUDIO_CONS_PATH
+        DependencyProto.FileDependency audioDepsActual = mFileDependencyMap.get(AUDIO_CONS_PATH);
+        assertNotNull(AUDIO_CONS_PATH + " not found in dependencyList", audioDepsActual);
+        // This file should have 0 dependencies.
+        validateDependencies(audioDepsActual, AUDIO_CONS_PATH, 0, new ArrayList<>());
+
+        // Test for AUDIO_TONE_CONS_PATH
+        DependencyProto.FileDependency audioToneDepsActual =
+                mFileDependencyMap.get(AUDIO_TONE_CONS_PATH);
+        assertNotNull(AUDIO_TONE_CONS_PATH + " not found in dependencyList", audioDepsActual);
+        // This file should have 0 dependencies.
+        validateDependencies(audioToneDepsActual, AUDIO_TONE_CONS_PATH, 0, new ArrayList<>());
+
+        // Test for ST_MANAGER_PATH
+        DependencyProto.FileDependency stManagerDepsActual =
+                mFileDependencyMap.get(ST_MANAGER_PATH);
+        assertNotNull(ST_MANAGER_PATH + " not found in dependencyList", audioDepsActual);
+        // This file should have 3 dependencies.
+        validateDependencies(stManagerDepsActual, ST_MANAGER_PATH, 3,
+                new ArrayList<>(List.of(AUDIO_CONS_PATH, AUDIO_TONE_CONS_PATH, PERM_SOURCE_PATH)));
+
+        // Test for PERM_MANAGER_PATH
+        DependencyProto.FileDependency permManagerDepsActual =
+                mFileDependencyMap.get(PERM_MANAGER_PATH);
+        assertNotNull(PERM_MANAGER_PATH + " not found in dependencyList", audioDepsActual);
+        // This file should have 2 dependencies.
+        validateDependencies(permManagerDepsActual, PERM_MANAGER_PATH, 2,
+                new ArrayList<>(List.of(PERM_SOURCE_PATH, PERM_DATA_PATH)));
+
+        // Test for PERM_SOURCE_PATH
+        DependencyProto.FileDependency permSourceDepsActual =
+                mFileDependencyMap.get(PERM_SOURCE_PATH);
+        assertNotNull(PERM_SOURCE_PATH + " not found in dependencyList", audioDepsActual);
+        // This file should have 0 dependencies.
+        validateDependencies(permSourceDepsActual, PERM_SOURCE_PATH, 0, new ArrayList<>());
+
+        // Test for PERM_DATA_PATH
+        DependencyProto.FileDependency permDataDepsActual =
+                mFileDependencyMap.get(PERM_DATA_PATH);
+        assertNotNull(PERM_DATA_PATH + " not found in dependencyList", audioDepsActual);
+        // This file should have 0 dependencies.
+        validateDependencies(permDataDepsActual, PERM_DATA_PATH, 0, new ArrayList<>());
+    }
+
+    private void validateDependencies(DependencyProto.FileDependency dependency, String fileName, int fileDepsCount, List<String> fileDeps) {
+        assertEquals(fileName + " does not have expected dependencies", fileDepsCount, dependency.getFileDependenciesCount());
+        assertTrue(fileName + " does not have expected dependencies", dependency.getFileDependenciesList().containsAll(fileDeps));
+    }
+
+    private static Map<String, DependencyProto.FileDependency> buildActualDepsMap(
+            DependencyProto.FileDependencyList fileDependencyList) {
+        Map<String, DependencyProto.FileDependency> dependencyMap = new HashMap<>();
+        for (DependencyProto.FileDependency fileDependency : fileDependencyList.getFileDependencyList()) {
+            if (fileDependency.getFilePath().equals(AUDIO_CONS_PATH)) {
+                dependencyMap.put(AUDIO_CONS_PATH, fileDependency);
+            }
+            if (fileDependency.getFilePath().equals(AUDIO_TONE_CONS_PATH)) {
+                dependencyMap.put(AUDIO_TONE_CONS_PATH, fileDependency);
+            }
+            if (fileDependency.getFilePath().equals(ST_MANAGER_PATH)) {
+                dependencyMap.put(ST_MANAGER_PATH, fileDependency);
+            }
+            if (fileDependency.getFilePath().equals(PERM_MANAGER_PATH)) {
+                dependencyMap.put(PERM_MANAGER_PATH, fileDependency);
+            }
+            if (fileDependency.getFilePath().equals(PERM_SOURCE_PATH)) {
+                dependencyMap.put(PERM_SOURCE_PATH, fileDependency);
+            }
+            if (fileDependency.getFilePath().equals(PERM_DATA_PATH)) {
+                dependencyMap.put(PERM_DATA_PATH, fileDependency);
+            }
+            if (fileDependency.getFilePath().equals(SOURCE_ANNO_PATH)) {
+                dependencyMap.put(SOURCE_ANNO_PATH, fileDependency);
+            }
+        }
+        assertFalse(SOURCE_ANNO_PATH + " found in dependencyList",
+                dependencyMap.containsKey(SOURCE_ANNO_PATH));
+        return dependencyMap;
+    }
+}
diff --git a/tools/dependency_mapper/tests/src/com/android/dependencymapper/JavaSourceAnalyzerTest.java b/tools/dependency_mapper/tests/src/com/android/dependencymapper/JavaSourceAnalyzerTest.java
new file mode 100644
index 0000000..1ca2b2a
--- /dev/null
+++ b/tools/dependency_mapper/tests/src/com/android/dependencymapper/JavaSourceAnalyzerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.android.dependencymapper;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class JavaSourceAnalyzerTest {
+    private static List<JavaSourceData> mJavaSourceDataList;
+
+    private static final String SOURCES_RSP_PATH =
+            "tests/res/testfiles/sources.rsp";
+
+    @BeforeClass
+    public static void beforeClass() throws URISyntaxException {
+        Path path = Paths.get(SOURCES_RSP_PATH);
+        // Perform source analysis.
+        mJavaSourceDataList = JavaSourceAnalyzer.analyze(path);
+    }
+
+    @Test
+    public void validateSourceData() {
+        Map<String, String> expectedSourceData = expectedSourceData();
+        int expectedFileCount = expectedSourceData.size();
+        int actualFileCount = 0;
+        for (JavaSourceData javaSourceData : mJavaSourceDataList) {
+            String file =  javaSourceData.getFilePath();
+            if (expectedSourceData.containsKey(file)) {
+                actualFileCount++;
+                assertEquals("Source Data not generated correctly for " + file,
+                        expectedSourceData.get(file), javaSourceData.getPackagePrependedFileName());
+            }
+        }
+        assertEquals("Not all source files processed", expectedFileCount, actualFileCount);
+    }
+
+    private Map<String, String> expectedSourceData() {
+        Map<String, String> expectedSourceData = new HashMap<>();
+        expectedSourceData.put("tests/res/testdata/annotation/AnnotationUsage.java",
+                "res.testdata.annotation.AnnotationUsage.java");
+        expectedSourceData.put("tests/res/testdata/constants/ConstantUsage.java",
+                "res.testdata.constants.ConstantUsage.java");
+        expectedSourceData.put("tests/res/testdata/inheritance/BaseClass.java",
+                "res.testdata.inheritance.BaseClass.java");
+        expectedSourceData.put("tests/res/testdata/methods/FieldUsage.java",
+                "res.testdata.methods.FieldUsage.java");
+        return expectedSourceData;
+    }
+}
diff --git a/tools/dependency_mapper/tests/src/com/android/dependencymapper/UtilsTest.java b/tools/dependency_mapper/tests/src/com/android/dependencymapper/UtilsTest.java
new file mode 100644
index 0000000..39c5190
--- /dev/null
+++ b/tools/dependency_mapper/tests/src/com/android/dependencymapper/UtilsTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.android.dependencymapper;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.dependencymapper.Utils;
+
+public class UtilsTest {
+
+    @Test
+    public void testTrimAndConvertToPackageBasedPath() {
+        String testPath1 = "com/android/storage/StorageManager.class";
+        String testPath2 = "com/android/package/PackageManager$Package.class";
+
+        String expectedPackageBasedPath1 = "com.android.storage.StorageManager";
+        String expectedPackageBasedPath2 = "com.android.package.PackageManager$Package";
+
+        assertEquals("Package Based Path not constructed correctly",
+                expectedPackageBasedPath1, Utils.trimAndConvertToPackageBasedPath(testPath1));
+        assertEquals("Package Based Path not constructed correctly",
+                expectedPackageBasedPath2, Utils.trimAndConvertToPackageBasedPath(testPath2));
+    }
+
+    @Test
+    public void testBuildPackagePrependedClassSource() {
+        String qualifiedClassPath1 = "com.android.storage.StorageManager";
+        String sourcePath1 = "StorageManager.java";
+        String qualifiedClassPath2 = "com.android.package.PackageManager$Package";
+        String sourcePath2 = "PackageManager.java";
+        String qualifiedClassPath3 = "com.android.storage.StorageManager$Storage";
+        String sourcePath3 = "StorageManager$Storage.java";
+
+
+        String expectedPackagePrependedPath1 = "com.android.storage.StorageManager.java";
+        String expectedPackagePrependedPath2 = "com.android.package.PackageManager.java";
+        String expectedPackagePrependedPath3 = "com.android.storage.StorageManager$Storage.java";
+
+        assertEquals("Package Prepended Class Source not constructed correctly",
+                expectedPackagePrependedPath1,
+                Utils.buildPackagePrependedClassSource(qualifiedClassPath1, sourcePath1));
+        assertEquals("Package Prepended Class Source not constructed correctly",
+                expectedPackagePrependedPath2,
+                Utils.buildPackagePrependedClassSource(qualifiedClassPath2, sourcePath2));
+        assertEquals("Package Prepended Class Source not constructed correctly",
+                expectedPackagePrependedPath3,
+                Utils.buildPackagePrependedClassSource(qualifiedClassPath3, sourcePath3));
+    }
+}