am 345efd9a: am 58157586: Merge "Add FeatureGroup to PackageInfo" into lmp-dev

* commit '345efd9a6988601e67d3c0202e433144670b5e62':
  Add FeatureGroup to PackageInfo
diff --git a/api/current.txt b/api/current.txt
index 5a874fe..5778c7e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8526,6 +8526,15 @@
     field public int reqTouchScreen;
   }
 
+  public final class FeatureGroupInfo implements android.os.Parcelable {
+    ctor public FeatureGroupInfo();
+    ctor public FeatureGroupInfo(android.content.pm.FeatureGroupInfo);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator CREATOR;
+    field public android.content.pm.FeatureInfo[] features;
+  }
+
   public class FeatureInfo implements android.os.Parcelable {
     ctor public FeatureInfo();
     ctor public FeatureInfo(android.content.pm.FeatureInfo);
@@ -8617,6 +8626,7 @@
     field public android.content.pm.ActivityInfo[] activities;
     field public android.content.pm.ApplicationInfo applicationInfo;
     field public android.content.pm.ConfigurationInfo[] configPreferences;
+    field public android.content.pm.FeatureGroupInfo[] featureGroups;
     field public long firstInstallTime;
     field public int[] gids;
     field public int installLocation;
diff --git a/core/java/android/content/pm/FeatureGroupInfo.java b/core/java/android/content/pm/FeatureGroupInfo.java
new file mode 100644
index 0000000..79a6eea
--- /dev/null
+++ b/core/java/android/content/pm/FeatureGroupInfo.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2014 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 android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A set of features that can be requested by an application. This corresponds
+ * to information collected from the
+ * AndroidManifest.xml's {@code <feature-group>} tag.
+ */
+public final class FeatureGroupInfo implements Parcelable {
+
+    /**
+     * The list of features that are required by this group.
+     *
+     * @see FeatureInfo#FLAG_REQUIRED
+     */
+    public FeatureInfo[] features;
+
+    public FeatureGroupInfo() {
+    }
+
+    public FeatureGroupInfo(FeatureGroupInfo other) {
+        features = other.features;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedArray(features, flags);
+    }
+
+    public static final Creator<FeatureGroupInfo> CREATOR = new Creator<FeatureGroupInfo>() {
+        @Override
+        public FeatureGroupInfo createFromParcel(Parcel source) {
+            FeatureGroupInfo group = new FeatureGroupInfo();
+            group.features = source.createTypedArray(FeatureInfo.CREATOR);
+            return group;
+        }
+
+        @Override
+        public FeatureGroupInfo[] newArray(int size) {
+            return new FeatureGroupInfo[size];
+        }
+    };
+}
diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java
index d919fc3..79fa327 100644
--- a/core/java/android/content/pm/FeatureInfo.java
+++ b/core/java/android/content/pm/FeatureInfo.java
@@ -22,7 +22,7 @@
 /**
  * A single feature that can be requested by an application. This corresponds
  * to information collected from the
- * AndroidManifest.xml's &lt;uses-feature&gt; tag.
+ * AndroidManifest.xml's {@code <uses-feature>} tag.
  */
 public class FeatureInfo implements Parcelable {
     /**
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 49ffef2..a0e3c4a 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -180,7 +180,7 @@
      * {@link android.R.styleable#AndroidManifestUsesConfiguration
      * &lt;uses-configuration&gt;} tags included under &lt;manifest&gt;,
      * or null if there were none. This is only filled in if the flag
-     * {@link PackageManager#GET_CONFIGURATIONS} was set.  
+     * {@link PackageManager#GET_CONFIGURATIONS} was set.
      */
     public ConfigurationInfo[] configPreferences;
 
@@ -192,6 +192,16 @@
     public FeatureInfo[] reqFeatures;
 
     /**
+     * Groups of features that this application has requested.
+     * Each group contains a set of features that are required.
+     * A device must match the features listed in {@link #reqFeatures} and one
+     * or more FeatureGroups in order to have satisfied the feature requirement.
+     *
+     * @see FeatureInfo#FLAG_REQUIRED
+     */
+    public FeatureGroupInfo[] featureGroups;
+
+    /**
      * Constant corresponding to <code>auto</code> in
      * the {@link android.R.attr#installLocation} attribute.
      * @hide
@@ -300,6 +310,7 @@
         dest.writeTypedArray(signatures, parcelableFlags);
         dest.writeTypedArray(configPreferences, parcelableFlags);
         dest.writeTypedArray(reqFeatures, parcelableFlags);
+        dest.writeTypedArray(featureGroups, parcelableFlags);
         dest.writeInt(installLocation);
         dest.writeInt(requiredForAllUsers ? 1 : 0);
         dest.writeInt(requiredForProfile);
@@ -344,6 +355,7 @@
         signatures = source.createTypedArray(Signature.CREATOR);
         configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
         reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
+        featureGroups = source.createTypedArray(FeatureGroupInfo.CREATOR);
         installLocation = source.readInt();
         requiredForAllUsers = source.readInt() != 0;
         requiredForProfile = source.readInt();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 5a54767..56b7164 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -177,9 +177,9 @@
     /**
      * {@link PackageInfo} flag: return information about
      * hardware preferences in
-     * {@link PackageInfo#configPreferences PackageInfo.configPreferences} and
-     * requested features in {@link PackageInfo#reqFeatures
-     * PackageInfo.reqFeatures}.
+     * {@link PackageInfo#configPreferences PackageInfo.configPreferences},
+     * and requested features in {@link PackageInfo#reqFeatures} and
+     * {@link PackageInfo#featureGroups}.
      */
     public static final int GET_CONFIGURATIONS = 0x00004000;
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 44bf35d..cddefb5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -434,6 +434,11 @@
                 pi.reqFeatures = new FeatureInfo[N];
                 p.reqFeatures.toArray(pi.reqFeatures);
             }
+            N = p.featureGroups != null ? p.featureGroups.size() : 0;
+            if (N > 0) {
+                pi.featureGroups = new FeatureGroupInfo[N];
+                p.featureGroups.toArray(pi.featureGroups);
+            }
         }
         if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
             int N = p.activities.size();
@@ -1502,24 +1507,7 @@
                 XmlUtils.skipCurrentTag(parser);
 
             } else if (tagName.equals("uses-feature")) {
-                FeatureInfo fi = new FeatureInfo();
-                sa = res.obtainAttributes(attrs,
-                        com.android.internal.R.styleable.AndroidManifestUsesFeature);
-                // Note: don't allow this value to be a reference to a resource
-                // that may change.
-                fi.name = sa.getNonResourceString(
-                        com.android.internal.R.styleable.AndroidManifestUsesFeature_name);
-                if (fi.name == null) {
-                    fi.reqGlEsVersion = sa.getInt(
-                            com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion,
-                            FeatureInfo.GL_ES_VERSION_UNDEFINED);
-                }
-                if (sa.getBoolean(
-                        com.android.internal.R.styleable.AndroidManifestUsesFeature_required,
-                        true)) {
-                    fi.flags |= FeatureInfo.FLAG_REQUIRED;
-                }
-                sa.recycle();
+                FeatureInfo fi = parseUsesFeature(res, attrs);
                 pkg.reqFeatures = ArrayUtils.add(pkg.reqFeatures, fi);
 
                 if (fi.name == null) {
@@ -1531,9 +1519,35 @@
                 XmlUtils.skipCurrentTag(parser);
 
             } else if (tagName.equals("feature-group")) {
-                // Skip this for now until we know what to do with it.
+                FeatureGroupInfo group = new FeatureGroupInfo();
+                ArrayList<FeatureInfo> features = null;
+                final int innerDepth = parser.getDepth();
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                        continue;
+                    }
 
-                XmlUtils.skipCurrentTag(parser);
+                    final String innerTagName = parser.getName();
+                    if (innerTagName.equals("uses-feature")) {
+                        FeatureInfo featureInfo = parseUsesFeature(res, attrs);
+                        // FeatureGroups are stricter and mandate that
+                        // any <uses-feature> declared are mandatory.
+                        featureInfo.flags |= FeatureInfo.FLAG_REQUIRED;
+                        features = ArrayUtils.add(features, featureInfo);
+                    } else {
+                        Slog.w(TAG, "Unknown element under <feature-group>: " + innerTagName +
+                                " at " + mArchiveSourcePath + " " +
+                                parser.getPositionDescription());
+                    }
+                    XmlUtils.skipCurrentTag(parser);
+                }
+
+                if (features != null) {
+                    group.features = new FeatureInfo[features.size()];
+                    group.features = features.toArray(group.features);
+                }
+                pkg.featureGroups = ArrayUtils.add(pkg.featureGroups, group);
 
             } else if (tagName.equals("uses-sdk")) {
                 if (SDK_VERSION > 0) {
@@ -1851,6 +1865,28 @@
         return pkg;
     }
 
+    private FeatureInfo parseUsesFeature(Resources res, AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+        FeatureInfo fi = new FeatureInfo();
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AndroidManifestUsesFeature);
+        // Note: don't allow this value to be a reference to a resource
+        // that may change.
+        fi.name = sa.getNonResourceString(
+                com.android.internal.R.styleable.AndroidManifestUsesFeature_name);
+        if (fi.name == null) {
+            fi.reqGlEsVersion = sa.getInt(
+                        com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion,
+                        FeatureInfo.GL_ES_VERSION_UNDEFINED);
+        }
+        if (sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestUsesFeature_required, true)) {
+            fi.flags |= FeatureInfo.FLAG_REQUIRED;
+        }
+        sa.recycle();
+        return fi;
+    }
+
     private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser,
                                         AttributeSet attrs, String[] outError)
             throws XmlPullParserException, IOException {
@@ -4225,6 +4261,9 @@
         // Applications requested features
         public ArrayList<FeatureInfo> reqFeatures = null;
 
+        // Applications requested feature groups
+        public ArrayList<FeatureGroupInfo> featureGroups = null;
+
         public int installLocation;
 
         /* An app that's required for all users and cannot be uninstalled for a user */
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c268d97..7d4c37e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1250,7 +1250,9 @@
          application.
 
          <p>This appears as a child tag of the root
-         {@link #AndroidManifest manifest} tag. -->
+         {@link #AndroidManifest manifest} tag.
+
+         @deprecated Use <code>feature-group</code> instead.-->
     <declare-styleable name="AndroidManifestUsesConfiguration" parent="AndroidManifest">
         <!-- The type of touch screen used by an application. -->
         <attr name="reqTouchScreen" />
diff --git a/tests/UsesFeature2Test/AndroidManifest.xml b/tests/UsesFeature2Test/AndroidManifest.xml
index 6b6c4da..8caf4a1 100644
--- a/tests/UsesFeature2Test/AndroidManifest.xml
+++ b/tests/UsesFeature2Test/AndroidManifest.xml
@@ -33,12 +33,5 @@
         <uses-feature android:name="android.hardware.opengles.aep" />
     </feature-group>
 
-    <application android:label="@string/app_title">
-        <activity android:name="ActivityMain">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
+    <application android:label="@string/app_title" android:hasCode="false" />
 </manifest>
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index c74a373..fe0a601 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1673,12 +1673,8 @@
 
                         String8 name = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
                         if (name != "" && error == "") {
-                            int required = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1);
-                            top.features.add(name, required);
-                            if (required) {
-                                addParentFeatures(&top, name);
-                            }
-
+                            top.features.add(name, true);
+                            addParentFeatures(&top, name);
                         } else {
                             int vers = getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error);
                             if (error == "") {
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 010d59b..0a80805 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1470,6 +1470,8 @@
     String16 action16("action");
     String16 category16("category");
     String16 data16("scheme");
+    String16 feature_group16("feature-group");
+    String16 uses_feature16("uses-feature");
     const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz"
         "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789";
     const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz"
@@ -1680,10 +1682,43 @@
                                  schemeIdentChars, true) != ATTR_OKAY) {
                     hasErrors = true;
                 }
+            } else if (strcmp16(block.getElementName(&len), feature_group16.string()) == 0) {
+                int depth = 1;
+                while ((code=block.next()) != ResXMLTree::END_DOCUMENT
+                       && code > ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::START_TAG) {
+                        depth++;
+                        if (strcmp16(block.getElementName(&len), uses_feature16.string()) == 0) {
+                            ssize_t idx = block.indexOfAttribute(
+                                    RESOURCES_ANDROID_NAMESPACE, "required");
+                            if (idx < 0) {
+                                continue;
+                            }
+
+                            int32_t data = block.getAttributeData(idx);
+                            if (data == 0) {
+                                fprintf(stderr, "%s:%d: Tag <uses-feature> can not have "
+                                        "android:required=\"false\" when inside a "
+                                        "<feature-group> tag.\n",
+                                        manifestPath.string(), block.getLineNumber());
+                                hasErrors = true;
+                            }
+                        }
+                    } else if (code == ResXMLTree::END_TAG) {
+                        depth--;
+                        if (depth == 0) {
+                            break;
+                        }
+                    }
+                }
             }
         }
     }
 
+    if (hasErrors) {
+        return UNKNOWN_ERROR;
+    }
+
     if (resFile != NULL) {
         // These resources are now considered to be a part of the included
         // resources, for others to reference.