Merge "Fix: Inaccurate mangled names."
diff --git a/vndk/tools/definition-tool/README.md b/vndk/tools/definition-tool/README.md
index 1314446..dbda891 100644
--- a/vndk/tools/definition-tool/README.md
+++ b/vndk/tools/definition-tool/README.md
@@ -88,7 +88,7 @@
 
     - The libraries will be installed to `/vendor/lib[64]/vndk-$VND-ext`
 
-10. **extra-vendor**
+10. **extra-vendor-lib**
 
     - This contains the extra libraries that have to be copied from
       `/system/lib[64]` to `/vendor/lib[64]`.
diff --git a/vndk/tools/definition-tool/assets/index.html b/vndk/tools/definition-tool/assets/index.html
new file mode 100644
index 0000000..bd935cc
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+
+<html>
+	<head>
+		<meta charset="utf-8" />
+		<title>VNDK Dependency Insight</title>
+		<link rel="stylesheet" href="insight.css" media="all" />
+		<script type="text/javascript" src="insight.js"></script>
+		<script type="text/javascript" src="insight-data.js"></script>
+	</head>
+
+	<body></body>
+</html>
diff --git a/vndk/tools/definition-tool/assets/insight-data.js b/vndk/tools/definition-tool/assets/insight-data.js
new file mode 100644
index 0000000..9ef7a65
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/insight-data.js
@@ -0,0 +1,21 @@
+(function () {
+    var strs = [
+        '/system/lib/libc.so',
+        '/system/lib/libm.so',
+        '/system/lib/libdl.so',
+        '/system/lib64/libc.so',
+        '/system/lib64/libm.so',
+        '/system/lib64/libdl.so',
+        'll-ndk',
+        'hl-ndk',
+    ];
+    var mods = [
+        [0, 32, [6], [[1, 2]], []],
+        [1, 32, [6], [], [0]],
+        [2, 32, [6], [], [0]],
+        [3, 64, [6], [[5], [4]], []],
+        [4, 64, [7], [[5]], [3]],
+        [5, 64, [7], [], [3, 4]],
+    ];
+    insight.init(document, strs, mods);
+})();
diff --git a/vndk/tools/definition-tool/assets/insight.css b/vndk/tools/definition-tool/assets/insight.css
new file mode 100644
index 0000000..7cff186
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/insight.css
@@ -0,0 +1,103 @@
+p, li, td, th, h1, h2, h3 {
+	line-height: 1.6;
+}
+
+#control {
+	background-color: #eefaff;
+	border: 1px solid #3399ff;
+	border-collapse: collapse;
+	margin: 0px 0px 20px 0px;
+	width: 100%;
+}
+
+#control td {
+	padding: 3px;
+}
+
+#control_menu:link {
+	display: block;
+	width: 5em;
+	height: 20pt;
+	text-align: center;
+	line-height: 20pt;
+	color: #ffffff;
+	background-color: #3399ff;
+	position: fixed;
+	top: 0px;
+	right: 0px;
+}
+
+#control_menu:visited {
+	color: #ffffff;
+}
+
+#control_menu:hover {
+	color: #ffffff;
+	background-color: #0000ff;
+}
+
+.menu {
+	list-style-type: none;
+	margin: 0px;
+	padding: 0px;
+}
+
+.menu li {
+	display: inline;
+	padding: 0px 1em 0px 0px;
+}
+
+a:link {
+	color: #2244ff;
+	text-decoration: none;
+	font-family: monospace;
+}
+
+a:visited {
+	color: #2244ff;
+	text-decoration: none;
+}
+
+a:hover {
+	color: #ff3322;
+	text-decoration: underline;
+}
+
+a:active {
+	color: #ff3322;
+	text-decoration: underline;
+}
+
+#module_table {
+	border-collapse: collapse;
+}
+
+#module_tbody td,
+#module_tbody th {
+	border: 1px solid #dddddd;
+	padding: 3px 20px;
+	vertical-align: top;
+}
+
+#module_tbody th {
+	background-color: #eeeeee;
+}
+
+#module_tbody h1,
+#module_tbody h2,
+#module_tbody p {
+	font-size: 100%;
+	margin: 0px;
+	padding: 0px;
+}
+
+#module_tbody ol,
+#module_tbody ul {
+	margin: .3em 0px;
+	padding: 0px 0px 0px 2em;
+}
+
+#module_tbody tr:target {
+	background-color: #fffff0;
+	transition: background-color 800ms ease;
+}
diff --git a/vndk/tools/definition-tool/assets/insight.js b/vndk/tools/definition-tool/assets/insight.js
new file mode 100644
index 0000000..2a9202f
--- /dev/null
+++ b/vndk/tools/definition-tool/assets/insight.js
@@ -0,0 +1,439 @@
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        define([], factory);
+    } else if (typeof module === 'object' && module.exports) {
+        module.exports = factory();
+    } else {
+        root.insight = factory();
+    }
+} (this, function () {
+    'use strict';
+
+    let document;
+    let strsData, mods, tagIds;
+    let domPathInput;
+    let domTBody;
+
+    //--------------------------------------------------------------------------
+    // DOM Helper Functions
+    //--------------------------------------------------------------------------
+    function domNewText(text) {
+        return document.createTextNode(text);
+    }
+
+    function domNewElem(type) {
+        let dom = document.createElement(type);
+        for (let i = 1; i < arguments.length; ++i) {
+            let arg = arguments[i];
+            if (typeof(arg) == 'string' || typeof(arg) == 'number') {
+                arg = domNewText(arg)
+            }
+            dom.appendChild(arg);
+        }
+        return dom;
+    }
+
+    function domNewLink(text, onClick) {
+        let dom = domNewElem('a', text);
+        dom.setAttribute('href', '#');
+        dom.addEventListener('click', onClick);
+        return dom;
+    }
+
+    //--------------------------------------------------------------------------
+    // Module Row
+    //--------------------------------------------------------------------------
+    function countDeps(deps) {
+        let direct = 0;
+        let indirect = 0;
+        if (deps.length > 0) {
+            direct = deps[0].length;
+            for (let i = 1; i < deps.length; ++i) {
+                indirect += deps[i].length;
+            }
+        }
+        return [direct, indirect];
+    }
+
+    function Module(id, modData) {
+        this.id = id;
+        this.path = strsData[modData[0]];
+        this.cls = modData[1];
+        this.tagIds = new Set(modData[2]);
+        this.deps = modData[3];
+        this.users = modData[4];
+
+        [this.numDirectDeps, this.numIndirectDeps] = countDeps(this.deps);
+        this.numUsers = this.users.length;
+
+        this.dom = null;
+        this.visible = false;
+
+        this.linkDoms = Object.create(null);
+    }
+
+    Module.prototype.isTagged = function (tagId) {
+        return this.tagIds.has(tagId);
+    }
+
+    Module.prototype.createModuleLinkDom = function (mod) {
+        let dom = domNewElem('a', mod.path);
+        dom.setAttribute('href', '#mod_' + mod.id);
+        dom.setAttribute('data-mod-id', mod.id);
+        dom.setAttribute('data-owner-id', this.id);
+        dom.addEventListener('click', onModuleLinkClicked);
+        dom.addEventListener('mouseover', onModuleLinkMouseOver);
+        dom.addEventListener('mouseout', onModuleLinkMouseOut);
+
+        this.linkDoms[mod.id] = dom;
+
+        return dom;
+    }
+
+    Module.prototype.createModuleRelationsDom = function (parent, label,
+                                                          modIds) {
+        parent.appendChild(domNewElem('h2', label));
+
+        let domOl = domNewElem('ol');
+        parent.appendChild(domOl);
+        for (let modId of modIds) {
+            domOl.appendChild(
+                    domNewElem('li', this.createModuleLinkDom(mods[modId])));
+        }
+    }
+
+    Module.prototype.createModulePathTdDom = function (parent) {
+        parent.appendChild(domNewElem('td', this.createModuleLinkDom(this)));
+    }
+
+    Module.prototype.createTagsTdDom = function (parent) {
+        let domTd = domNewElem('td');
+        for (let tag of this.tagIds) {
+            domTd.appendChild(domNewElem('p', strsData[tag]));
+        }
+        parent.appendChild(domTd);
+    }
+
+    Module.prototype.createDepsTdDom = function (parent) {
+        let domTd = domNewElem(
+                'td', this.numDirectDeps + ' + ' + this.numIndirectDeps);
+
+        let deps = this.deps;
+        if (deps.length > 0) {
+            this.createModuleRelationsDom(domTd, 'Direct', deps[0]);
+
+            for (let i = 1; i < deps.length; ++i) {
+                this.createModuleRelationsDom(domTd, 'Indirect #' + i, deps[i]);
+            }
+        }
+
+        parent.appendChild(domTd);
+    }
+
+    Module.prototype.createUsersTdDom = function (parent) {
+        let domTd = domNewElem('td', this.numUsers);
+
+        let users = this.users;
+        if (users.length > 0) {
+            this.createModuleRelationsDom(domTd, 'Direct', users);
+        }
+
+        parent.appendChild(domTd);
+    }
+
+    Module.prototype.createDom = function () {
+        let dom = this.dom = domNewElem('tr');
+        dom.setAttribute('id', 'mod_'  + this.id);
+
+        this.createModulePathTdDom(dom);
+        this.createTagsTdDom(dom);
+        this.createDepsTdDom(dom);
+        this.createUsersTdDom(dom)
+    }
+
+    Module.prototype.showDom = function () {
+        if (this.visible) {
+            return;
+        }
+        domTBody.appendChild(this.dom);
+        this.visible = true;
+    }
+
+    Module.prototype.hideDom = function () {
+        if (!this.visible) {
+            return;
+        }
+        this.dom.parentNode.removeChild(this.dom);
+        this.visible = false;
+    }
+
+    function createModulesFromData(stringsData, modulesData) {
+        return modulesData.map(function (modData, id) {
+            return new Module(id, modData);
+        });
+    }
+
+    function createTagIdsFromData(stringsData, mods) {
+        let tagIds = new Set();
+        for (let mod of mods) {
+            for (let tag of mod.tagIds) {
+                tagIds.add(tag);
+            }
+        }
+
+        tagIds = Array.from(tagIds);
+        tagIds.sort(function (a, b) {
+            return strsData[a].localeCompare(strsData[b]);
+        });
+
+        return tagIds;
+    }
+
+    //--------------------------------------------------------------------------
+    // Data
+    //--------------------------------------------------------------------------
+    function init(doc, stringsData, modulesData) {
+        document = doc;
+        strsData = stringsData;
+
+        mods = createModulesFromData(stringsData, modulesData);
+        tagIds = createTagIdsFromData(stringsData, mods);
+
+        document.addEventListener('DOMContentLoaded', function (evt) {
+            createControlDom(document.body);
+            createTableDom(document.body);
+        });
+    }
+
+    //--------------------------------------------------------------------------
+    // Control
+    //--------------------------------------------------------------------------
+    function createControlDom(parent) {
+        let domTBody = domNewElem('tbody');
+
+        createSelectionTrDom(domTBody);
+        createAddByTagsTrDom(domTBody);
+        createAddByPathTrDom(domTBody);
+
+        let domTable = domNewElem('table', domTBody);
+        domTable.id = 'control';
+
+        let domFixedLink = domNewElem('a', 'Menu');
+        domFixedLink.href = '#control';
+        domFixedLink.id = 'control_menu';
+
+        parent.appendChild(domFixedLink);
+        parent.appendChild(domTable);
+    }
+
+    function createControlMenuTr(parent, label, items) {
+        let domUl = domNewElem('ul');
+        domUl.className = 'menu';
+        for (let [txt, callback] of items) {
+            domUl.appendChild(domNewElem('li', domNewLink(txt, callback)));
+        }
+
+        let domTr = domNewElem('tr',
+                               createControlLabelTdDom(label),
+                               domNewElem('td', domUl));
+
+        parent.appendChild(domTr);
+    }
+
+    function createSelectionTrDom(parent) {
+        const items = [
+            ['All', onAddAll],
+            ['32-bit', onAddAll32],
+            ['64-bit', onAddAll64],
+            ['Clear', onClear],
+        ];
+
+        createControlMenuTr(parent, 'Selection:', items);
+    }
+
+    function createAddByTagsTrDom(parent) {
+        if (tagIds.length == 0) {
+            return;
+        }
+
+        const items = tagIds.map(function (tagId) {
+            return [strsData[tagId], function (evt) {
+                evt.preventDefault(true);
+                showModulesByTagId(tagId);
+            }];
+        });
+
+        createControlMenuTr(parent, 'Add by Tags:', items);
+    }
+
+    function createAddByPathTrDom(parent) {
+        let domForm = domNewElem('form');
+        domForm.addEventListener('submit', onAddModuleByPath);
+
+        domPathInput = domNewElem('input');
+        domPathInput.type = 'text';
+        domForm.appendChild(domPathInput);
+
+        let domBtn = domNewElem('input');
+        domBtn.type = 'submit';
+        domBtn.value = 'Add';
+        domForm.appendChild(domBtn);
+
+        let domTr = domNewElem('tr',
+                               createControlLabelTdDom('Add by Path:'),
+                               domNewElem('td', domForm));
+
+        parent.appendChild(domTr);
+    }
+
+    function createControlLabelTdDom(text) {
+        return domNewElem('td', domNewElem('strong', text));
+    }
+
+
+    //--------------------------------------------------------------------------
+    // Table
+    //--------------------------------------------------------------------------
+    function createTableDom(parent) {
+        domTBody = domNewElem('tbody');
+        domTBody.id = 'module_tbody';
+
+        createTableHeaderDom(domTBody);
+        createAllModulesDom();
+        showAllModules();
+
+        let domTable  = domNewElem('table', domTBody);
+        domTable.id = 'module_table';
+
+        parent.appendChild(domTable);
+    }
+
+    function createTableHeaderDom(parent) {
+        const labels = [
+            'Name',
+            'Tags',
+            'Dependencies (Direct + Indirect)',
+            'Users',
+        ];
+
+        let domTr = domNewElem('tr');
+        for (let label of labels) {
+            domTr.appendChild(domNewElem('th', label));
+        }
+
+        parent.appendChild(domTr);
+    }
+
+    function createAllModulesDom() {
+        for (let mod of mods) {
+            mod.createDom();
+        }
+    }
+
+    function hideAllModules() {
+        for (let mod of mods) {
+            mod.hideDom();
+        }
+    }
+
+    function showAllModules() {
+        for (let mod of mods) {
+            mod.showDom();
+        }
+    }
+
+    function showModulesByFilter(pred) {
+        for (let mod of mods) {
+            if (pred(mod)) {
+                mod.showDom();
+            }
+        }
+    }
+
+    function showModulesByTagId(tagId) {
+        showModulesByFilter(function (mod) {
+            return mod.isTagged(tagId);
+        });
+    }
+
+
+    //--------------------------------------------------------------------------
+    // Events
+    //--------------------------------------------------------------------------
+
+    function onAddModuleByPath(evt) {
+        evt.preventDefault();
+
+        let path = domPathInput.value;
+        domPathInput.value = '';
+
+        for (let mod of mods) {
+            if (mod.path == path) {
+                mod.showDom();
+                return;
+            }
+        }
+        alert('Path not found: ' + path);
+    }
+
+    function onAddAll(evt) {
+        evt.preventDefault(true);
+        hideAllModules();
+        showAllModules();
+    }
+
+    function onAddAllClass(evt, cls) {
+        evt.preventDefault(true);
+        hideAllModules();
+        showModulesByFilter(function (mod) {
+            return mod.cls == cls;
+        });
+    }
+
+    function onAddAll32(evt) {
+        onAddAllClass(evt, 32);
+    }
+
+    function onAddAll64(evt) {
+        onAddAllClass(evt, 64);
+    }
+
+    function onClear(evt) {
+        evt.preventDefault(true);
+        hideAllModules();
+    }
+
+    function onModuleLinkClicked(evt) {
+        let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10);
+        mods[modId].showDom();
+    }
+
+    function setDirectDepBackgroundColor(modId, ownerId, color) {
+        let mod = mods[modId];
+        let owner = mods[ownerId];
+        let ownerLinkDoms = owner.linkDoms;
+        if (mod.deps.length > 0) {
+            for (let depId of mod.deps[0]) {
+                if (depId in ownerLinkDoms) {
+                    ownerLinkDoms[depId].style.backgroundColor = color;
+                }
+            }
+        }
+    }
+
+    function onModuleLinkMouseOver(evt) {
+        let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10);
+        let ownerId = parseInt(evt.target.getAttribute('data-owner-id'), 10);
+        setDirectDepBackgroundColor(modId, ownerId, '#ffff00');
+    }
+
+    function onModuleLinkMouseOut(evt) {
+        let modId = parseInt(evt.target.getAttribute('data-mod-id'), 10);
+        let ownerId = parseInt(evt.target.getAttribute('data-owner-id'), 10);
+        setDirectDepBackgroundColor(modId, ownerId, 'transparent');
+    }
+
+    return {
+        'init': init,
+    };
+}));
diff --git a/vndk/tools/definition-tool/tests/test_elf_linker.py b/vndk/tools/definition-tool/tests/test_elf_linker.py
index 25129a7..7b5930b 100755
--- a/vndk/tools/definition-tool/tests/test_elf_linker.py
+++ b/vndk/tools/definition-tool/tests/test_elf_linker.py
@@ -389,10 +389,14 @@
         # LL-NDK (should be excluded from result)
         gb.add_multilib(PT_SYSTEM, 'libc')
 
+        # SP-Both VNDK-stable
+        gb.add_multilib(PT_SYSTEM, 'libsp_both_vs')
+
         # SP-NDK VNDK-stable
         gb.add_multilib(PT_SYSTEM, 'libcutils_dep', dt_needed=['libc.so'])
         gb.add_multilib(PT_SYSTEM, 'libcutils',
-                        dt_needed=['libc.so', 'libcutils_dep.so'])
+                        dt_needed=['libc.so', 'libcutils_dep.so',
+                                   'libsp_both_vs.so'])
 
         # SP-NDK dependencies
         gb.add_multilib(PT_SYSTEM, 'libutils',
@@ -410,7 +414,7 @@
         # SP-HAL VNDK-stable
         gb.add_multilib(PT_SYSTEM, 'libhidlbase')
         gb.add_multilib(PT_SYSTEM, 'libhidlmemory',
-                        dt_needed=['libhidlbase.so'])
+                        dt_needed=['libhidlbase.so', 'libsp_both_vs.so'])
 
         # SP-HAL
         gb.add_multilib(PT_VENDOR, 'libEGL_chipset', extra_dir='egl',
@@ -426,22 +430,32 @@
                     return GenericRefs.NEW_LIB
                 return GenericRefs.EXPORT_EQUAL
 
-        sp_hal, sp_hal_dep, sp_hal_vndk_stable, sp_ndk, sp_ndk_vndk_stable = \
-                gb.graph.compute_sp_lib(MockGenericRefs())
+        sp_lib = gb.graph.compute_sp_lib(MockGenericRefs())
 
-        self.assertEqual(2 * 1, len(sp_hal))
-        self.assertEqual(2 * 2, len(sp_hal_dep))
-        self.assertEqual(2 * 2, len(sp_hal_vndk_stable))
-        self.assertEqual(2 * 1, len(sp_ndk))
-        self.assertEqual(2 * 3, len(sp_ndk_vndk_stable))
+        self.assertEqual(2 * 1, len(sp_lib.sp_hal))
+        self.assertEqual(2 * 2, len(sp_lib.sp_hal_dep))
+        self.assertEqual(2 * 2, len(sp_lib.sp_hal_vndk_stable))
+        self.assertEqual(2 * 1, len(sp_lib.sp_ndk))
+        self.assertEqual(2 * 3, len(sp_lib.sp_ndk_vndk_stable))
+        self.assertEqual(2 * 1, len(sp_lib.sp_both_vndk_stable))
 
-        sp_hal = self._get_paths_from_nodes(sp_hal)
-        sp_hal_dep = self._get_paths_from_nodes(sp_hal_dep)
-        sp_hal_vndk_stable = self._get_paths_from_nodes(sp_hal_vndk_stable)
-        sp_ndk = self._get_paths_from_nodes(sp_ndk)
-        sp_ndk_vndk_stable = self._get_paths_from_nodes(sp_ndk_vndk_stable)
+        sp_hal = self._get_paths_from_nodes(sp_lib.sp_hal)
+        sp_hal_dep = self._get_paths_from_nodes(sp_lib.sp_hal_dep)
+        sp_hal_vndk_stable = self._get_paths_from_nodes(
+                sp_lib.sp_hal_vndk_stable)
+
+        sp_ndk = self._get_paths_from_nodes(sp_lib.sp_ndk)
+        sp_ndk_vndk_stable = self._get_paths_from_nodes(
+                sp_lib.sp_ndk_vndk_stable)
+
+        sp_both_vndk_stable = self._get_paths_from_nodes(
+                sp_lib.sp_both_vndk_stable)
 
         for lib_dir in ('lib', 'lib64'):
+            # SP-Both
+            self.assertIn('/system/{}/libsp_both_vs.so'.format(lib_dir),
+                          sp_both_vndk_stable)
+
             # SP-NDK dependencies
             self.assertIn('/system/{}/libcutils.so'.format(lib_dir),
                           sp_ndk_vndk_stable)
diff --git a/vndk/tools/definition-tool/tests/test_vndk.py b/vndk/tools/definition-tool/tests/test_vndk.py
index 4bc03df..dc7eb15 100755
--- a/vndk/tools/definition-tool/tests/test_vndk.py
+++ b/vndk/tools/definition-tool/tests/test_vndk.py
@@ -10,7 +10,7 @@
 
 from compat import StringIO
 from vndk_definition_tool import (ELF, ELFLinker, PT_SYSTEM, PT_VENDOR,
-                                  GenericRefs)
+                                  GenericRefs, SPLibResult)
 
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 TESTDATA_DIR = os.path.join(SCRIPT_DIR ,'testdata', 'test_vndk')
@@ -23,42 +23,55 @@
     def _get_paths_from_nodes(self, nodes):
         return sorted([node.path for node in nodes])
 
-    def test_compute_vndk(self):
-        input_dir = os.path.join(TESTDATA_DIR, 'pre_treble')
+    def _create_graph_gr(self, input_dir, generic_refs_dir):
+        if not generic_refs_dir:
+            generic_refs = None
+        else:
+            generic_refs_dir = os.path.join(TESTDATA_DIR, generic_refs_dir)
+            generic_refs = GenericRefs.create_from_dir(generic_refs_dir)
 
-        graph = ELFLinker.create_from_dump(
-                system_dirs=[os.path.join(input_dir, 'system')],
-                vendor_dirs=[os.path.join(input_dir, 'vendor')])
-
-        vndk = graph.compute_vndk(sp_hals=set(), vndk_stable=set(),
-                                  vndk_customized_for_system=set(),
-                                  vndk_customized_for_vendor=set(),
-                                  generic_refs=None,
-                                  banned_libs=MockBannedLibs())
-
-        self.assertEqual(['/system/lib/libcutils.so',
-                          '/system/lib64/libcutils.so'],
-                         self._get_paths_from_nodes(vndk.vndk_core))
-        self.assertEqual([], self._get_paths_from_nodes(vndk.vndk_fwk_ext))
-        self.assertEqual([], self._get_paths_from_nodes(vndk.vndk_vnd_ext))
-
-    def test_compute_vndk_fwk_ext(self):
-        generic_refs_dir = os.path.join(TESTDATA_DIR, 'vndk_gr')
-
-        generic_refs = GenericRefs.create_from_dir(generic_refs_dir)
-
-        input_dir = os.path.join(TESTDATA_DIR, 'vndk_fwk_ext')
+        input_dir = os.path.join(TESTDATA_DIR, input_dir)
 
         graph = ELFLinker.create_from_dump(
                 system_dirs=[os.path.join(input_dir, 'system')],
                 vendor_dirs=[os.path.join(input_dir, 'vendor')],
                 generic_refs=generic_refs)
 
-        vndk = graph.compute_vndk(sp_hals=set(), vndk_stable=set(),
-                                  vndk_customized_for_system=set(),
-                                  vndk_customized_for_vendor=set(),
-                                  generic_refs=generic_refs,
-                                  banned_libs=MockBannedLibs())
+        return (graph, generic_refs)
+
+    def _create_graph_vndk(self, input_dir, generic_refs_dir):
+        graph, generic_refs = self._create_graph_gr(input_dir, generic_refs_dir)
+
+        vndk = graph._compute_vndk(
+                sp_lib=SPLibResult(set(), set(), set(), set(), set(), set()),
+                vndk_customized_for_system=set(),
+                vndk_customized_for_vendor=set(),
+                generic_refs=generic_refs,
+                banned_libs=MockBannedLibs())
+
+        return (graph, vndk)
+
+    def test_compute_vndk(self):
+        graph, vndk = self._create_graph_vndk('pre_treble', None)
+
+        self.assertEqual(['/system/lib/vndk/libcutils.so',
+                          '/system/lib64/vndk/libcutils.so'],
+                         self._get_paths_from_nodes(vndk.vndk_core))
+        self.assertEqual([], self._get_paths_from_nodes(vndk.vndk_fwk_ext))
+        self.assertEqual([], self._get_paths_from_nodes(vndk.vndk_vnd_ext))
+
+    def test_compute_vndk_indirect_no_gr(self):
+        graph, vndk = self._create_graph_vndk('vndk_indirect', None)
+
+        self.assertEqual(['/system/lib/vndk/libcutils.so',
+                          '/system/lib64/vndk/libcutils.so'],
+                         self._get_paths_from_nodes(vndk.vndk_core))
+        self.assertEqual(['/system/lib/vndk/libcutils_dep.so',
+                          '/system/lib64/vndk/libcutils_dep.so'],
+                         self._get_paths_from_nodes(vndk.vndk_indirect))
+
+    def test_compute_vndk_fwk_ext(self):
+        graph, vndk = self._create_graph_vndk('vndk_fwk_ext', 'vndk_gr')
 
         self.assertEqual(['/system/lib/vndk/libRS.so',
                           '/system/lib/vndk/libcutils.so',
@@ -71,22 +84,7 @@
         self.assertEqual([], self._get_paths_from_nodes(vndk.vndk_vnd_ext))
 
     def test_compute_vndk_vnd_ext(self):
-        generic_refs_dir = os.path.join(TESTDATA_DIR, 'vndk_gr')
-
-        generic_refs = GenericRefs.create_from_dir(generic_refs_dir)
-
-        input_dir = os.path.join(TESTDATA_DIR, 'vndk_vnd_ext')
-
-        graph = ELFLinker.create_from_dump(
-                system_dirs=[os.path.join(input_dir, 'system')],
-                vendor_dirs=[os.path.join(input_dir, 'vendor')],
-                generic_refs=generic_refs)
-
-        vndk = graph.compute_vndk(sp_hals=set(), vndk_stable=set(),
-                                  vndk_customized_for_system=set(),
-                                  vndk_customized_for_vendor=set(),
-                                  generic_refs=generic_refs,
-                                  banned_libs=MockBannedLibs())
+        graph, vndk = self._create_graph_vndk('vndk_vnd_ext', 'vndk_gr')
 
         self.assertEqual(['/system/lib/vndk/libRS.so',
                           '/system/lib/vndk/libcutils.so',
@@ -99,16 +97,8 @@
                          self._get_paths_from_nodes(vndk.vndk_vnd_ext))
 
     def test_compute_vndk_inward_customization(self):
-        generic_refs_dir = os.path.join(TESTDATA_DIR, 'vndk_gr')
-
-        generic_refs = GenericRefs.create_from_dir(generic_refs_dir)
-
-        input_dir = os.path.join(TESTDATA_DIR, 'vndk_inward_customization')
-
-        graph = ELFLinker.create_from_dump(
-                system_dirs=[os.path.join(input_dir, 'system')],
-                vendor_dirs=[os.path.join(input_dir, 'vendor')],
-                generic_refs=generic_refs)
+        graph, generic_refs = self._create_graph_gr(
+                'vndk_inward_customization', 'vndk_gr')
 
         # Make sure libjpeg.so was loaded from the input dir.
         libjpeg_32 = graph.get_lib('/system/lib/libjpeg.so')
@@ -117,11 +107,12 @@
         self.assertIsNotNone(libjpeg_64)
 
         # Compute vndk sets and move libraries to the correct directories.
-        vndk = graph.compute_vndk(sp_hals=set(), vndk_stable=set(),
-                                  vndk_customized_for_system=set(),
-                                  vndk_customized_for_vendor=set(),
-                                  generic_refs=generic_refs,
-                                  banned_libs=MockBannedLibs())
+        vndk = graph._compute_vndk(
+                sp_lib=SPLibResult(set(), set(), set(), set(), set(), set()),
+                vndk_customized_for_system=set(),
+                vndk_customized_for_vendor=set(),
+                generic_refs=generic_refs,
+                banned_libs=MockBannedLibs())
 
         # Check vndk-core libraries.
         self.assertEqual(['/system/lib/vndk/libRS.so',
@@ -161,22 +152,8 @@
         # will break the vndk-indirect computation because libC is not in
         # generic references.
 
-        generic_refs_dir = os.path.join(TESTDATA_DIR, 'vndk_indirect_ext_gr')
-
-        generic_refs = GenericRefs.create_from_dir(generic_refs_dir)
-
-        input_dir = os.path.join(TESTDATA_DIR, 'vndk_indirect_ext')
-
-        graph = ELFLinker.create_from_dump(
-                system_dirs=[os.path.join(input_dir, 'system')],
-                vendor_dirs=[os.path.join(input_dir, 'vendor')],
-                generic_refs=generic_refs)
-
-        vndk = graph.compute_vndk(sp_hals=set(), vndk_stable=set(),
-                                  vndk_customized_for_system=set(),
-                                  vndk_customized_for_vendor=set(),
-                                  generic_refs=generic_refs,
-                                  banned_libs=MockBannedLibs())
+        graph, vndk = self._create_graph_vndk('vndk_indirect_ext',
+                                              'vndk_indirect_ext_gr')
 
         self.assertEqual(['/system/lib/vndk/libRS.so',
                           '/system/lib/vndk/libcutils.so',
@@ -198,22 +175,7 @@
         # vndk-vnd-ext in the first round, libA depends libC, libC depends
         # libB.
 
-        generic_refs_dir = os.path.join(TESTDATA_DIR, 'vndk_ext_dep_gr')
-
-        generic_refs = GenericRefs.create_from_dir(generic_refs_dir)
-
-        input_dir = os.path.join(TESTDATA_DIR, 'vndk_ext_dep')
-
-        graph = ELFLinker.create_from_dump(
-                system_dirs=[os.path.join(input_dir, 'system')],
-                vendor_dirs=[os.path.join(input_dir, 'vendor')],
-                generic_refs=generic_refs)
-
-        vndk = graph.compute_vndk(sp_hals=set(), vndk_stable=set(),
-                                  vndk_customized_for_system=set(),
-                                  vndk_customized_for_vendor=set(),
-                                  generic_refs=generic_refs,
-                                  banned_libs=MockBannedLibs())
+        graph, vndk = self._create_graph_vndk('vndk_ext_dep', 'vndk_ext_dep_gr')
 
         self.assertEqual(['/system/lib/vndk/libA.so',
                           '/system/lib/vndk/libB.so',
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libRS.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libRS.so.sym
new file mode 100644
index 0000000..6b2702a
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libRS.so.sym
@@ -0,0 +1,8 @@
+EI_CLASS	32
+EI_DATA		Little-Endian
+E_MACHINE	EM_ARM
+DT_NEEDED	libdl.so
+EXP_SYMBOL	rsContextCreate
+IMP_SYMBOL	dlclose
+IMP_SYMBOL	dlopen
+IMP_SYMBOL	dlsym
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libc.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libc.so.sym
new file mode 100644
index 0000000..0f9b747
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libc.so.sym
@@ -0,0 +1,12 @@
+EI_CLASS	32
+EI_DATA		Little-Endian
+E_MACHINE	EM_ARM
+DT_NEEDED	libdl.so
+DT_NEEDED	libm.so
+EXP_SYMBOL	fclose
+EXP_SYMBOL	fopen
+EXP_SYMBOL	fread
+IMP_SYMBOL	dlclose
+IMP_SYMBOL	dlopen
+IMP_SYMBOL	cos
+IMP_SYMBOL	sin
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libcutils.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libcutils.so.sym
new file mode 100644
index 0000000..ba09abf
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libcutils.so.sym
@@ -0,0 +1,10 @@
+EI_CLASS	32
+EI_DATA		Little-Endian
+E_MACHINE	EM_ARM
+DT_NEEDED	libc.so
+DT_NEEDED	libcutils_dep.so
+DT_NEEDED	libdl.so
+IMP_SYMBOL	dlclose
+IMP_SYMBOL	dlopen
+IMP_SYMBOL	fclose
+IMP_SYMBOL	fopen
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libcutils_dep.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libcutils_dep.so.sym
new file mode 100644
index 0000000..770ee84
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libcutils_dep.so.sym
@@ -0,0 +1,9 @@
+EI_CLASS	32
+EI_DATA		Little-Endian
+E_MACHINE	EM_ARM
+DT_NEEDED	libc.so
+DT_NEEDED	libdl.so
+IMP_SYMBOL	dlclose
+IMP_SYMBOL	dlopen
+IMP_SYMBOL	fclose
+IMP_SYMBOL	fopen
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libdl.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libdl.so.sym
new file mode 100644
index 0000000..9cfd2a2
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libdl.so.sym
@@ -0,0 +1,6 @@
+EI_CLASS	32
+EI_DATA		Little-Endian
+E_MACHINE	EM_ARM
+EXP_SYMBOL	dlclose
+EXP_SYMBOL	dlopen
+EXP_SYMBOL	dlsym
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libm.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libm.so.sym
new file mode 100644
index 0000000..3919c02
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib/libm.so.sym
@@ -0,0 +1,5 @@
+EI_CLASS	32
+EI_DATA		Little-Endian
+E_MACHINE	EM_ARM
+EXP_SYMBOL	cos
+EXP_SYMBOL	sin
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libRS.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libRS.so.sym
new file mode 100644
index 0000000..95408ef
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libRS.so.sym
@@ -0,0 +1,8 @@
+EI_CLASS	64
+EI_DATA		Little-Endian
+E_MACHINE	EM_AARCH64
+DT_NEEDED	libdl.so
+EXP_SYMBOL	rsContextCreate
+IMP_SYMBOL	dlclose
+IMP_SYMBOL	dlopen
+IMP_SYMBOL	dlsym
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libc.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libc.so.sym
new file mode 100644
index 0000000..e3843e6
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libc.so.sym
@@ -0,0 +1,12 @@
+EI_CLASS	64
+EI_DATA		Little-Endian
+E_MACHINE	EM_AARCH64
+DT_NEEDED	libdl.so
+DT_NEEDED	libm.so
+EXP_SYMBOL	fclose
+EXP_SYMBOL	fopen
+EXP_SYMBOL	fread
+IMP_SYMBOL	dlclose
+IMP_SYMBOL	dlopen
+IMP_SYMBOL	cos
+IMP_SYMBOL	sin
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libcutils.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libcutils.so.sym
new file mode 100644
index 0000000..37c0344
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libcutils.so.sym
@@ -0,0 +1,10 @@
+EI_CLASS	64
+EI_DATA		Little-Endian
+E_MACHINE	EM_AARCH64
+DT_NEEDED	libc.so
+DT_NEEDED	libcutils_dep.so
+DT_NEEDED	libdl.so
+IMP_SYMBOL	dlclose
+IMP_SYMBOL	dlopen
+IMP_SYMBOL	fclose
+IMP_SYMBOL	fopen
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libcutils_dep.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libcutils_dep.so.sym
new file mode 100644
index 0000000..5a4fbf7
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libcutils_dep.so.sym
@@ -0,0 +1,9 @@
+EI_CLASS	64
+EI_DATA		Little-Endian
+E_MACHINE	EM_AARCH64
+DT_NEEDED	libc.so
+DT_NEEDED	libdl.so
+IMP_SYMBOL	dlclose
+IMP_SYMBOL	dlopen
+IMP_SYMBOL	fclose
+IMP_SYMBOL	fopen
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libdl.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libdl.so.sym
new file mode 100644
index 0000000..5e06f27
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libdl.so.sym
@@ -0,0 +1,6 @@
+EI_CLASS	64
+EI_DATA		Little-Endian
+E_MACHINE	EM_AARCH64
+EXP_SYMBOL	dlclose
+EXP_SYMBOL	dlopen
+EXP_SYMBOL	dlsym
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libm.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libm.so.sym
new file mode 100644
index 0000000..e9c0ca4
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/system/lib64/libm.so.sym
@@ -0,0 +1,5 @@
+EI_CLASS	64
+EI_DATA		Little-Endian
+E_MACHINE	EM_AARCH64
+EXP_SYMBOL	cos
+EXP_SYMBOL	sin
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/vendor/lib/libEGL_chipset.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/vendor/lib/libEGL_chipset.so.sym
new file mode 100644
index 0000000..076d58f
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/vendor/lib/libEGL_chipset.so.sym
@@ -0,0 +1,9 @@
+EI_CLASS	32
+EI_DATA		Little-Endian
+E_MACHINE	EM_ARM
+DT_NEEDED	libc.so
+DT_NEEDED	libcutils.so
+DT_NEEDED	libdl.so
+EXP_SYMBOL	eglGetDisplay
+IMP_SYMBOL	fclose
+IMP_SYMBOL	fopen
diff --git a/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/vendor/lib64/libEGL_chipset.so.sym b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/vendor/lib64/libEGL_chipset.so.sym
new file mode 100644
index 0000000..2429b9c
--- /dev/null
+++ b/vndk/tools/definition-tool/tests/testdata/test_vndk/vndk_indirect/vendor/lib64/libEGL_chipset.so.sym
@@ -0,0 +1,9 @@
+EI_CLASS	64
+EI_DATA		Little-Endian
+E_MACHINE	EM_AARCH64
+DT_NEEDED	libc.so
+DT_NEEDED	libcutils.so
+DT_NEEDED	libdl.so
+EXP_SYMBOL	eglGetDisplay
+IMP_SYMBOL	fclose
+IMP_SYMBOL	fopen
diff --git a/vndk/tools/definition-tool/vndk_definition_tool.py b/vndk/tools/definition-tool/vndk_definition_tool.py
index c93faa7..bbe0b74 100755
--- a/vndk/tools/definition-tool/vndk_definition_tool.py
+++ b/vndk/tools/definition-tool/vndk_definition_tool.py
@@ -5,8 +5,10 @@
 import argparse
 import collections
 import itertools
+import json
 import os
 import re
+import shutil
 import stat
 import struct
 import sys
@@ -619,9 +621,54 @@
 NUM_PARTITIONS = 2
 
 
-VNDKHeuristics = collections.namedtuple(
-        'VNDKHeuristics',
-        'extra_vendor_libs vndk_core vndk_indirect vndk_fwk_ext vndk_vnd_ext')
+VNDKResult = collections.namedtuple(
+        'VNDKResult',
+        'sp_hal sp_hal_dep sp_hal_vndk_stable sp_ndk sp_ndk_vndk_stable '
+        'sp_both_vndk_stable '
+        'extra_vendor_lib vndk_core vndk_indirect vndk_fwk_ext vndk_vnd_ext')
+
+def print_vndk_lib(vndk_lib, file=sys.stdout):
+    # SP-NDK and SP-HAL
+    print_sp_lib(vndk_lib, file=file)
+
+    # VNDK (framework)
+    for lib in sorted_lib_path_list(vndk_lib.vndk_core):
+        print('vndk-core:', lib, file=file)
+    for lib in sorted_lib_path_list(vndk_lib.vndk_indirect):
+        print('vndk-indirect:', lib, file=file)
+    for lib in sorted_lib_path_list(vndk_lib.vndk_fwk_ext):
+        print('vndk-fwk-ext:', lib, file=file)
+
+    # VNDK (vendor)
+    for lib in sorted_lib_path_list(vndk_lib.vndk_vnd_ext):
+        print('vndk-vnd-ext:', lib, file=file)
+    for lib in sorted_lib_path_list(vndk_lib.extra_vendor_lib):
+        print('extra-vendor-lib:', lib, file=file)
+
+
+SPLibResult = collections.namedtuple(
+        'SPLibResult',
+        'sp_hal sp_hal_dep sp_hal_vndk_stable sp_ndk sp_ndk_vndk_stable '
+        'sp_both_vndk_stable')
+
+def print_sp_lib(sp_lib, file=sys.stdout):
+    # SP-NDK
+    for lib in sorted_lib_path_list(sp_lib.sp_ndk):
+        print('sp-ndk:', lib, file=file)
+    for lib in sorted_lib_path_list(sp_lib.sp_ndk_vndk_stable):
+        print('sp-ndk-vndk-stable:', lib, file=file)
+
+    # SP-HAL
+    for lib in sorted_lib_path_list(sp_lib.sp_hal):
+        print('sp-hal:', lib, file=file)
+    for lib in sorted_lib_path_list(sp_lib.sp_hal_dep):
+        print('sp-hal-dep:', lib, file=file)
+    for lib in sorted_lib_path_list(sp_lib.sp_hal_vndk_stable):
+        print('sp-hal-vndk-stable:', lib, file=file)
+
+    # SP-both
+    for lib in sorted_lib_path_list(sp_lib.sp_both_vndk_stable):
+        print('sp-both-vndk-stable:', lib, file=file)
 
 
 class ELFResolver(object):
@@ -998,6 +1045,7 @@
 
         sp_ndk = self.compute_sp_ndk()
         sp_ndk_closure = self.compute_closure(sp_ndk, is_ndk)
+        sp_ndk_vndk_stable = sp_ndk_closure - sp_ndk
 
         sp_hal = self.compute_predefined_sp_hal()
         sp_hal_closure = self.compute_closure(sp_hal, is_ndk)
@@ -1014,11 +1062,12 @@
             else:
                 sp_hal_dep.add(lib)
 
-        sp_ndk_vndk_stable = sp_ndk_closure - sp_ndk
-        sp_hal_vndk_stable = sp_hal_vndk_stable - sp_ndk - sp_ndk_vndk_stable
+        sp_both_vndk_stable = sp_ndk_vndk_stable & sp_hal_vndk_stable
+        sp_ndk_vndk_stable -= sp_both_vndk_stable
+        sp_hal_vndk_stable -= sp_both_vndk_stable
 
-        return (sp_hal, sp_hal_dep, sp_hal_vndk_stable, sp_ndk, \
-                sp_ndk_vndk_stable)
+        return SPLibResult(sp_hal, sp_hal_dep, sp_hal_vndk_stable, sp_ndk,
+                           sp_ndk_vndk_stable, sp_both_vndk_stable)
 
     def _po_component_sorted(self, lib_set, get_successors,
                              get_strong_successors):
@@ -1148,8 +1197,24 @@
 
         return (vndk_core, vndk_fwk_ext, vndk_vnd_ext)
 
-    def compute_vndk(self, sp_hals, vndk_stable, vndk_customized_for_system,
+    def compute_vndk(self, vndk_customized_for_system,
                      vndk_customized_for_vendor, generic_refs, banned_libs):
+        return self._compute_vndk(
+                self.compute_sp_lib(generic_refs), vndk_customized_for_system,
+                vndk_customized_for_vendor, generic_refs, banned_libs)
+
+    def _compute_vndk(self, sp_lib, vndk_customized_for_system,
+                      vndk_customized_for_vendor, generic_refs, banned_libs):
+        # Compute sp-hal and vndk-stable.
+        vndk_stable = sp_lib.sp_hal_vndk_stable | sp_lib.sp_ndk_vndk_stable | \
+                      sp_lib.sp_both_vndk_stable
+        sp_hal_closure = sp_lib.sp_hal | sp_lib.sp_hal_dep
+
+        # Normalize partition tags.  We expect many violations from the
+        # pre-Treble world.  Guess a resolution for the incorrect partition
+        # tag.
+        self.normalize_partition_tags(sp_lib.sp_hal, generic_refs)
+
         # ELF resolvers.
         VNDK_CORE_SEARCH_PATH32 = (
             '/system/lib/vndk',
@@ -1190,7 +1255,7 @@
         # Collect VNDK candidates.
         def is_not_vndk(lib):
             return (lib.is_ndk or banned_libs.is_banned(lib.path) or
-                    (lib in sp_hals) or (lib in vndk_stable))
+                    (lib in sp_hal_closure) or (lib in vndk_stable))
 
         def collect_libs_with_partition_user(lib_set, partition):
             result = set()
@@ -1207,7 +1272,7 @@
         vndk_visited = set(vndk_candidates)
 
         # Sets for missing libraries.
-        extra_vendor_libs = set()
+        extra_vendor_lib = set()
 
         def get_vndk_core_lib_name(lib):
             lib_name = os.path.basename(lib.path)
@@ -1249,9 +1314,14 @@
             vndk_core.add(vndk_core_lib)
             return vndk_core_lib
 
+        def add_to_vndk_core(lib):
+            self.rename_lib(lib, PT_SYSTEM, get_vndk_core_lib_name(lib))
+            vndk_core.add(lib)
+
         # Compute vndk-core, vndk-fwk-ext and vndk-vnd-ext.
         if not generic_refs:
-            vndk_core.update(vndk_candidates)
+            for lib in vndk_candidates:
+                add_to_vndk_core(lib)
         else:
             while vndk_candidates:
                 if __debug__:
@@ -1288,10 +1358,6 @@
                         if lib not in user.imported_ext_symbols:
                             replace_linked_lib(user, lib, generic_lib, dep_type)
 
-                def add_to_vndk_core(lib):
-                    self.rename_lib(lib, PT_SYSTEM, get_vndk_core_lib_name(lib))
-                    vndk_core.add(lib)
-
                 def add_to_vndk_fwk_ext(lib, generic_lib):
                     self.rename_lib(lib, PT_SYSTEM,
                                     get_vndk_fwk_ext_lib_name(lib))
@@ -1344,7 +1410,7 @@
                 for lib in prev_vndk_candidates:
                     category = generic_refs.classify_lib(lib)
                     if category == GenericRefs.NEW_LIB:
-                        extra_vendor_libs.add(lib)
+                        extra_vendor_lib.add(lib)
                         add_deps_to_vndk_candidate(lib)
                     elif category == GenericRefs.EXPORT_EQUAL:
                         vndk_customized_candidates.add(lib)
@@ -1444,8 +1510,12 @@
             else:
                 vndk_indirect.add(lib)
 
-        return VNDKHeuristics(extra_vendor_libs, vndk_core, vndk_indirect,
-                              vndk_fwk_ext, vndk_vnd_ext)
+        return VNDKResult(
+                sp_lib.sp_hal, sp_lib.sp_hal_dep, sp_lib.sp_hal_vndk_stable,
+                sp_lib.sp_ndk, sp_lib.sp_ndk_vndk_stable,
+                sp_lib.sp_both_vndk_stable,
+                extra_vendor_lib, vndk_core, vndk_indirect,
+                vndk_fwk_ext, vndk_vnd_ext)
 
     def compute_vndk_cap(self, banned_libs):
         # ELF files on vendor partitions are banned unconditionally.  ELF files
@@ -1672,31 +1742,15 @@
                 help='sub directory of vendor partition that has system files')
 
 
-class VNDKCommand(ELFGraphCommand):
-    def __init__(self):
-        super(VNDKCommand, self).__init__(
-                'vndk', help='Compute VNDK libraries set')
-
+class VNDKCommandBase(ELFGraphCommand):
     def add_argparser_options(self, parser):
-        super(VNDKCommand, self).add_argparser_options(parser)
+        super(VNDKCommandBase, self).add_argparser_options(parser)
 
         parser.add_argument(
                 '--load-generic-refs',
                 help='compare with generic reference symbols')
 
         parser.add_argument(
-                '--warn-incorrect-partition', action='store_true',
-                help='warn about libraries only have cross partition linkages')
-
-        parser.add_argument(
-                '--warn-high-level-ndk-deps', action='store_true',
-                help='warn about VNDK depends on high-level NDK')
-
-        parser.add_argument(
-                '--warn-banned-vendor-lib-deps', action='store_true',
-                help='warn when a vendor binaries depends on banned lib')
-
-        parser.add_argument(
                 '--ban-vendor-lib-dep', action='append',
                 help='library that must not be used by vendor binaries')
 
@@ -1712,6 +1766,98 @@
                 '--outward-customization-for-vendor', action='append',
                 help='outward customized vndk for vendor partition')
 
+    def _check_arg_dir_exists(self, arg_name, dirs):
+        for path in dirs:
+            if not os.path.exists(path):
+                print('error: Failed to find the directory "{}" specified in {}'
+                        .format(path, arg_name), file=sys.stderr)
+                sys.exit(1)
+            if not os.path.isdir(path):
+                print('error: Path "{}" specified in {} is not a directory'
+                        .format(path, arg_name), file=sys.stderr)
+                sys.exit(1)
+
+    def check_dirs_from_args(self, args):
+        self._check_arg_dir_exists('--system', args.system)
+        self._check_arg_dir_exists('--vendor', args.vendor)
+
+    def _get_generic_refs_from_args(self, args):
+        if not args.load_generic_refs:
+            return None
+        return GenericRefs.create_from_dir(args.load_generic_refs)
+
+    def _get_banned_libs_from_args(self, args):
+        if not args.ban_vendor_lib_dep:
+            return BannedLibDict.create_default()
+
+        banned_libs = BannedLibDict()
+        for name in args.ban_vendor_lib_dep:
+            banned_libs.add(name, 'user-banned', BA_WARN)
+        return banned_libs
+
+    def _get_outward_customized_sets_from_args(self, args, graph):
+        vndk_customized_for_system = set()
+        vndk_customized_for_vendor = set()
+        system_libs = graph.lib_pt[PT_SYSTEM].values()
+
+        if args.outward_customization_default_partition in {'system', 'both'}:
+            vndk_customized_for_system.update(system_libs)
+
+        if args.outward_customization_default_partition in {'vendor', 'both'}:
+            vndk_customized_for_vendor.update(system_libs)
+
+        if args.outward_customization_for_system:
+            vndk_customized_for_system.update(
+                    graph.get_libs(
+                        args.outward_customization_for_system, lambda x: None))
+
+        if args.outward_customization_for_vendor:
+            vndk_customized_for_vendor.update(
+                    graph.get_libs(
+                        args.outward_customization_for_vendor, lambda x: None))
+        return (vndk_customized_for_system, vndk_customized_for_vendor)
+
+    def create_from_args(self, args):
+        """Create all essential data structures for VNDK computation."""
+
+        self.check_dirs_from_args(args)
+
+        generic_refs = self._get_generic_refs_from_args(args)
+        banned_libs = self._get_banned_libs_from_args(args)
+
+        graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
+                                 args.vendor, args.vendor_dir_as_system,
+                                 args.load_extra_deps,
+                                 generic_refs=generic_refs)
+
+        vndk_customized_for_system, vndk_customized_for_vendor = \
+                self._get_outward_customized_sets_from_args(args, graph)
+
+        return (generic_refs, banned_libs, graph, vndk_customized_for_system,
+                vndk_customized_for_vendor)
+
+
+class VNDKCommand(VNDKCommandBase):
+    def __init__(self):
+        super(VNDKCommand, self).__init__(
+                'vndk', help='Compute VNDK libraries set')
+
+    def add_argparser_options(self, parser):
+        super(VNDKCommand, self).add_argparser_options(parser)
+
+        parser.add_argument(
+                '--warn-incorrect-partition', action='store_true',
+                help='warn about libraries only have cross partition linkages')
+
+        parser.add_argument(
+                '--warn-high-level-ndk-deps', action='store_true',
+                help='warn about VNDK depends on high-level NDK')
+
+        parser.add_argument(
+                '--warn-banned-vendor-lib-deps', action='store_true',
+                help='warn when a vendor binaries depends on banned lib')
+
+
     def _warn_incorrect_partition_lib_set(self, lib_set, partition, error_msg):
         for lib in lib_set.values():
             if not lib.num_users:
@@ -1755,122 +1901,153 @@
                     print('warning: {}: NDK library should not be extended.'
                             .format(lib.path), file=sys.stderr)
 
-    def _check_arg_dir_exists(self, arg_name, dirs):
-        for path in dirs:
-            if not os.path.exists(path):
-                print('error: Failed to find the directory "{}" specified in {}'
-                        .format(path, arg_name), file=sys.stderr)
-                sys.exit(1)
-            if not os.path.isdir(path):
-                print('error: Path "{}" specified in {} is not a directory'
-                        .format(path, arg_name), file=sys.stderr)
-                sys.exit(1)
-
     def main(self, args):
-        # Check the command line options.
-        self._check_arg_dir_exists('--system', args.system)
-        self._check_arg_dir_exists('--vendor', args.vendor)
-
-        # Load the generic reference.
-        generic_refs = None
-        if args.load_generic_refs:
-            generic_refs = GenericRefs.create_from_dir(args.load_generic_refs)
-
-        # Link ELF objects.
-        graph = ELFLinker.create(args.system, args.system_dir_as_vendor,
-                                 args.vendor, args.vendor_dir_as_system,
-                                 args.load_extra_deps,
-                                 generic_refs=generic_refs)
+        generic_refs, banned_libs, graph, vndk_customized_for_system, \
+                vndk_customized_for_vendor = self.create_from_args(args)
 
         # Check the API extensions to NDK libraries.
         if generic_refs:
             self._check_ndk_extensions(graph, generic_refs)
 
-        # Create banned libraries.
-        if not args.ban_vendor_lib_dep:
-            banned_libs = BannedLibDict.create_default()
-        else:
-            banned_libs = BannedLibDict()
-            for name in args.ban_vendor_lib_dep:
-                banned_libs.add(name, 'user-banned', BA_WARN)
-
         if args.warn_incorrect_partition:
             self._warn_incorrect_partition(graph)
 
         if args.warn_banned_vendor_lib_deps:
             self._warn_banned_vendor_lib_deps(graph, banned_libs)
 
-        # Compute sp-hal and vndk-stable.
-        sp_hal, sp_hal_dep, sp_hal_vndk_stable, sp_ndk, sp_ndk_vndk_stable = \
-                graph.compute_sp_lib(generic_refs)
-
-        vndk_stable = sp_hal_vndk_stable | sp_ndk_vndk_stable
-        sp_hal_closure = sp_hal | sp_hal_dep
-
-        # Normalize partition tags.  We expect many violations from the
-        # pre-Treble world.  Guess a resolution for the incorrect partition
-        # tag.
-        graph.normalize_partition_tags(sp_hal, generic_refs)
-
-        # User may specify the partition for outward-customized vndk libs.  The
-        # following code converts the path into ELFLinkData.
-        vndk_customized_for_system = set()
-        vndk_customized_for_vendor = set()
-
-        system_libs = graph.lib_pt[PT_SYSTEM].values()
-        if args.outward_customization_default_partition in {'system', 'both'}:
-            vndk_customized_for_system.update(system_libs)
-
-        if args.outward_customization_default_partition in {'vendor', 'both'}:
-            vndk_customized_for_vendor.update(system_libs)
-
-        if args.outward_customization_for_system:
-            vndk_customized_for_system.update(
-                    graph.get_libs(
-                        args.outward_customization_for_system, lambda x: None))
-
-        if args.outward_customization_for_vendor:
-            vndk_customized_for_vendor.update(
-                    graph.get_libs(
-                        args.outward_customization_for_vendor, lambda x: None))
-
         # Compute vndk heuristics.
-        vndk = graph.compute_vndk(
-                sp_hal_closure, vndk_stable, vndk_customized_for_system,
-                vndk_customized_for_vendor, generic_refs, banned_libs)
+        vndk_lib = graph.compute_vndk(vndk_customized_for_system,
+                                      vndk_customized_for_vendor, generic_refs,
+                                      banned_libs)
 
         if args.warn_high_level_ndk_deps:
             self._warn_high_level_ndk_deps(
-                    (vndk.vndk_core, vndk.vndk_indirect, vndk.vndk_fwk_ext,
-                     vndk.vndk_vnd_ext))
+                    (vndk_lib.vndk_core, vndk_lib.vndk_indirect,
+                     vndk_lib.vndk_fwk_ext, vndk_lib.vndk_vnd_ext))
 
-        # SP-NDK
-        for lib in sorted_lib_path_list(sp_ndk):
-            print('sp-ndk:', lib)
-        for lib in sorted_lib_path_list(sp_ndk_vndk_stable):
-            print('sp-ndk-vndk-stable:', lib)
+        # Print results.
+        print_vndk_lib(vndk_lib)
+        return 0
 
-        # SP-HAL
-        for lib in sorted_lib_path_list(sp_hal):
-            print('sp-hal:', lib)
-        for lib in sorted_lib_path_list(sp_hal_dep):
-            print('sp-hal-dep:', lib)
-        for lib in sorted_lib_path_list(sp_hal_vndk_stable):
-            print('sp-hal-vndk-stable:', lib)
 
-        # VNDK (framework)
-        for lib in sorted_lib_path_list(vndk.vndk_core):
-            print('vndk-core:', lib)
-        for lib in sorted_lib_path_list(vndk.vndk_indirect):
-            print('vndk-indirect:', lib)
-        for lib in sorted_lib_path_list(vndk.vndk_fwk_ext):
-            print('vndk-fwk-ext:', lib)
+class DepsInsightCommand(VNDKCommandBase):
+    def __init__(self):
+        super(DepsInsightCommand, self).__init__(
+                'deps-insight', help='Generate HTML to show dependencies')
 
-        # VNDK (vendor)
-        for lib in sorted_lib_path_list(vndk.vndk_vnd_ext):
-            print('vndk-vnd-ext:', lib)
-        for lib in sorted_lib_path_list(vndk.extra_vendor_libs):
-            print('extra-vendor-lib:', lib)
+    def add_argparser_options(self, parser):
+        super(DepsInsightCommand, self).add_argparser_options(parser)
+
+        parser.add_argument(
+                '--output', '-o', help='output directory')
+
+    def main(self, args):
+        generic_refs, banned_libs, graph, vndk_customized_for_system, \
+                vndk_customized_for_vendor = self.create_from_args(args)
+
+        # Compute vndk heuristics.
+        vndk_lib = graph.compute_vndk(vndk_customized_for_system,
+                                      vndk_customized_for_vendor, generic_refs,
+                                      banned_libs)
+
+        # Serialize data.
+        strs = []
+        strs_dict = dict()
+
+        libs = list(graph.lib32.values()) + list(graph.lib64.values())
+        libs.sort(key=lambda lib: lib.path)
+        libs_dict = {lib: i for i, lib in enumerate(libs)}
+
+        def get_str_idx(s):
+            try:
+                return strs_dict[s]
+            except KeyError:
+                idx = len(strs)
+                strs_dict[s] = idx
+                strs.append(s)
+                return idx
+
+        def collect_path_sorted_lib_idxs(libs):
+            libs = sorted(libs, key=lambda lib: lib.path)
+            return [libs_dict[lib] for lib in libs]
+
+        def collect_deps(lib):
+            queue = list(lib.deps)
+            visited = set(queue)
+            visited.add(lib)
+            deps = []
+
+            # Traverse dependencies with breadth-first search.
+            while queue:
+                # Collect dependencies for next queue.
+                next_queue = []
+                for lib in queue:
+                    for dep in lib.deps:
+                        if dep not in visited:
+                            next_queue.append(dep)
+                            visited.add(dep)
+
+                # Append current queue to result.
+                deps.append(collect_path_sorted_lib_idxs(queue))
+
+                queue = next_queue
+
+            return deps
+
+        def collect_tags(lib):
+            tags = []
+            if lib.is_ll_ndk:
+                tags.append(get_str_idx('ll-ndk'))
+            if lib.is_sp_ndk:
+                tags.append(get_str_idx('sp-ndk'))
+            if lib.is_hl_ndk:
+                tags.append(get_str_idx('hl-ndk'))
+
+            if lib in vndk_lib.sp_hal:
+                tags.append(get_str_idx('sp-hal'))
+            if lib in vndk_lib.sp_hal_dep:
+                tags.append(get_str_idx('sp-hal-dep'))
+            if lib in vndk_lib.sp_hal_vndk_stable:
+                tags.append(get_str_idx('sp-hal-vndk-stable'))
+
+            if lib in vndk_lib.sp_ndk_vndk_stable:
+                tags.append(get_str_idx('sp-ndk-vndk-stable'))
+            if lib in vndk_lib.sp_both_vndk_stable:
+                tags.append(get_str_idx('sp-both-vndk-stable'))
+
+            if lib in vndk_lib.vndk_core:
+                tags.append(get_str_idx('vndk-core'))
+            if lib in vndk_lib.vndk_indirect:
+                tags.append(get_str_idx('vndk-indirect'))
+            if lib in vndk_lib.vndk_fwk_ext:
+                tags.append(get_str_idx('vndk-fwk-ext'))
+            if lib in vndk_lib.vndk_vnd_ext:
+                tags.append(get_str_idx('vndk-vnd-ext'))
+            if lib in vndk_lib.extra_vendor_lib:
+                tags.append(get_str_idx('extra-vendor-lib'))
+            return tags
+
+        mods = []
+        for lib in libs:
+            mods.append([get_str_idx(lib.path),
+                         32 if lib.elf.is_32bit else 64,
+                         collect_tags(lib),
+                         collect_deps(lib),
+                         collect_path_sorted_lib_idxs(lib.users)])
+
+        # Generate output files.
+        makedirs(args.output, exist_ok=True)
+        script_dir = os.path.dirname(os.path.abspath(__file__))
+        for name in ('index.html', 'insight.css', 'insight.js'):
+            shutil.copyfile(os.path.join(script_dir, 'assets', name),
+                            os.path.join(args.output, name))
+
+        with open(os.path.join(args.output, 'insight-data.js'), 'w') as f:
+            f.write('''(function () {
+    var strs = ''' + json.dumps(strs) + ''';
+    var mods = ''' + json.dumps(mods) + ''';
+    insight.init(document, strs, mods);
+})();''')
 
         return 0
 
@@ -2043,23 +2220,7 @@
                                  args.vendor, args.vendor_dir_as_system,
                                  args.load_extra_deps)
 
-        sp_hal, sp_hal_dep, sp_hal_vndk_stable, sp_ndk, sp_ndk_vndk_stable = \
-                graph.compute_sp_lib(generic_refs)
-
-        # SP-NDK
-        for lib in sorted_lib_path_list(sp_ndk):
-            print('sp-ndk:', lib)
-        for lib in sorted_lib_path_list(sp_ndk_vndk_stable):
-            print('sp-ndk-vndk-stable:', lib)
-
-        # SP-HAL
-        for lib in sorted_lib_path_list(sp_hal):
-            print('sp-hal:', lib)
-        for lib in sorted_lib_path_list(sp_hal_dep):
-            print('sp-hal-dep:', lib)
-        for lib in sorted_lib_path_list(sp_hal_vndk_stable):
-            print('sp-hal-vndk-stable:', lib)
-
+        print_sp_lib(graph.compute_sp_lib(generic_refs))
         return 0
 
 
@@ -2079,6 +2240,7 @@
     register_subcmd(VNDKCapCommand())
     register_subcmd(DepsCommand())
     register_subcmd(DepsClosureCommand())
+    register_subcmd(DepsInsightCommand())
     register_subcmd(SpLibCommand())
     register_subcmd(VNDKStableCommand())