Add manifest_check tool

Add a tool that can check that the <uses-library> tags in an
AndroidManifest.xml file match a list provided by the build.

Bug: 132357300
Test: manifest_check_test
Change-Id: If15abf792282bef677469595e80f19923b87ab62
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 35b5771..31f5922 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -3,6 +3,7 @@
     main: "manifest_fixer.py",
     srcs: [
         "manifest_fixer.py",
+        "manifest.py",
     ],
     version: {
         py2: {
@@ -20,6 +21,43 @@
     srcs: [
         "manifest_fixer_test.py",
         "manifest_fixer.py",
+        "manifest.py",
+    ],
+    version: {
+        py2: {
+            enabled: true,
+        },
+        py3: {
+            enabled: false,
+        },
+    },
+    test_suites: ["general-tests"],
+}
+
+python_binary_host {
+    name: "manifest_check",
+    main: "manifest_check.py",
+    srcs: [
+        "manifest_check.py",
+        "manifest.py",
+    ],
+    version: {
+        py2: {
+            enabled: true,
+        },
+        py3: {
+            enabled: false,
+        },
+    },
+}
+
+python_test_host {
+    name: "manifest_check_test",
+    main: "manifest_check_test.py",
+    srcs: [
+        "manifest_check_test.py",
+        "manifest_check.py",
+        "manifest.py",
     ],
     version: {
         py2: {
diff --git a/scripts/TEST_MAPPING b/scripts/TEST_MAPPING
index cafecde..1b0a229 100644
--- a/scripts/TEST_MAPPING
+++ b/scripts/TEST_MAPPING
@@ -1,8 +1,12 @@
 {
   "presubmit" : [
     {
+      "name": "manifest_check_test",
+      "host": true
+    },
+    {
       "name": "manifest_fixer_test",
       "host": true
-    }
+    }    
   ]
 }
diff --git a/scripts/manifest.py b/scripts/manifest.py
new file mode 100755
index 0000000..4c75f8b
--- /dev/null
+++ b/scripts/manifest.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 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.
+#
+"""A tool for inserting values from the build system into a manifest."""
+
+from __future__ import print_function
+from xml.dom import minidom
+
+
+android_ns = 'http://schemas.android.com/apk/res/android'
+
+
+def get_children_with_tag(parent, tag_name):
+  children = []
+  for child in  parent.childNodes:
+    if child.nodeType == minidom.Node.ELEMENT_NODE and \
+       child.tagName == tag_name:
+      children.append(child)
+  return children
+
+
+def find_child_with_attribute(element, tag_name, namespace_uri,
+                              attr_name, value):
+  for child in get_children_with_tag(element, tag_name):
+    attr = child.getAttributeNodeNS(namespace_uri, attr_name)
+    if attr is not None and attr.value == value:
+      return child
+  return None
+
+
+def parse_manifest(doc):
+  """Get the manifest element."""
+
+  manifest = doc.documentElement
+  if manifest.tagName != 'manifest':
+    raise RuntimeError('expected manifest tag at root')
+  return manifest
+
+
+def ensure_manifest_android_ns(doc):
+  """Make sure the manifest tag defines the android namespace."""
+
+  manifest = parse_manifest(doc)
+
+  ns = manifest.getAttributeNodeNS(minidom.XMLNS_NAMESPACE, 'android')
+  if ns is None:
+    attr = doc.createAttributeNS(minidom.XMLNS_NAMESPACE, 'xmlns:android')
+    attr.value = android_ns
+    manifest.setAttributeNode(attr)
+  elif ns.value != android_ns:
+    raise RuntimeError('manifest tag has incorrect android namespace ' +
+                       ns.value)
+
+
+def as_int(s):
+  try:
+    i = int(s)
+  except ValueError:
+    return s, False
+  return i, True
+
+
+def compare_version_gt(a, b):
+  """Compare two SDK versions.
+
+  Compares a and b, treating codenames like 'Q' as higher
+  than numerical versions like '28'.
+
+  Returns True if a > b
+
+  Args:
+    a: value to compare
+    b: value to compare
+  Returns:
+    True if a is a higher version than b
+  """
+
+  a, a_is_int = as_int(a.upper())
+  b, b_is_int = as_int(b.upper())
+
+  if a_is_int == b_is_int:
+    # Both are codenames or both are versions, compare directly
+    return a > b
+  else:
+    # One is a codename, the other is not.  Return true if
+    # b is an integer version
+    return b_is_int
+
+
+def get_indent(element, default_level):
+  indent = ''
+  if element is not None and element.nodeType == minidom.Node.TEXT_NODE:
+    text = element.nodeValue
+    indent = text[:len(text)-len(text.lstrip())]
+  if not indent or indent == '\n':
+    # 1 indent = 4 space
+    indent = '\n' + (' ' * default_level * 4)
+  return indent
+
+
+def write_xml(f, doc):
+  f.write('<?xml version="1.0" encoding="utf-8"?>\n')
+  for node in doc.childNodes:
+    f.write(node.toxml(encoding='utf-8') + '\n')
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
new file mode 100755
index 0000000..9122da1
--- /dev/null
+++ b/scripts/manifest_check.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 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.
+#
+"""A tool for checking that a manifest agrees with the build system."""
+
+from __future__ import print_function
+
+import argparse
+import sys
+from xml.dom import minidom
+
+
+from manifest import android_ns
+from manifest import get_children_with_tag
+from manifest import parse_manifest
+from manifest import write_xml
+
+
+class ManifestMismatchError(Exception):
+  pass
+
+
+def parse_args():
+  """Parse commandline arguments."""
+
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--uses-library', dest='uses_libraries',
+                      action='append',
+                      help='specify uses-library entries known to the build system')
+  parser.add_argument('--optional-uses-library',
+                      dest='optional_uses_libraries',
+                      action='append',
+                      help='specify uses-library entries known to the build system with required:false')
+  parser.add_argument('--enforce-uses-libraries',
+                      dest='enforce_uses_libraries',
+                      action='store_true',
+                      help='check the uses-library entries known to the build system against the manifest')
+  parser.add_argument('--extract-target-sdk-version',
+                      dest='extract_target_sdk_version',
+                      action='store_true',
+                      help='print the targetSdkVersion from the manifest')
+  parser.add_argument('--output', '-o', dest='output', help='output AndroidManifest.xml file')
+  parser.add_argument('input', help='input AndroidManifest.xml file')
+  return parser.parse_args()
+
+
+def enforce_uses_libraries(doc, uses_libraries, optional_uses_libraries):
+  """Verify that the <uses-library> tags in the manifest match those provided by the build system.
+
+  Args:
+    doc: The XML document.
+    uses_libraries: The names of <uses-library> tags known to the build system
+    optional_uses_libraries: The names of <uses-library> tags with required:fals
+      known to the build system
+  Raises:
+    RuntimeError: Invalid manifest
+    ManifestMismatchError: Manifest does not match
+  """
+
+  manifest = parse_manifest(doc)
+  elems = get_children_with_tag(manifest, 'application')
+  application = elems[0] if len(elems) == 1 else None
+  if len(elems) > 1:
+    raise RuntimeError('found multiple <application> tags')
+  elif not elems:
+    if uses_libraries or optional_uses_libraries:
+      raise ManifestMismatchError('no <application> tag found')
+    return
+
+  verify_uses_library(application, uses_libraries, optional_uses_libraries)
+
+
+def verify_uses_library(application, uses_libraries, optional_uses_libraries):
+  """Verify that the uses-library values known to the build system match the manifest.
+
+  Args:
+    application: the <application> tag in the manifest.
+    uses_libraries: the names of expected <uses-library> tags.
+    optional_uses_libraries: the names of expected <uses-library> tags with required="false".
+  Raises:
+    ManifestMismatchError: Manifest does not match
+  """
+
+  if uses_libraries is None:
+    uses_libraries = []
+
+  if optional_uses_libraries is None:
+    optional_uses_libraries = []
+
+  manifest_uses_libraries, manifest_optional_uses_libraries = parse_uses_library(application)
+
+  err = []
+  if manifest_uses_libraries != uses_libraries:
+    err.append('Expected required <uses-library> tags "%s", got "%s"' %
+               (', '.join(uses_libraries), ', '.join(manifest_uses_libraries)))
+
+  if manifest_optional_uses_libraries != optional_uses_libraries:
+    err.append('Expected optional <uses-library> tags "%s", got "%s"' %
+               (', '.join(optional_uses_libraries), ', '.join(manifest_optional_uses_libraries)))
+
+  if err:
+    raise ManifestMismatchError('\n'.join(err))
+
+
+def parse_uses_library(application):
+  """Extract uses-library tags from the manifest.
+
+  Args:
+    application: the <application> tag in the manifest.
+  """
+
+  libs = get_children_with_tag(application, 'uses-library')
+
+  uses_libraries = [uses_library_name(x) for x in libs if uses_library_required(x)]
+  optional_uses_libraries = [uses_library_name(x) for x in libs if not uses_library_required(x)]
+
+  return first_unique_elements(uses_libraries), first_unique_elements(optional_uses_libraries)
+
+
+def first_unique_elements(l):
+  result = []
+  [result.append(x) for x in l if x not in result]
+  return result
+
+
+def uses_library_name(lib):
+  """Extract the name attribute of a uses-library tag.
+
+  Args:
+    lib: a <uses-library> tag.
+  """
+  name = lib.getAttributeNodeNS(android_ns, 'name')
+  return name.value if name is not None else ""
+
+
+def uses_library_required(lib):
+  """Extract the required attribute of a uses-library tag.
+
+  Args:
+    lib: a <uses-library> tag.
+  """
+  required = lib.getAttributeNodeNS(android_ns, 'required')
+  return (required.value == 'true') if required is not None else True
+
+
+def extract_target_sdk_version(doc):
+  """Returns the targetSdkVersion from the manifest.
+
+  Args:
+    doc: The XML document.
+  Raises:
+    RuntimeError: invalid manifest
+  """
+
+  manifest = parse_manifest(doc)
+
+  # Get or insert the uses-sdk element
+  uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
+  if len(uses_sdk) > 1:
+    raise RuntimeError('found multiple uses-sdk elements')
+  elif len(uses_sdk) == 0:
+    raise RuntimeError('missing uses-sdk element')
+
+  uses_sdk = uses_sdk[0]
+
+  min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion')
+  if min_attr is None:
+    raise RuntimeError('minSdkVersion is not specified')
+
+  target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion')
+  if target_attr is None:
+    target_attr = min_attr
+
+  return target_attr.value
+
+
+def main():
+  """Program entry point."""
+  try:
+    args = parse_args()
+
+    doc = minidom.parse(args.input)
+
+    if args.enforce_uses_libraries:
+      enforce_uses_libraries(doc,
+                             args.uses_libraries,
+                             args.optional_uses_libraries)
+
+    if args.extract_target_sdk_version:
+      print(extract_target_sdk_version(doc))
+
+    if args.output:
+      with open(args.output, 'wb') as f:
+        write_xml(f, doc)
+
+  # pylint: disable=broad-except
+  except Exception as err:
+    print('error: ' + str(err), file=sys.stderr)
+    sys.exit(-1)
+
+if __name__ == '__main__':
+  main()
diff --git a/scripts/manifest_check_test.py b/scripts/manifest_check_test.py
new file mode 100755
index 0000000..7baad5d
--- /dev/null
+++ b/scripts/manifest_check_test.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 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.
+#
+"""Unit tests for manifest_fixer.py."""
+
+import sys
+import unittest
+from xml.dom import minidom
+
+import manifest_check
+
+sys.dont_write_bytecode = True
+
+
+def uses_library(name, attr=''):
+  return '<uses-library android:name="%s"%s />' % (name, attr)
+
+
+def required(value):
+  return ' android:required="%s"' % ('true' if value else 'false')
+
+
+class EnforceUsesLibrariesTest(unittest.TestCase):
+  """Unit tests for add_extract_native_libs function."""
+
+  def run_test(self, input_manifest, uses_libraries=None, optional_uses_libraries=None):
+    doc = minidom.parseString(input_manifest)
+    try:
+      manifest_check.enforce_uses_libraries(doc, uses_libraries, optional_uses_libraries)
+      return True
+    except manifest_check.ManifestMismatchError:
+      return False
+
+  manifest_tmpl = (
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+      '    <application>\n'
+      '    %s\n'
+      '    </application>\n'
+      '</manifest>\n')
+
+  def test_uses_library(self):
+    manifest_input = self.manifest_tmpl % (uses_library('foo'))
+    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    self.assertTrue(matches)
+
+  def test_uses_library_required(self):
+    manifest_input = self.manifest_tmpl % (uses_library('foo', required(True)))
+    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    self.assertTrue(matches)
+
+  def test_optional_uses_library(self):
+    manifest_input = self.manifest_tmpl % (uses_library('foo', required(False)))
+    matches = self.run_test(manifest_input, optional_uses_libraries=['foo'])
+    self.assertTrue(matches)
+
+  def test_expected_uses_library(self):
+    manifest_input = self.manifest_tmpl % (uses_library('foo', required(False)))
+    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    self.assertFalse(matches)
+
+  def test_expected_optional_uses_library(self):
+    manifest_input = self.manifest_tmpl % (uses_library('foo'))
+    matches = self.run_test(manifest_input, optional_uses_libraries=['foo'])
+    self.assertFalse(matches)
+
+  def test_missing_uses_library(self):
+    manifest_input = self.manifest_tmpl % ('')
+    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    self.assertFalse(matches)
+
+  def test_missing_optional_uses_library(self):
+    manifest_input = self.manifest_tmpl % ('')
+    matches = self.run_test(manifest_input, optional_uses_libraries=['foo'])
+    self.assertFalse(matches)
+
+  def test_extra_uses_library(self):
+    manifest_input = self.manifest_tmpl % (uses_library('foo'))
+    matches = self.run_test(manifest_input)
+    self.assertFalse(matches)
+
+  def test_extra_optional_uses_library(self):
+    manifest_input = self.manifest_tmpl % (uses_library('foo', required(False)))
+    matches = self.run_test(manifest_input)
+    self.assertFalse(matches)
+
+  def test_multiple_uses_library(self):
+    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo'),
+                                                      uses_library('bar')]))
+    matches = self.run_test(manifest_input, uses_libraries=['foo', 'bar'])
+    self.assertTrue(matches)
+
+  def test_multiple_optional_uses_library(self):
+    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo', required(False)),
+                                                      uses_library('bar', required(False))]))
+    matches = self.run_test(manifest_input, optional_uses_libraries=['foo', 'bar'])
+    self.assertTrue(matches)
+
+  def test_order_uses_library(self):
+    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo'),
+                                                      uses_library('bar')]))
+    matches = self.run_test(manifest_input, uses_libraries=['bar', 'foo'])
+    self.assertFalse(matches)
+
+  def test_order_optional_uses_library(self):
+    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo', required(False)),
+                                                      uses_library('bar', required(False))]))
+    matches = self.run_test(manifest_input, optional_uses_libraries=['bar', 'foo'])
+    self.assertFalse(matches)
+
+  def test_duplicate_uses_library(self):
+    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo'),
+                                                      uses_library('foo')]))
+    matches = self.run_test(manifest_input, uses_libraries=['foo'])
+    self.assertTrue(matches)
+
+  def test_duplicate_optional_uses_library(self):
+    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo', required(False)),
+                                                      uses_library('foo', required(False))]))
+    matches = self.run_test(manifest_input, optional_uses_libraries=['foo'])
+    self.assertTrue(matches)
+
+  def test_mixed(self):
+    manifest_input = self.manifest_tmpl % ('\n'.join([uses_library('foo'),
+                                                      uses_library('bar', required(False))]))
+    matches = self.run_test(manifest_input, uses_libraries=['foo'],
+                            optional_uses_libraries=['bar'])
+    self.assertTrue(matches)
+
+
+class ExtractTargetSdkVersionTest(unittest.TestCase):
+  def test_target_sdk_version(self):
+    manifest = (
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+      '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29" />\n'
+      '</manifest>\n')
+    doc = minidom.parseString(manifest)
+    target_sdk_version = manifest_check.extract_target_sdk_version(doc)
+    self.assertEqual(target_sdk_version, '29')
+
+  def test_min_sdk_version(self):
+    manifest = (
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+      '    <uses-sdk android:minSdkVersion="28" />\n'
+      '</manifest>\n')
+    doc = minidom.parseString(manifest)
+    target_sdk_version = manifest_check.extract_target_sdk_version(doc)
+    self.assertEqual(target_sdk_version, '28')
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)
diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py
index 83868e6..bb14851 100755
--- a/scripts/manifest_fixer.py
+++ b/scripts/manifest_fixer.py
@@ -17,30 +17,20 @@
 """A tool for inserting values from the build system into a manifest."""
 
 from __future__ import print_function
+
 import argparse
 import sys
 from xml.dom import minidom
 
 
-android_ns = 'http://schemas.android.com/apk/res/android'
-
-
-def get_children_with_tag(parent, tag_name):
-  children = []
-  for child in  parent.childNodes:
-    if child.nodeType == minidom.Node.ELEMENT_NODE and \
-       child.tagName == tag_name:
-      children.append(child)
-  return children
-
-
-def find_child_with_attribute(element, tag_name, namespace_uri,
-                              attr_name, value):
-  for child in get_children_with_tag(element, tag_name):
-    attr = child.getAttributeNodeNS(namespace_uri, attr_name)
-    if attr is not None and attr.value == value:
-      return child
-  return None
+from manifest import android_ns
+from manifest import compare_version_gt
+from manifest import ensure_manifest_android_ns
+from manifest import find_child_with_attribute
+from manifest import get_children_with_tag
+from manifest import get_indent
+from manifest import parse_manifest
+from manifest import write_xml
 
 
 def parse_args():
@@ -74,76 +64,6 @@
   return parser.parse_args()
 
 
-def parse_manifest(doc):
-  """Get the manifest element."""
-
-  manifest = doc.documentElement
-  if manifest.tagName != 'manifest':
-    raise RuntimeError('expected manifest tag at root')
-  return manifest
-
-
-def ensure_manifest_android_ns(doc):
-  """Make sure the manifest tag defines the android namespace."""
-
-  manifest = parse_manifest(doc)
-
-  ns = manifest.getAttributeNodeNS(minidom.XMLNS_NAMESPACE, 'android')
-  if ns is None:
-    attr = doc.createAttributeNS(minidom.XMLNS_NAMESPACE, 'xmlns:android')
-    attr.value = android_ns
-    manifest.setAttributeNode(attr)
-  elif ns.value != android_ns:
-    raise RuntimeError('manifest tag has incorrect android namespace ' +
-                       ns.value)
-
-
-def as_int(s):
-  try:
-    i = int(s)
-  except ValueError:
-    return s, False
-  return i, True
-
-
-def compare_version_gt(a, b):
-  """Compare two SDK versions.
-
-  Compares a and b, treating codenames like 'Q' as higher
-  than numerical versions like '28'.
-
-  Returns True if a > b
-
-  Args:
-    a: value to compare
-    b: value to compare
-  Returns:
-    True if a is a higher version than b
-  """
-
-  a, a_is_int = as_int(a.upper())
-  b, b_is_int = as_int(b.upper())
-
-  if a_is_int == b_is_int:
-    # Both are codenames or both are versions, compare directly
-    return a > b
-  else:
-    # One is a codename, the other is not.  Return true if
-    # b is an integer version
-    return b_is_int
-
-
-def get_indent(element, default_level):
-  indent = ''
-  if element is not None and element.nodeType == minidom.Node.TEXT_NODE:
-    text = element.nodeValue
-    indent = text[:len(text)-len(text.lstrip())]
-  if not indent or indent == '\n':
-    # 1 indent = 4 space
-    indent = '\n' + (' ' * default_level * 4)
-  return indent
-
-
 def raise_min_sdk_version(doc, min_sdk_version, target_sdk_version, library):
   """Ensure the manifest contains a <uses-sdk> tag with a minSdkVersion.
 
@@ -151,6 +71,7 @@
     doc: The XML document.  May be modified by this function.
     min_sdk_version: The requested minSdkVersion attribute.
     target_sdk_version: The requested targetSdkVersion attribute.
+    library: True if the manifest is for a library.
   Raises:
     RuntimeError: invalid manifest
   """
@@ -249,6 +170,7 @@
     indent = get_indent(application.previousSibling, 1)
     application.appendChild(doc.createTextNode(indent))
 
+
 def add_uses_non_sdk_api(doc):
   """Add android:usesNonSdkApi=true attribute to <application>.
 
@@ -323,12 +245,6 @@
                        (attr.value, value))
 
 
-def write_xml(f, doc):
-  f.write('<?xml version="1.0" encoding="utf-8"?>\n')
-  for node in doc.childNodes:
-    f.write(node.toxml(encoding='utf-8') + '\n')
-
-
 def main():
   """Program entry point."""
   try:
diff --git a/scripts/manifest_fixer_test.py b/scripts/manifest_fixer_test.py
index 7fbbc17..2035421 100755
--- a/scripts/manifest_fixer_test.py
+++ b/scripts/manifest_fixer_test.py
@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-"""Unit tests for manifest_fixer_test.py."""
+"""Unit tests for manifest_fixer.py."""
 
 import StringIO
 import sys
@@ -393,10 +393,10 @@
     return output.getvalue()
 
   manifest_tmpl = (
-    '<?xml version="1.0" encoding="utf-8"?>\n'
-    '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
-    '    <application%s/>\n'
-    '</manifest>\n')
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+      '    <application%s/>\n'
+      '</manifest>\n')
 
   def extract_native_libs(self, value):
     return ' android:extractNativeLibs="%s"' % value