Add the tree hierarchy for the group

bug:15288554

Change-Id: I7caedb8466d078a0d9a6148e00d02eded4a4d234
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e347302..d28f9de 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4798,6 +4798,8 @@
         <attr name="scaleX" />
         <!-- The amount to scale the group on X coordinate -->
         <attr name="scaleY" />
+        <!-- The alpha of the group (0 is transparent and 1 is opaque) -->
+        <attr name="alpha" />
     </declare-styleable>
 
     <!-- Defines the path used in Vector Drawables. -->
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index afd529c..304502e 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -40,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Stack;
 
 /**
  * This lets you create a drawable based on an XML vector graphic It can be
@@ -129,6 +130,8 @@
     private static final int LINEJOIN_ROUND = 1;
     private static final int LINEJOIN_BEVEL = 2;
 
+    private static final boolean DBG_VECTOR_DRAWABLE = false;
+
     private final VectorDrawableState mVectorState;
 
     private int mAlpha = 0xFF;
@@ -279,12 +282,17 @@
         boolean noGroupTag = true;
         boolean noPathTag = true;
 
-        VGroup currentGroup = new VGroup();
+        // Use a stack to help to build the group tree.
+        // The top of the stack is always the current group.
+        final Stack<VGroup> groupStack = new Stack<VGroup>();
+        groupStack.push(pathRenderer.mRootGroup);
 
         int eventType = parser.getEventType();
         while (eventType != XmlPullParser.END_DOCUMENT) {
             if (eventType == XmlPullParser.START_TAG) {
                 final String tagName = parser.getName();
+                final VGroup currentGroup = groupStack.peek();
+
                 if (SHAPE_PATH.equals(tagName)) {
                     final VPath path = new VPath();
                     path.inflate(res, attrs, theme);
@@ -297,18 +305,24 @@
                     pathRenderer.parseViewport(res, attrs);
                     noViewportTag = false;
                 } else if (SHAPE_GROUP.equals(tagName)) {
-                    currentGroup = new VGroup();
-                    currentGroup.inflate(res, attrs, theme);
-                    pathRenderer.mGroupList.add(currentGroup);
+                    VGroup newChildGroup = new VGroup();
+                    newChildGroup.inflate(res, attrs, theme);
+                    currentGroup.mChildGroupList.add(newChildGroup);
+                    groupStack.push(newChildGroup);
                     noGroupTag = false;
                 }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                final String tagName = parser.getName();
+                if (SHAPE_GROUP.equals(tagName)) {
+                    groupStack.pop();
+                }
             }
-
             eventType = parser.next();
         }
 
-        if (noGroupTag && !noPathTag) {
-            pathRenderer.mGroupList.add(currentGroup);
+        // Print the tree out for debug.
+        if (DBG_VECTOR_DRAWABLE) {
+            printGroupTree(pathRenderer.mRootGroup, 0);
         }
 
         if (noSizeTag || noViewportTag || noPathTag) {
@@ -338,6 +352,21 @@
         return pathRenderer;
     }
 
+    private void printGroupTree(VGroup currentGroup, int level) {
+        String indent = "";
+        for (int i = 0 ; i < level ; i++) {
+            indent += "    ";
+        }
+        // Print the current node
+        Log.v(LOGTAG, indent + "current group is :" +  currentGroup.getName()
+                + " rotation is " + currentGroup.mRotate);
+        Log.v(LOGTAG, indent + "matrix is :" +  currentGroup.getLocalMatrix().toString());
+        // Then print all the children
+        for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
+            printGroupTree(currentGroup.mChildGroupList.get(i), level + 1);
+        }
+    }
+
     private void setPathRenderer(VPathRenderer pathRenderer) {
         mVectorState.mVPathRenderer = pathRenderer;
     }
@@ -350,6 +379,7 @@
         public VectorDrawableState(VectorDrawableState copy) {
             if (copy != null) {
                 mChangingConfigurations = copy.mChangingConfigurations;
+                // TODO: Make sure the constant state are handled correctly.
                 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
                 mPadding = new Rect(copy.mPadding);
             }
@@ -377,28 +407,42 @@
     }
 
     private static class VPathRenderer {
+        /* Right now the internal data structure is organized as a tree.
+         * Each node can be a group node, or a path.
+         * A group node can have groups or paths as children, but a path node has
+         * no children.
+         * One example can be:
+         *                 Root Group
+         *                /    |     \
+         *           Group    Path    Group
+         *          /     \             |
+         *         Path   Path         Path
+         *
+         */
+        private final VGroup mRootGroup;
+
         private final Path mPath = new Path();
         private final Path mRenderPath = new Path();
-        private final Matrix mMatrix = new Matrix();
+        private static final Matrix IDENTITY_MATRIX = new Matrix();
 
         private Paint mStrokePaint;
         private Paint mFillPaint;
         private ColorFilter mColorFilter;
         private PathMeasure mPathMeasure;
 
-        final ArrayList<VGroup> mGroupList = new ArrayList<VGroup>();
+        private float mBaseWidth = 0;
+        private float mBaseHeight = 0;
+        private float mViewportWidth = 0;
+        private float mViewportHeight = 0;
 
-        float mBaseWidth = 0;
-        float mBaseHeight = 0;
-        float mViewportWidth = 0;
-        float mViewportHeight = 0;
+        private final Matrix mFinalPathMatrix = new Matrix();
 
         public VPathRenderer() {
+            mRootGroup = new VGroup();
         }
 
         public VPathRenderer(VPathRenderer copy) {
-            mGroupList.addAll(copy.mGroupList);
-
+            mRootGroup = copy.mRootGroup;
             mBaseWidth = copy.mBaseWidth;
             mBaseHeight = copy.mBaseHeight;
             mViewportWidth = copy.mViewportHeight;
@@ -406,33 +450,59 @@
         }
 
         public boolean canApplyTheme() {
-            final ArrayList<VGroup> groups = mGroupList;
-            for (int i = groups.size() - 1; i >= 0; i--) {
-                final ArrayList<VPath> paths = groups.get(i).mVGList;
-                for (int j = paths.size() - 1; j >= 0; j--) {
-                    final VPath path = paths.get(j);
-                    if (path.canApplyTheme()) {
-                        return true;
-                    }
+            // If one of the paths can apply theme, then return true;
+            return recursiveCanApplyTheme(mRootGroup);
+        }
+
+        private boolean recursiveCanApplyTheme(VGroup currentGroup) {
+            // We can do a tree traverse here, if there is one path return true,
+            // then we return true for the whole tree.
+            final ArrayList<VPath> paths = currentGroup.mPathList;
+            for (int j = paths.size() - 1; j >= 0; j--) {
+                final VPath path = paths.get(j);
+                if (path.canApplyTheme()) {
+                    return true;
                 }
             }
 
+            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
+
+            for (int i = 0; i < childGroups.size(); i++) {
+                VGroup childGroup = childGroups.get(i);
+                if (childGroup.canApplyTheme()
+                        || recursiveCanApplyTheme(childGroup)) {
+                    return true;
+                }
+            }
             return false;
         }
 
         public void applyTheme(Theme t) {
-            final ArrayList<VGroup> groups = mGroupList;
-            for (int i = groups.size() - 1; i >= 0; i--) {
-                VGroup currentGroup = groups.get(i);
-                currentGroup.applyTheme(t);
-                final ArrayList<VPath> paths = currentGroup.mVGList;
-                for (int j = paths.size() - 1; j >= 0; j--) {
-                    final VPath path = paths.get(j);
-                    if (path.canApplyTheme()) {
-                        path.applyTheme(t);
-                    }
+            // Apply theme to every path of the tree.
+            recursiveApplyTheme(mRootGroup, t);
+        }
+
+        private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
+            // We can do a tree traverse here, apply theme to all paths which
+            // can apply theme.
+            final ArrayList<VPath> paths = currentGroup.mPathList;
+            for (int j = paths.size() - 1; j >= 0; j--) {
+                final VPath path = paths.get(j);
+                if (path.canApplyTheme()) {
+                    path.applyTheme(t);
                 }
             }
+
+            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
+
+            for (int i = 0; i < childGroups.size(); i++) {
+                VGroup childGroup = childGroups.get(i);
+                if (childGroup.canApplyTheme()) {
+                    childGroup.applyTheme(t);
+                }
+                recursiveApplyTheme(childGroup, t);
+            }
+
         }
 
         public void setColorFilter(ColorFilter colorFilter) {
@@ -448,34 +518,35 @@
 
         }
 
-        public void draw(Canvas canvas, int w, int h) {
-            if (mGroupList == null || mGroupList.size() == 0) {
-                Log.e(LOGTAG,"There is no group to draw");
-                return;
-            }
+        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
+                Canvas canvas, int w, int h) {
+            // Calculate current group's matrix by preConcat the parent's and
+            // and the current one on the top of the stack.
+            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
+            // Mi the local matrix at level i of the group tree.
+            currentGroup.mStackedMatrix.set(currentMatrix);
 
-            for (int i = 0; i < mGroupList.size(); i++) {
-                VGroup currentGroup = mGroupList.get(i);
-                if (currentGroup != null) {
-                    drawPath(currentGroup, canvas, w, h);
-                }
+            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
+
+            drawPath(currentGroup, canvas, w, h);
+            // Draw the group tree in post order.
+            for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
+                drawGroupTree(currentGroup.mChildGroupList.get(i),
+                        currentGroup.mStackedMatrix, canvas, w, h);
             }
         }
 
+        public void draw(Canvas canvas, int w, int h) {
+            // Travese the tree in pre-order to draw.
+            drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h);
+        }
+
         private void drawPath(VGroup vGroup, Canvas canvas, int w, int h) {
             final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
 
-            mMatrix.reset();
-
-            // The order we apply is the same as the
-            // RenderNode.cpp::applyViewPropertyTransforms().
-            mMatrix.postTranslate(-vGroup.mPivotX, -vGroup.mPivotY);
-            mMatrix.postScale(vGroup.mScaleX, vGroup.mScaleY);
-            mMatrix.postRotate(vGroup.mRotate, 0, 0);
-            mMatrix.postTranslate(vGroup.mTranslateX + vGroup.mPivotX, vGroup.mTranslateY + vGroup.mPivotY);
-
-            mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
-            mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
+            mFinalPathMatrix.set(vGroup.mStackedMatrix);
+            mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
+            mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
 
             ArrayList<VPath> paths = vGroup.getPaths();
             for (int i = 0; i < paths.size(); i++) {
@@ -507,7 +578,7 @@
 
                 mRenderPath.reset();
 
-                mRenderPath.addPath(path, mMatrix);
+                mRenderPath.addPath(path, mFinalPathMatrix);
 
                 if (vPath.mClip) {
                     canvas.clipPath(mRenderPath, Region.Op.REPLACE);
@@ -588,7 +659,8 @@
 
     private static class VGroup {
         private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>();
-        private final ArrayList<VPath> mVGList = new ArrayList<VPath>();
+        private final ArrayList<VPath> mPathList = new ArrayList<VPath>();
+        private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>();
 
         private float mRotate = 0;
         private float mPivotX = 0;
@@ -597,15 +669,36 @@
         private float mScaleY = 1;
         private float mTranslateX = 0;
         private float mTranslateY = 0;
+        private float mAlpha = 1;
+
+        // mLocalMatrix is parsed from the XML.
+        private final Matrix mLocalMatrix = new Matrix();
+        // mStackedMatrix is only used when drawing, it combines all the
+        // parents' local matrices with the current one.
+        private final Matrix mStackedMatrix = new Matrix();
 
         private int[] mThemeAttrs;
 
+        private String mName = null;
+
+        public String getName() {
+            return mName;
+        }
+
+        public Matrix getLocalMatrix() {
+            return mLocalMatrix;
+        }
+
         public void add(VPath path) {
             String id = path.getID();
             mVGPathMap.put(id, path);
-            mVGList.add(path);
+            mPathList.add(path);
          }
 
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null;
+        }
+
         public void applyTheme(Theme t) {
             if (mThemeAttrs == null) {
                 return;
@@ -621,6 +714,11 @@
             mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
             mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
             mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
+            mAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mAlpha);
+            updateLocalMatrix();
+            if (a.hasValue(R.styleable.VectorDrawableGroup_name)) {
+                mName = a.getString(R.styleable.VectorDrawableGroup_name);
+            }
             a.recycle();
         }
 
@@ -660,15 +758,34 @@
                 mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
             }
 
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) {
+                mName = a.getString(R.styleable.VectorDrawableGroup_name);
+            }
+
+            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) {
+                mAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mAlpha);
+            }
+
+            updateLocalMatrix();
             a.recycle();
         }
 
+        private void updateLocalMatrix() {
+            // The order we apply is the same as the
+            // RenderNode.cpp::applyViewPropertyTransforms().
+            mLocalMatrix.reset();
+            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
+            mLocalMatrix.postScale(mScaleX, mScaleY);
+            mLocalMatrix.postRotate(mRotate, 0, 0);
+            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
+        }
+
         /**
          * Must return in order of adding
          * @return ordered list of paths
          */
         public ArrayList<VPath> getPaths() {
-            return mVGList;
+            return mPathList;
         }
 
     }
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable22.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable22.xml
new file mode 100644
index 0000000..8d38cb5
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable22.xml
@@ -0,0 +1,72 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="64dp"
+        android:width="64dp" />
+
+    <viewport
+        android:viewportHeight="400"
+        android:viewportWidth="400" />
+
+    <group android:name="backgroundGroup" >
+        <path
+            android:name="background1"
+            android:fill="#80000000"
+            android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+        <path
+            android:name="background2"
+            android:fill="#80000000"
+            android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+    </group>
+    <group
+        android:name="translateToCenterGroup"
+        android:translateX="50.0"
+        android:translateY="90.0" >
+        <path
+            android:name="twoLines"
+            android:pathData="M 0,0 v 100 M 0,0 h 100"
+            android:stroke="#FFFF0000"
+            android:strokeWidth="20" />
+
+        <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="-45.0" >
+            <path
+                android:name="twoLines1"
+                android:pathData="M 0,0 v 100 M 0,0 h 100"
+                android:stroke="#FF00FF00"
+                android:strokeWidth="20" />
+
+            <group
+                android:name="translateGroup"
+                android:translateX="130.0"
+                android:translateY="160.0" >
+                <group android:name="scaleGroup" >
+                    <path
+                        android:name="twoLines2"
+                        android:pathData="M 0,0 v 100 M 0,0 h 100"
+                        android:stroke="#FF0000FF"
+                        android:strokeWidth="20" />
+                </group>
+            </group>
+        </group>
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable23.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable23.xml
new file mode 100644
index 0000000..52acd7a
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable23.xml
@@ -0,0 +1,86 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <size
+        android:height="64dp"
+        android:width="64dp" />
+
+    <viewport
+        android:viewportHeight="400"
+        android:viewportWidth="400" />
+
+    <group android:name="backgroundGroup" >
+        <path
+            android:name="background1"
+            android:fill="#80000000"
+            android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
+        <path
+            android:name="background2"
+            android:fill="#80000000"
+            android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
+    </group>
+    <group
+        android:name="translateToCenterGroup"
+        android:translateX="50.0"
+        android:translateY="90.0" >
+        <path
+            android:name="twoLines"
+            android:pathData="@string/twoLinePathData"
+            android:stroke="#FFFF0000"
+            android:strokeWidth="20" />
+
+        <group
+            android:name="rotationGroup"
+            android:pivotX="0.0"
+            android:pivotY="0.0"
+            android:rotation="-45.0" >
+            <path
+                android:name="twoLines1"
+                android:pathData="@string/twoLinePathData"
+                android:stroke="#FF00FF00"
+                android:strokeWidth="20" />
+
+            <group
+                android:name="translateGroup"
+                android:translateX="130.0"
+                android:translateY="160.0" >
+                <group android:name="scaleGroup" >
+                    <path
+                        android:name="twoLines3"
+                        android:pathData="@string/twoLinePathData"
+                        android:stroke="#FF0000FF"
+                        android:strokeWidth="20" />
+                </group>
+            </group>
+
+            <group
+                android:name="translateGroupHalf"
+                android:translateX="65.0"
+                android:translateY="80.0" >
+                <group android:name="scaleGroup" >
+                    <path
+                        android:name="twoLines2"
+                        android:pathData="@string/twoLinePathData"
+                        android:fill="?android:attr/colorForeground"
+                        android:stroke="?android:attr/colorForeground"
+                        android:strokeWidth="20" />
+                </group>
+            </group>
+        </group>
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml
index 64163c2..b49a1aa 100644
--- a/tests/VectorDrawableTest/res/values/strings.xml
+++ b/tests/VectorDrawableTest/res/values/strings.xml
@@ -15,4 +15,5 @@
 -->
 
 <resources>
+    <string name="twoLinePathData" >"M 0,0 v 100 M 0,0 h 100"</string>
 </resources>
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
index e0624e5..c2a5e6b 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/VectorDrawablePerformance.java
@@ -17,11 +17,11 @@
 import android.content.res.Resources;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Bundle;
-import android.view.View;
 import android.widget.TextView;
 import android.widget.Button;
 import android.widget.GridLayout;
 import android.widget.ScrollView;
+
 import java.text.DecimalFormat;
 
 @SuppressWarnings({"UnusedDeclaration"})
@@ -48,7 +48,9 @@
             R.drawable.vector_drawable18,
             R.drawable.vector_drawable19,
             R.drawable.vector_drawable20,
-            R.drawable.vector_drawable21
+            R.drawable.vector_drawable21,
+            R.drawable.vector_drawable22,
+            R.drawable.vector_drawable23
     };
 
     @Override