Adds tools for building Android apps.
--
MOS_MIGRATED_REVID=94515805
diff --git a/tools/android/merge_manifests_test.py b/tools/android/merge_manifests_test.py
new file mode 100644
index 0000000..349eee0
--- /dev/null
+++ b/tools/android/merge_manifests_test.py
@@ -0,0 +1,509 @@
+# Copyright 2015 Google Inc. All rights reserved.
+#
+# 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.
+
+"""This file contains unit tests for the merge_manifests script."""
+
+import re
+import unittest
+import xml.dom.minidom
+
+from tools.android import merge_manifests
+
+FIRST_MANIFEST = """<?xml version='1.0' encoding='utf-8'?>
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.apps.testapp"
+ android:versionCode="70"
+ android:versionName="1.0">
+ <uses-sdk android:minSdkVersion="10"/>
+ <uses-feature android:name="android.hardware.nfc" android:required="true" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <application
+ android:icon="@drawable/icon"
+ android:name="com.google.android.apps.testapp.TestApplication"
+ android:theme="@style/Theme.Test"
+ android:label="@string/app_name">
+ <!-- START LIBRARIES (Maintain Alphabetic order) -->
+ <!-- NFC extras -->
+ <uses-library android:name="com.google.android.nfc_extras" android:required="false"/>
+ <!-- END LIBRARIES -->
+ <!-- START ACTIVITIES (Maintain Alphabetic order) -->
+ <!-- Entry point activity - navigation and title bar. -->
+ <activity
+ android:name=".entrypoint.EntryPointActivityGroup"
+ android:screenOrientation="portrait"
+ android:launchMode="singleTop"/>
+ <activity android:name=".ui.topup.TopUpActivity" />
+ <service android:name=".nfcevent.NfcEventService" />
+ <receiver
+ android:name="com.receiver.TestReceiver"
+ android:process="@string/receiver_service_name">
+ <!-- Receive the actual message -->
+ <intent-filter>
+ <action
+ android:name="android.intent.action.USER_PRESENT"/>
+ </intent-filter>
+ </receiver>
+ <provider
+ android:name=".dataaccess.persistence.ContentProvider"
+ android:authorities="com.google.android.apps.testapp"
+ android:exported="false" />
+ </application>
+</manifest>
+"""
+
+SECOND_MANIFEST = """<?xml version='1.0' encoding='utf-8'?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.apps.testapp2"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <permission android:name="com.google.android.apps.foo.C2D_MESSAGE"
+ android:protectionLevel="signature" />
+ <uses-sdk android:minSdkVersion="5" />
+ <uses-feature android:name="android.hardware.nfc" android:required="true" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <!-- Comment for permission android.permission.GET_ACCOUNTS.
+ This is just to make sure the comment is being merged correctly.
+ -->
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:name="com.google.android.apps.testapp.TestApplication2"
+ android:theme="@style/Theme.Test2"
+ android:label="@string/app_name"
+ android:backupAgent="FooBar">
+ <activity android:name=".ui.home.HomeActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".TestActivity2"></activity>
+ <activity android:name=".PreviewActivity"></activity>
+ <activity android:name=".ShowTextActivity" android:excludeFromRecents="true"></activity>
+ <activity android:name=".ShowStringListActivity"
+ android:excludeFromRecents="true"
+ android:parentActivityName=".ui.home.HomeActivity">
+ </activity>
+ <service android:name=".TestService">
+ <meta-data android:name="param" android:value="value"/>
+ </service>
+ <service android:name=".nfcevent.NfcEventService" />
+ <receiver android:name=".ConnectivityReceiver" android:enabled="false" >
+ <intent-filter>
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+ </intent-filter>
+ </receiver>
+ <activity-alias android:name="BarFoo" android:targetActivity=".FooBar" />
+ <provider
+ android:name="some.package.with.inner.class$AnInnerClass" />
+ <provider
+ android:name="${packageName}"
+ android:authorities="${packageName}.${packageName}"
+ android:exported="false" />
+ <provider
+ android:name="${packageName}.PlaceHolderProviderName"
+ android:authorities="PlaceHolderProviderAuthorities.${packageName}"
+ android:exported="false" />
+ <activity
+ android:name="activityPrefix.${packageName}.activitySuffix">
+ <intent-filter>
+ <action android:name="actionPrefix.${packageName}.actionSuffix" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+"""
+
+THIRD_MANIFEST = """<?xml version='1.0' encoding='utf-8'?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.apps.testapp3"
+ android:versionCode="3"
+ android:versionName="1.30">
+ <uses-sdk android:minSdkVersion="14" />
+ <uses-feature android:name="android.hardware.nfc" android:required="true" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <application>
+ <activity android:name=".ui.home.HomeActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name="TestActivity"></activity>
+ <service android:name=".TestService" />
+ <receiver android:name=".ConnectivityReceiver" android:enabled="true" >
+ <intent-filter>
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGER" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
+"""
+
+MANUALLY_MERGED = """<?xml version='1.0' encoding='utf-8'?>
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.apps.testapp"
+ android:versionCode="70"
+ android:versionName="1.0">
+ <!-- *** WARNING *** DO NOT EDIT! THIS IS GENERATED MANIFEST BY MERGE_MANIFEST TOOL.
+ Merger manifest:
+ FIRST_MANIFEST
+ Mergee manifests:
+ SECOND_MANIFEST
+ THIRD_MANIFEST
+ -->
+ <uses-sdk android:minSdkVersion="10"/>
+ <uses-feature android:name="android.hardware.nfc" android:required="true" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:name="com.google.android.apps.testapp.TestApplication"
+ android:theme="@style/Theme.Test"
+ android:label="@string/app_name">
+ <!-- START LIBRARIES (Maintain Alphabetic order) -->
+ <!-- NFC extras -->
+ <uses-library android:name="com.google.android.nfc_extras" android:required="false"/>
+ <!-- END LIBRARIES -->
+ <!-- START ACTIVITIES (Maintain Alphabetic order) -->
+ <!-- Entry point activity - navigation and title bar. -->
+ <activity
+ android:name="com.google.android.apps.testapp.entrypoint.EntryPointActivityGroup"
+ android:screenOrientation="portrait"
+ android:launchMode="singleTop"/>
+ <activity android:name="com.google.android.apps.testapp.ui.topup.TopUpActivity" />
+ <service android:name="com.google.android.apps.testapp.nfcevent.NfcEventService" />
+ <receiver
+ android:name="com.receiver.TestReceiver"
+ android:process="@string/receiver_service_name">
+ <!-- Receive the actual message -->
+ <intent-filter>
+ <action
+ android:name="android.intent.action.USER_PRESENT"/>
+ </intent-filter>
+ </receiver>
+ <provider android:authorities="com.google.android.apps.testapp" android:exported="false"
+ android:name="com.google.android.apps.testapp.dataaccess.persistence.ContentProvider"/>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <activity android:label="@string/app_name" android:name="com.google.android.apps.testapp2.ui.home.HomeActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <activity android:name="com.google.android.apps.testapp2.TestActivity2"></activity>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <activity android:name="com.google.android.apps.testapp2.PreviewActivity"></activity>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <activity android:name="com.google.android.apps.testapp2.ShowTextActivity"
+ android:excludeFromRecents="true"></activity>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <activity android:name="com.google.android.apps.testapp2.ShowStringListActivity"
+ android:excludeFromRecents="true"
+ android:parentActivityName="com.google.android.apps.testapp2.ui.home.HomeActivity">
+ </activity>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <activity
+ android:name="activityPrefix.com.google.android.apps.testapp.activitySuffix">
+ <intent-filter>
+ <action android:name="actionPrefix.com.google.android.apps.testapp.actionSuffix" />
+ </intent-filter>
+ </activity>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <provider
+ android:name="some.package.with.inner.class$AnInnerClass" />
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <provider
+ android:name="com.google.android.apps.testapp"
+ android:authorities="com.google.android.apps.testapp.com.google.android.apps.testapp"
+ android:exported="false" />
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <provider
+ android:name="com.google.android.apps.testapp.PlaceHolderProviderName"
+ android:authorities="PlaceHolderProviderAuthorities.com.google.android.apps.testapp"
+ android:exported="false" />
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <receiver android:name="com.google.android.apps.testapp2.ConnectivityReceiver"
+ android:enabled="false" >
+ <intent-filter>
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+ </intent-filter>
+ </receiver>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <service android:name="com.google.android.apps.testapp2.TestService">
+ <meta-data android:name="param" android:value="value"/>
+ </service>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <service android:name="com.google.android.apps.testapp2.nfcevent.NfcEventService"/>
+ <!-- Merged from file: THIRD_MANIFEST -->
+ <activity android:label="@string/app_name" android:name="com.google.android.apps.testapp3.ui.home.HomeActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <!-- Merged from file: THIRD_MANIFEST -->
+ <activity android:name="com.google.android.apps.testapp3.TestActivity"/>
+ <!-- Merged from file: THIRD_MANIFEST -->
+ <receiver android:enabled="true"
+ android:name="com.google.android.apps.testapp3.ConnectivityReceiver">
+ <intent-filter>
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGER"/>
+ </intent-filter>
+ </receiver>
+ <!-- Merged from file: THIRD_MANIFEST -->
+ <service android:name="com.google.android.apps.testapp3.TestService"/>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <activity-alias android:name="com.google.android.apps.testapp2.BarFoo"
+ android:targetActivity="com.google.android.apps.testapp2.FooBar"/>
+ </application>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <permission android:name="com.google.android.apps.foo.C2D_MESSAGE" android:protectionLevel="signature" />
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <uses-permission android:name="android.permission.INTERNET" />
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <!-- Comment for permission android.permission.GET_ACCOUNTS.
+ This is just to make sure the comment is being merged correctly.
+ -->
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+</manifest>
+"""
+
+
+ALIAS_MANIFEST = """<?xml version='1.0' encoding='utf-8'?>
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.apps.testapp"
+ android:versionCode="70"
+ android:versionName="1.0">
+ <uses-sdk android:minSdkVersion="10"/>
+ <uses-feature android:name="android.hardware.nfc" android:required="true" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <application
+ android:icon="@drawable/icon"
+ android:name="com.google.android.apps.testapp.TestApplication"
+ android:theme="@style/Theme.Test"
+ android:label="@string/app_name">
+ <activity-alias android:name="com.google.foo.should.not.be.first"
+ android:targetActivity=".entrypoint.EntryPointActivityGroup"/>
+ <!-- START LIBRARIES (Maintain Alphabetic order) -->
+ <!-- NFC extras -->
+ <uses-library android:name="com.google.android.nfc_extras" android:required="false"/>
+ <!-- END LIBRARIES -->
+ <!-- START ACTIVITIES (Maintain Alphabetic order) -->
+ <!-- Entry point activity - navigation and title bar. -->
+ <activity
+ android:name=".entrypoint.EntryPointActivityGroup"
+ android:screenOrientation="portrait"
+ android:launchMode="singleTop"/>
+ <activity android:name=".ui.topup.TopUpActivity" />
+ <service android:name=".nfcevent.NfcEventService" />
+ <receiver
+ android:name="com.receiver.TestReceiver"
+ android:process="@string/receiver_service_name">
+ <!-- Receive the actual message -->
+ <intent-filter>
+ <action
+ android:name="android.intent.action.USER_PRESENT"/>
+ </intent-filter>
+ </receiver>
+ <provider
+ android:name=".dataaccess.persistence.ContentProvider"
+ android:authorities="com.google.android.apps.testapp"
+ android:exported="false" />
+ </application>
+</manifest>
+"""
+
+
+# This case exists when a library manifest relies on
+# dependent manifests to provide required elements, i.e. a <application>
+INVALID_MERGER_MANIFEST = """
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.invalid"
+ android:versionCode="9100000"
+ android:versionName="9.1.0.0x">
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
+</manifest>
+"""
+
+
+VALID_MANIFEST = """
+<manifest
+ android:versionCode="9100000"
+ android:versionName="9.1.0.0x"
+ package="com.google.android.invalid"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- *** WARNING *** DO NOT EDIT! THIS IS GENERATED MANIFEST BY MERGE_MANIFEST TOOL.
+ Merger manifest:
+ INVALID_MANIFEST
+ Mergee manifests:
+ SECOND_MANIFEST
+ -->
+ <application
+ android:backupAgent="com.google.android.apps.testapp2.FooBar"
+ android:icon="@drawable/icon"
+ android:label="@string/app_name"
+ android:name="com.google.android.apps.testapp.TestApplication2"
+ android:theme="@style/Theme.Test2">
+ <activity
+ android:name="com.google.android.apps.testapp2.TestActivity2"/>
+ <activity
+ android:name="com.google.android.apps.testapp2.PreviewActivity"/>
+ <activity android:excludeFromRecents="true"
+ android:name="com.google.android.apps.testapp2.ShowTextActivity"/>
+ <activity android:excludeFromRecents="true"
+ android:name="com.google.android.apps.testapp2.ShowStringListActivity"
+ android:parentActivityName="com.google.android.apps.testapp2.ui.home.HomeActivity">
+ </activity>
+ <service
+ android:name="com.google.android.apps.testapp2.TestService">
+ <meta-data android:name="param"
+ android:value="value"/>
+ </service>
+ <service
+ android:name="com.google.android.apps.testapp2.nfcevent.NfcEventService"/>
+ <receiver
+ android:enabled="false"
+ android:name="com.google.android.apps.testapp2.ConnectivityReceiver">
+ <intent-filter>
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
+ </intent-filter>
+ </receiver>
+ <provider android:name="some.package.with.inner.class$AnInnerClass"/>
+ <provider
+ android:authorities="com.google.android.invalid.com.google.android.invalid"
+ android:exported="false" android:name="com.google.android.invalid"/>
+ <provider android:authorities="PlaceHolderProviderAuthorities.com.google.android.invalid"
+ android:exported="false"
+ android:name="com.google.android.invalid.PlaceHolderProviderName"/>
+ <activity android:name="activityPrefix.com.google.android.invalid.activitySuffix">
+ <intent-filter>
+ <action android:name="actionPrefix.com.google.android.invalid.actionSuffix"/>
+ </intent-filter>
+ </activity>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <activity android:label="@string/app_name"
+ android:name="com.google.android.apps.testapp2.ui.home.HomeActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity-alias
+ android:name="com.google.android.apps.testapp2.BarFoo"
+ android:targetActivity="com.google.android.apps.testapp2.FooBar"/>
+ </application>
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21"/>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <permission android:name="com.google.android.apps.foo.C2D_MESSAGE" android:protectionLevel="signature"/>
+ <!-- Merged from file: SECOND_MANIFEST -->
+ <uses-feature android:name="android.hardware.nfc" android:required="true"/>
+</manifest>
+"""
+
+
+def Reformat(string):
+ """Reformat for comparison."""
+ string = re.compile(r'^[ \t]*\n?', re.MULTILINE).sub('', string)
+ return string
+
+
+class MergeManifestsTest(unittest.TestCase):
+ """Unit tests for the MergeManifest class."""
+
+ def testMerge(self):
+ self.maxDiff = None
+ merger = merge_manifests.MergeManifests(
+ (FIRST_MANIFEST, 'FIRST_MANIFEST'),
+ [(SECOND_MANIFEST, 'SECOND_MANIFEST'),
+ (THIRD_MANIFEST, 'THIRD_MANIFEST')],
+ ['android.permission.READ_LOGS'])
+ result = merger.Merge()
+ expected = xml.dom.minidom.parseString(MANUALLY_MERGED).toprettyxml()
+ self.assertEquals(Reformat(expected), Reformat(result))
+
+ def testReformat(self):
+ text = ' a\n b\n\n\n \t c'
+ expected = 'a\nb\nc'
+ self.assertEquals(expected, Reformat(text))
+
+ def testValidateAndWarnPermissions(self):
+ permissions = ['android.permission.VIBRATE', 'android.permission.LAUGH']
+ warnings = merge_manifests._ValidateAndWarnPermissions(permissions)
+ self.assertTrue('android.permission.VIBRATE' not in warnings)
+ self.assertTrue('android.permission.LAUGH' in warnings)
+
+ def testExcludeAllPermissions(self):
+ merger = merge_manifests.MergeManifests(
+ (FIRST_MANIFEST, 'FIRST_MANIFEST'),
+ [(SECOND_MANIFEST, 'SECOND_MANIFEST'),
+ (THIRD_MANIFEST, 'THIRD_MANIFEST')],
+ ['all'])
+ result = merger.Merge()
+ self.assertFalse('android.permission.READ_LOGS' in result)
+ self.assertFalse('android.permission.INTERNET' in result)
+ self.assertTrue('android.permission.ACCESS_COARSE_LOCATION' in result)
+
+ def testUndefinedArgumentPlaceholder(self):
+ bad_manifest = SECOND_MANIFEST.replace(
+ '${packageName}', '${unknownPlaceHolder}')
+ merger = merge_manifests.MergeManifests(
+ (FIRST_MANIFEST, 'FIRST_MANIFEST'),
+ [(bad_manifest, 'invalidManifest'),
+ (THIRD_MANIFEST, 'THIRD_MANIFEST')])
+ try:
+ merger.Merge()
+ self.fail('merging manifests with unknown placeholders didn\'t fail')
+ except merge_manifests.UndefinedPlaceholderException:
+ pass
+
+ def testActivityAliasesAreAlwaysLast(self):
+ merger = merge_manifests.MergeManifests(
+ (FIRST_MANIFEST, 'FIRST_MANIFEST'),
+ [(SECOND_MANIFEST, 'SECOND_MANIFEST'),
+ (ALIAS_MANIFEST, 'THIRD_MANIFEST')],
+ ['all'])
+ result = merger.Merge()
+ last_occurence_of_activity = result.rfind('<activity ')
+ first_occurence_of_alias = result.find('<activity-alias ')
+ self.assertLess(last_occurence_of_activity, first_occurence_of_alias,
+ msg='First activity-alias is not after the last activity!')
+
+ def testMergeToCreateValidManifest(self):
+ self.maxDiff = None
+ merger = merge_manifests.MergeManifests(
+ (INVALID_MERGER_MANIFEST, 'INVALID_MANIFEST'),
+ [(SECOND_MANIFEST, 'SECOND_MANIFEST')],
+ ['all'])
+ result = merger.Merge()
+ expected = xml.dom.minidom.parseString(VALID_MANIFEST).toprettyxml()
+ self.assertEquals(Reformat(expected), Reformat(result))
+
+
+if __name__ == '__main__':
+ unittest.main()
+