Parse <property> tags

parse <property> tags defined on various components [application,
activity, activity alias, service, provider, and receiver]

Bug: 169258655
Test: atest FrameworksServicesTests:PackageParserTest
Change-Id: I4c94056db3909cc28636a9f93d875eff3d593f79
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 1f72374..f2bb91c 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -103,6 +103,7 @@
         ":PackageParserTestApp1",
         ":PackageParserTestApp2",
         ":PackageParserTestApp3",
+        ":PackageParserTestApp4",
         ":apex.test",
     ],
     resource_zips: [":FrameworksServicesTests_apks_as_resources"],
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 7694d09..90c2982 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -21,7 +21,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import static java.lang.Boolean.TRUE;
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
 import android.annotation.NonNull;
@@ -32,6 +34,7 @@
 import android.content.pm.FeatureGroupInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.Property;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.content.pm.ServiceInfo;
@@ -81,7 +84,9 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 @Presubmit
@@ -99,6 +104,8 @@
     private static final String TEST_APP1_APK = "PackageParserTestApp1.apk";
     private static final String TEST_APP2_APK = "PackageParserTestApp2.apk";
     private static final String TEST_APP3_APK = "PackageParserTestApp3.apk";
+    private static final String TEST_APP4_APK = "PackageParserTestApp4.apk";
+    private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
 
     @Before
     public void setUp() throws IOException {
@@ -270,6 +277,234 @@
         }
     }
 
+    private static final int PROPERTY_TYPE_BOOLEAN = 1;
+    private static final int PROPERTY_TYPE_FLOAT = 2;
+    private static final int PROPERTY_TYPE_INTEGER = 3;
+    private static final int PROPERTY_TYPE_RESOURCE = 4;
+    private static final int PROPERTY_TYPE_STRING = 5;
+    public void assertProperty(Map<String, Property> properties, String propertyName,
+            int propertyType, Object propertyValue) {
+        assertTrue(properties.containsKey(propertyName));
+
+        final Property testProperty = properties.get(propertyName);
+        assertEquals(propertyType, testProperty.getType());
+
+        if (propertyType == PROPERTY_TYPE_BOOLEAN) {
+            assertTrue(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Boolean boolValue = (Boolean) propertyValue;
+            if (boolValue.booleanValue()) {
+                assertTrue(testProperty.getBoolean());
+            } else {
+                assertFalse(testProperty.getBoolean());
+            }
+            // assert the other values have an appropriate default
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_FLOAT) {
+            assertFalse(testProperty.isBoolean());
+            assertTrue(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Float floatValue = (Float) propertyValue;
+            assertEquals(floatValue.floatValue(), testProperty.getFloat(), 0.0f);
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_INTEGER) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertTrue(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Integer integerValue = (Integer) propertyValue;
+            assertEquals(integerValue.intValue(), testProperty.getInteger());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_RESOURCE) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertTrue(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Integer resourceValue = (Integer) propertyValue;
+            assertEquals(resourceValue.intValue(), testProperty.getResourceId());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_STRING) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertTrue(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final String stringValue = (String) propertyValue;
+            assertEquals(stringValue, testProperty.getString());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+        } else {
+            fail("Unknown property type");
+        }
+    }
+
+    @Test
+    public void testParseApplicationProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final Map<String, Property> properties = pkg.getProperties();
+            assertEquals(10, properties.size());
+            assertProperty(properties,
+                    "android.cts.PROPERTY_RESOURCE_XML", PROPERTY_TYPE_RESOURCE, 0x7f060000);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_RESOURCE_INTEGER", PROPERTY_TYPE_RESOURCE, 0x7f040000);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_BOOLEAN", PROPERTY_TYPE_BOOLEAN, TRUE);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_BOOLEAN_VIA_RESOURCE", PROPERTY_TYPE_BOOLEAN, TRUE);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_FLOAT", PROPERTY_TYPE_FLOAT, 3.14f);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_FLOAT_VIA_RESOURCE", PROPERTY_TYPE_FLOAT, 2.718f);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_INTEGER", PROPERTY_TYPE_INTEGER, 42);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_INTEGER_VIA_RESOURCE", PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(properties,
+                    "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala");
+            assertProperty(properties,
+                    "android.cts.PROPERTY_STRING_VIA_RESOURCE", PROPERTY_TYPE_STRING, "giraffe");
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseActivityProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedActivity> activities = pkg.getActivities();
+            for (ParsedActivity activity : activities) {
+                final Map<String, Property> properties = activity.getProperties();
+                if ((PACKAGE_NAME + ".MyActivityAlias").equals(activity.getName())) {
+                    assertEquals(2, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_ACTIVITY_ALIAS", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_INTEGER, 123);
+                } else if ((PACKAGE_NAME + ".MyActivity").equals(activity.getName())) {
+                    assertEquals(3, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_ACTIVITY", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala activity");
+                } else if ("android.app.AppDetailsActivity".equals(activity.getName())) {
+                    // ignore default added activity
+                } else {
+                    fail("Found unknown activity; name = " + activity.getName());
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseProviderProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedProvider> providers = pkg.getProviders();
+            for (ParsedProvider provider : providers) {
+                final Map<String, Property> properties = provider.getProperties();
+                if ((PACKAGE_NAME + ".MyProvider").equals(provider.getName())) {
+                    assertEquals(1, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_PROVIDER", PROPERTY_TYPE_INTEGER, 123);
+                } else {
+                    fail("Found unknown provider; name = " + provider.getName());
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseReceiverProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedActivity> receivers = pkg.getReceivers();
+            for (ParsedActivity receiver : receivers) {
+                final Map<String, Property> properties = receiver.getProperties();
+                if ((PACKAGE_NAME + ".MyReceiver").equals(receiver.getName())) {
+                    assertEquals(2, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_RECEIVER", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_STRING", PROPERTY_TYPE_STRING, "koala receiver");
+                } else {
+                    fail("Found unknown receiver; name = " + receiver.getName());
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
+    @Test
+    public void testParseServiceProperties() throws Exception {
+        final File testFile = extractFile(TEST_APP4_APK);
+        try {
+            final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            final List<ParsedService> services = pkg.getServices();
+            for (ParsedService service : services) {
+                final Map<String, Property> properties = service.getProperties();
+                if ((PACKAGE_NAME + ".MyService").equals(service.getName())) {
+                    assertEquals(2, properties.size());
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_SERVICE", PROPERTY_TYPE_INTEGER, 123);
+                    assertProperty(properties,
+                            "android.cts.PROPERTY_COMPONENT", PROPERTY_TYPE_RESOURCE, 0x7f040000);
+                } else {
+                    fail("Found unknown service; name = " + service.getName());
+                }
+            }
+        } finally {
+            testFile.delete();
+        }
+    }
+
     /**
      * A trivial subclass of package parser that only caches the package name, and throws away
      * all other information.
@@ -386,6 +621,13 @@
                     b.getInstrumentations().get(i));
         }
 
+        assertEquals(a.getProperties().size(), b.getProperties().size());
+        final Iterator<String> iter = a.getProperties().keySet().iterator();
+        while (iter.hasNext()) {
+            final String key = iter.next();
+            assertEquals(a.getProperties().get(key), b.getProperties().get(key));
+        }
+
         assertEquals(a.getRequestedPermissions(), b.getRequestedPermissions());
         assertEquals(a.getProtectedBroadcasts(), b.getProtectedBroadcasts());
         assertEquals(a.getLibraryNames(), b.getLibraryNames());
@@ -443,6 +685,13 @@
             assertEquals(aIntent.getNonLocalizedLabel(), bIntent.getNonLocalizedLabel());
             assertEquals(aIntent.getIcon(), bIntent.getIcon());
         }
+
+        assertEquals(a.getProperties().size(), b.getProperties().size());
+        final Iterator<String> iter = a.getProperties().keySet().iterator();
+        while (iter.hasNext()) {
+            final String key = iter.next();
+            assertEquals(a.getProperties().get(key), b.getProperties().get(key));
+        }
     }
 
     private static void assertPermissionsEqual(ParsedPermission a, ParsedPermission b) {
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
index c409438..f69dfe9 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
+++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
@@ -51,3 +51,17 @@
     resource_dirs: ["res"],
     manifest: "AndroidManifestApp3.xml",
 }
+
+android_test_helper_app {
+    name: "PackageParserTestApp4",
+    sdk_version: "current",
+    srcs: ["**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    resource_dirs: ["res"],
+    manifest: "AndroidManifestApp4.xml",
+}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
new file mode 100644
index 0000000..299b9a0
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.packageparserapp" >
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <property android:name="android.cts.PROPERTY_RESOURCE_XML" android:resource="@xml/xml_property" />
+        <property android:name="android.cts.PROPERTY_RESOURCE_INTEGER" android:resource="@integer/integer_property" />
+        <property android:name="android.cts.PROPERTY_BOOLEAN" android:value="true" />
+        <property android:name="android.cts.PROPERTY_BOOLEAN_VIA_RESOURCE" android:value="@bool/boolean_property" />
+        <property android:name="android.cts.PROPERTY_FLOAT" android:value="3.14" />
+        <property android:name="android.cts.PROPERTY_FLOAT_VIA_RESOURCE" android:value="@dimen/float_property" />
+        <property android:name="android.cts.PROPERTY_INTEGER" android:value="42" />
+        <property android:name="android.cts.PROPERTY_INTEGER_VIA_RESOURCE" android:value="@integer/integer_property" />
+        <property android:name="android.cts.PROPERTY_STRING" android:value="koala" />
+        <property android:name="android.cts.PROPERTY_STRING_VIA_RESOURCE" android:value="@string/string_property" />
+
+	    <activity android:name="com.android.servicestests.apps.packageparserapp.MyActivity"
+	              android:exported="true" >
+	        <property android:name="android.cts.PROPERTY_ACTIVITY" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_COMPONENT" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_STRING" android:value="koala activity" />
+	        <intent-filter>
+	           <action android:name="android.intent.action.MAIN" />
+	           <category android:name="android.intent.category.LAUNCHER" />
+	        </intent-filter>
+	    </activity>
+	    <activity-alias android:name="com.android.servicestests.apps.packageparserapp.MyActivityAlias"
+	                    android:targetActivity="com.android.servicestests.apps.packageparserapp.MyActivity">
+	        <property android:name="android.cts.PROPERTY_ACTIVITY_ALIAS" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_COMPONENT" android:value="@integer/integer_property" />
+	    </activity-alias>
+	    <provider android:name="com.android.servicestests.apps.packageparserapp.MyProvider"
+	             android:authorities="propertytest">
+	        <property android:name="android.cts.PROPERTY_PROVIDER" android:value="@integer/integer_property" />
+	    </provider>
+	    <receiver android:name="com.android.servicestests.apps.packageparserapp.MyReceiver">
+	        <property android:name="android.cts.PROPERTY_RECEIVER" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_STRING" android:value="koala receiver" />
+	    </receiver>
+	    <service android:name="com.android.servicestests.apps.packageparserapp.MyService">
+	        <property android:name="android.cts.PROPERTY_SERVICE" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_COMPONENT" android:resource="@integer/integer_property" />
+	    </service>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.servicestests.apps.packageparserapp" />
+</manifest>
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/res/values/values.xml b/services/tests/servicestests/test-apps/PackageParserApp/res/values/values.xml
index 6a4cc65..67ecf66 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/res/values/values.xml
+++ b/services/tests/servicestests/test-apps/PackageParserApp/res/values/values.xml
@@ -16,4 +16,10 @@
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <bool name="config_isIsolated">true</bool>
-</resources>
\ No newline at end of file
+    <bool name="boolean_property">true</bool>
+    <color name="color_property">#00FF00</color>
+    <item name="float_property" format="float" type="dimen">2.718</item>
+    <dimen name="dimen_property">23dp</dimen>
+    <integer name="integer_property">123</integer>
+    <string name="string_property">giraffe</string>
+</resources>
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/res/xml/xml_property.xml b/services/tests/servicestests/test-apps/PackageParserApp/res/xml/xml_property.xml
new file mode 100644
index 0000000..588db8d
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParserApp/res/xml/xml_property.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<paths>
+    <external-path path="Android/data/" name="files_root" />
+    <external-path path="." name="external_storage_root" />
+</paths>
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/src/com/android/servicestests/apps/packageparserapp/MyProvider.java b/services/tests/servicestests/test-apps/PackageParserApp/src/com/android/servicestests/apps/packageparserapp/MyProvider.java
new file mode 100644
index 0000000..6627166
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParserApp/src/com/android/servicestests/apps/packageparserapp/MyProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.servicestests.apps.packageparserapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class MyProvider extends ContentProvider {
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "text/plain";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+}