Add tools:openDrawer to open a DrawerLayout.

Store a list of drawer layouts with tools:openDrawer encountered and
call openDrawer on them during the post-inflation processing.

Change-Id: Idee299a9af1bb106509a03bb2e8424c372b93dc5
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 20b6e41..1e33e3a 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -25,6 +25,7 @@
 import com.android.layoutlib.bridge.BridgeConstants;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
 import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
 import com.android.layoutlib.bridge.impl.ParserFactory;
 import com.android.layoutlib.bridge.util.ReflectionUtils;
@@ -33,10 +34,13 @@
 
 import org.xmlpull.v1.XmlPullParser;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.util.AttributeSet;
 
 import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
 
 import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
 
@@ -48,6 +52,7 @@
     private final LayoutlibCallback mLayoutlibCallback;
     private boolean mIsInMerge = false;
     private ResourceReference mResourceReference;
+    private Map<View, String> mOpenDrawerLayouts;
 
     /**
      * List of class prefixes which are tried first by default.
@@ -256,7 +261,14 @@
                     resourceId = 0;
                 }
                 RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId);
+            } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) {
+                String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
+                        BridgeConstants.ATTR_OPEN_DRAWER);
+                if (attrVal != null) {
+                    getDrawerLayoutMap().put(view, attrVal);
+                }
             }
+
         }
     }
 
@@ -312,4 +324,28 @@
 
         return viewKey;
     }
+
+    public void postInflateProcess(View view) {
+        if (mOpenDrawerLayouts != null) {
+            String gravity = mOpenDrawerLayouts.get(view);
+            if (gravity != null) {
+                DrawerLayoutUtil.openDrawer(view, gravity);
+            }
+            mOpenDrawerLayouts.remove(view);
+        }
+    }
+
+    @NonNull
+    private Map<View, String> getDrawerLayoutMap() {
+        if (mOpenDrawerLayouts == null) {
+            mOpenDrawerLayouts = new HashMap<View, String>(4);
+        }
+        return mOpenDrawerLayouts;
+    }
+
+    public void onDoneInflation() {
+        if (mOpenDrawerLayouts != null) {
+            mOpenDrawerLayouts.clear();
+        }
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
index 0e3fd1f..6228766 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java
@@ -54,4 +54,5 @@
     /** Attribute in the tools namespace used to specify layout manager for RecyclerView. */
     @SuppressWarnings("SpellCheckingInspection")
     public static final String ATTR_LIST_ITEM = "listitem";
+    public static final String ATTR_OPEN_DRAWER = "openDrawer";
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
new file mode 100644
index 0000000..40d3811
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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.layoutlib.bridge.android.support;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
+
+import android.annotation.Nullable;
+import android.view.View;
+
+import static android.view.Gravity.END;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.RIGHT;
+import static android.view.Gravity.START;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
+
+public class DrawerLayoutUtil {
+
+    public static final String CN_DRAWER_LAYOUT = "android.support.v4.widget.DrawerLayout";
+
+    public static void openDrawer(View drawerLayout, @Nullable String drawerGravity) {
+        int gravity = -1;
+        if ("left".equals(drawerGravity)) {
+            gravity = LEFT;
+        } else if ("right".equals(drawerGravity)) {
+            gravity = RIGHT;
+        } else if ("start".equals(drawerGravity)) {
+            gravity = START;
+        } else if ("end".equals(drawerGravity)) {
+            gravity = END;
+        }
+        if (gravity > 0) {
+            openDrawer(drawerLayout, gravity);
+        }
+    }
+
+    private static void openDrawer(View drawerLayout, int gravity) {
+        try {
+            invoke(getMethod(drawerLayout.getClass(), "openDrawer", int.class), drawerLayout,
+                    gravity);
+        } catch (ReflectionException e) {
+            Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to open navigation drawer",
+                    getCause(e), null);
+        }
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
index 4182cd9..d14c80b 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
@@ -21,6 +21,7 @@
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.util.ReflectionUtils;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +31,7 @@
 import java.lang.reflect.Method;
 
 import static com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause;
 import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
 import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
 
@@ -70,11 +72,6 @@
         }
     }
 
-    private static Throwable getCause(Throwable throwable) {
-        Throwable cause = throwable.getCause();
-        return cause == null ? throwable : cause;
-    }
-
     private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context,
             @NonNull LayoutlibCallback callback) throws ReflectionException {
         if (getLayoutManager(recyclerView) == null) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 72e97ad..23df3f1 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -423,6 +423,7 @@
 
             // post-inflate process. For now this supports TabHost/TabWidget
             postInflateProcess(view, params.getLayoutlibCallback(), isPreference ? view : null);
+            mInflater.onDoneInflation();
 
             setActiveToolbar(view, context, params);
 
@@ -1342,6 +1343,7 @@
                 }
             }
         } else if (view instanceof ViewGroup) {
+            mInflater.postInflateProcess(view);
             ViewGroup group = (ViewGroup) view;
             final int count = group.getChildCount();
             for (int c = 0; c < count; c++) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
index b78b613..7ce27b6 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
@@ -27,7 +27,7 @@
  */
 public class ReflectionUtils {
 
-    @Nullable
+    @NonNull
     public static Method getMethod(@NonNull Class<?> clazz, @NonNull String name,
             @Nullable Class<?>... params) throws ReflectionException {
         try {
@@ -67,6 +67,12 @@
         return false;
     }
 
+    @NonNull
+    public static Throwable getCause(@NonNull Throwable throwable) {
+        Throwable cause = throwable.getCause();
+        return cause == null ? throwable : cause;
+    }
+
     /**
      * Wraps all reflection related exceptions. Created since ReflectiveOperationException was
      * introduced in 1.7 and we are still on 1.6