Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.googlecode.android_scripting.facade.ui;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.graphics.Bitmap;
     22 import android.graphics.BitmapFactory;
     23 import android.graphics.Typeface;
     24 import android.graphics.drawable.BitmapDrawable;
     25 import android.graphics.drawable.Drawable;
     26 import android.net.Uri;
     27 import android.text.InputType;
     28 import android.text.method.DigitsKeyListener;
     29 import android.util.DisplayMetrics;
     30 import android.view.Gravity;
     31 import android.view.View;
     32 import android.view.ViewGroup;
     33 import android.view.ViewGroup.LayoutParams;
     34 import android.view.ViewGroup.MarginLayoutParams;
     35 import android.widget.AdapterView;
     36 import android.widget.AdapterView.OnItemClickListener;
     37 import android.widget.ArrayAdapter;
     38 import android.widget.ListAdapter;
     39 import android.widget.RelativeLayout;
     40 import android.widget.Spinner;
     41 import android.widget.SpinnerAdapter;
     42 import android.widget.TableLayout;
     43 import android.widget.TextView;
     44 
     45 import com.googlecode.android_scripting.Log;
     46 
     47 import java.io.IOException;
     48 import java.io.InputStream;
     49 import java.io.Reader;
     50 import java.lang.reflect.Constructor;
     51 import java.lang.reflect.Field;
     52 import java.lang.reflect.InvocationTargetException;
     53 import java.lang.reflect.Method;
     54 import java.util.ArrayList;
     55 import java.util.HashMap;
     56 import java.util.List;
     57 import java.util.Map;
     58 import java.util.Map.Entry;
     59 
     60 import org.json.JSONArray;
     61 import org.xmlpull.v1.XmlPullParser;
     62 import org.xmlpull.v1.XmlPullParserException;
     63 import org.xmlpull.v1.XmlPullParserFactory;
     64 
     65 public class ViewInflater {
     66   private static XmlPullParserFactory mFactory;
     67   public static final String ANDROID = "http://schemas.android.com/apk/res/android";
     68   public static final int BASESEQ = 0x7f0f0000;
     69   private int mNextSeq = BASESEQ;
     70   private final Map<String, Integer> mIdList = new HashMap<String, Integer>();
     71   private final List<String> mErrors = new ArrayList<String>();
     72   private Context mContext;
     73   private DisplayMetrics mMetrics;
     74   private static final Map<String, Integer> mInputTypes = new HashMap<String, Integer>();
     75   public static final Map<String, String> mColorNames = new HashMap<String, String>();
     76   public static final Map<String, Integer> mRelative = new HashMap<String, Integer>();
     77   static {
     78     mColorNames.put("aliceblue", "#f0f8ff");
     79     mColorNames.put("antiquewhite", "#faebd7");
     80     mColorNames.put("aqua", "#00ffff");
     81     mColorNames.put("aquamarine", "#7fffd4");
     82     mColorNames.put("azure", "#f0ffff");
     83     mColorNames.put("beige", "#f5f5dc");
     84     mColorNames.put("bisque", "#ffe4c4");
     85     mColorNames.put("black", "#000000");
     86     mColorNames.put("blanchedalmond", "#ffebcd");
     87     mColorNames.put("blue", "#0000ff");
     88     mColorNames.put("blueviolet", "#8a2be2");
     89     mColorNames.put("brown", "#a52a2a");
     90     mColorNames.put("burlywood", "#deb887");
     91     mColorNames.put("cadetblue", "#5f9ea0");
     92     mColorNames.put("chartreuse", "#7fff00");
     93     mColorNames.put("chocolate", "#d2691e");
     94     mColorNames.put("coral", "#ff7f50");
     95     mColorNames.put("cornflowerblue", "#6495ed");
     96     mColorNames.put("cornsilk", "#fff8dc");
     97     mColorNames.put("crimson", "#dc143c");
     98     mColorNames.put("cyan", "#00ffff");
     99     mColorNames.put("darkblue", "#00008b");
    100     mColorNames.put("darkcyan", "#008b8b");
    101     mColorNames.put("darkgoldenrod", "#b8860b");
    102     mColorNames.put("darkgray", "#a9a9a9");
    103     mColorNames.put("darkgrey", "#a9a9a9");
    104     mColorNames.put("darkgreen", "#006400");
    105     mColorNames.put("darkkhaki", "#bdb76b");
    106     mColorNames.put("darkmagenta", "#8b008b");
    107     mColorNames.put("darkolivegreen", "#556b2f");
    108     mColorNames.put("darkorange", "#ff8c00");
    109     mColorNames.put("darkorchid", "#9932cc");
    110     mColorNames.put("darkred", "#8b0000");
    111     mColorNames.put("darksalmon", "#e9967a");
    112     mColorNames.put("darkseagreen", "#8fbc8f");
    113     mColorNames.put("darkslateblue", "#483d8b");
    114     mColorNames.put("darkslategray", "#2f4f4f");
    115     mColorNames.put("darkslategrey", "#2f4f4f");
    116     mColorNames.put("darkturquoise", "#00ced1");
    117     mColorNames.put("darkviolet", "#9400d3");
    118     mColorNames.put("deeppink", "#ff1493");
    119     mColorNames.put("deepskyblue", "#00bfff");
    120     mColorNames.put("dimgray", "#696969");
    121     mColorNames.put("dimgrey", "#696969");
    122     mColorNames.put("dodgerblue", "#1e90ff");
    123     mColorNames.put("firebrick", "#b22222");
    124     mColorNames.put("floralwhite", "#fffaf0");
    125     mColorNames.put("forestgreen", "#228b22");
    126     mColorNames.put("fuchsia", "#ff00ff");
    127     mColorNames.put("gainsboro", "#dcdcdc");
    128     mColorNames.put("ghostwhite", "#f8f8ff");
    129     mColorNames.put("gold", "#ffd700");
    130     mColorNames.put("goldenrod", "#daa520");
    131     mColorNames.put("gray", "#808080");
    132     mColorNames.put("grey", "#808080");
    133     mColorNames.put("green", "#008000");
    134     mColorNames.put("greenyellow", "#adff2f");
    135     mColorNames.put("honeydew", "#f0fff0");
    136     mColorNames.put("hotpink", "#ff69b4");
    137     mColorNames.put("indianred ", "#cd5c5c");
    138     mColorNames.put("indigo ", "#4b0082");
    139     mColorNames.put("ivory", "#fffff0");
    140     mColorNames.put("khaki", "#f0e68c");
    141     mColorNames.put("lavender", "#e6e6fa");
    142     mColorNames.put("lavenderblush", "#fff0f5");
    143     mColorNames.put("lawngreen", "#7cfc00");
    144     mColorNames.put("lemonchiffon", "#fffacd");
    145     mColorNames.put("lightblue", "#add8e6");
    146     mColorNames.put("lightcoral", "#f08080");
    147     mColorNames.put("lightcyan", "#e0ffff");
    148     mColorNames.put("lightgoldenrodyellow", "#fafad2");
    149     mColorNames.put("lightgray", "#d3d3d3");
    150     mColorNames.put("lightgrey", "#d3d3d3");
    151     mColorNames.put("lightgreen", "#90ee90");
    152     mColorNames.put("lightpink", "#ffb6c1");
    153     mColorNames.put("lightsalmon", "#ffa07a");
    154     mColorNames.put("lightseagreen", "#20b2aa");
    155     mColorNames.put("lightskyblue", "#87cefa");
    156     mColorNames.put("lightslategray", "#778899");
    157     mColorNames.put("lightslategrey", "#778899");
    158     mColorNames.put("lightsteelblue", "#b0c4de");
    159     mColorNames.put("lightyellow", "#ffffe0");
    160     mColorNames.put("lime", "#00ff00");
    161     mColorNames.put("limegreen", "#32cd32");
    162     mColorNames.put("linen", "#faf0e6");
    163     mColorNames.put("magenta", "#ff00ff");
    164     mColorNames.put("maroon", "#800000");
    165     mColorNames.put("mediumaquamarine", "#66cdaa");
    166     mColorNames.put("mediumblue", "#0000cd");
    167     mColorNames.put("mediumorchid", "#ba55d3");
    168     mColorNames.put("mediumpurple", "#9370d8");
    169     mColorNames.put("mediumseagreen", "#3cb371");
    170     mColorNames.put("mediumslateblue", "#7b68ee");
    171     mColorNames.put("mediumspringgreen", "#00fa9a");
    172     mColorNames.put("mediumturquoise", "#48d1cc");
    173     mColorNames.put("mediumvioletred", "#c71585");
    174     mColorNames.put("midnightblue", "#191970");
    175     mColorNames.put("mintcream", "#f5fffa");
    176     mColorNames.put("mistyrose", "#ffe4e1");
    177     mColorNames.put("moccasin", "#ffe4b5");
    178     mColorNames.put("navajowhite", "#ffdead");
    179     mColorNames.put("navy", "#000080");
    180     mColorNames.put("oldlace", "#fdf5e6");
    181     mColorNames.put("olive", "#808000");
    182     mColorNames.put("olivedrab", "#6b8e23");
    183     mColorNames.put("orange", "#ffa500");
    184     mColorNames.put("orangered", "#ff4500");
    185     mColorNames.put("orchid", "#da70d6");
    186     mColorNames.put("palegoldenrod", "#eee8aa");
    187     mColorNames.put("palegreen", "#98fb98");
    188     mColorNames.put("paleturquoise", "#afeeee");
    189     mColorNames.put("palevioletred", "#d87093");
    190     mColorNames.put("papayawhip", "#ffefd5");
    191     mColorNames.put("peachpuff", "#ffdab9");
    192     mColorNames.put("peru", "#cd853f");
    193     mColorNames.put("pink", "#ffc0cb");
    194     mColorNames.put("plum", "#dda0dd");
    195     mColorNames.put("powderblue", "#b0e0e6");
    196     mColorNames.put("purple", "#800080");
    197     mColorNames.put("red", "#ff0000");
    198     mColorNames.put("rosybrown", "#bc8f8f");
    199     mColorNames.put("royalblue", "#4169e1");
    200     mColorNames.put("saddlebrown", "#8b4513");
    201     mColorNames.put("salmon", "#fa8072");
    202     mColorNames.put("sandybrown", "#f4a460");
    203     mColorNames.put("seagreen", "#2e8b57");
    204     mColorNames.put("seashell", "#fff5ee");
    205     mColorNames.put("sienna", "#a0522d");
    206     mColorNames.put("silver", "#c0c0c0");
    207     mColorNames.put("skyblue", "#87ceeb");
    208     mColorNames.put("slateblue", "#6a5acd");
    209     mColorNames.put("slategray", "#708090");
    210     mColorNames.put("slategrey", "#708090");
    211     mColorNames.put("snow", "#fffafa");
    212     mColorNames.put("springgreen", "#00ff7f");
    213     mColorNames.put("steelblue", "#4682b4");
    214     mColorNames.put("tan", "#d2b48c");
    215     mColorNames.put("teal", "#008080");
    216     mColorNames.put("thistle", "#d8bfd8");
    217     mColorNames.put("tomato", "#ff6347");
    218     mColorNames.put("turquoise", "#40e0d0");
    219     mColorNames.put("violet", "#ee82ee");
    220     mColorNames.put("wheat", "#f5deb3");
    221     mColorNames.put("white", "#ffffff");
    222     mColorNames.put("whitesmoke", "#f5f5f5");
    223     mColorNames.put("yellow", "#ffff00");
    224     mColorNames.put("yellowgreen", "#9acd32");
    225 
    226     mRelative.put("above", RelativeLayout.ABOVE);
    227     mRelative.put("alignBaseline", RelativeLayout.ALIGN_BASELINE);
    228     mRelative.put("alignBottom", RelativeLayout.ALIGN_BOTTOM);
    229     mRelative.put("alignLeft", RelativeLayout.ALIGN_LEFT);
    230     mRelative.put("alignParentBottom", RelativeLayout.ALIGN_PARENT_BOTTOM);
    231     mRelative.put("alignParentLeft", RelativeLayout.ALIGN_PARENT_LEFT);
    232     mRelative.put("alignParentRight", RelativeLayout.ALIGN_PARENT_RIGHT);
    233     mRelative.put("alignParentTop", RelativeLayout.ALIGN_PARENT_TOP);
    234     mRelative.put("alignRight", RelativeLayout.ALIGN_PARENT_RIGHT);
    235     mRelative.put("alignTop", RelativeLayout.ALIGN_TOP);
    236     // mRelative.put("alignWithParentIfMissing",RelativeLayout.); // No idea what this translates
    237     // to.
    238     mRelative.put("below", RelativeLayout.BELOW);
    239     mRelative.put("centerHorizontal", RelativeLayout.CENTER_HORIZONTAL);
    240     mRelative.put("centerInParent", RelativeLayout.CENTER_IN_PARENT);
    241     mRelative.put("centerVertical", RelativeLayout.CENTER_VERTICAL);
    242     mRelative.put("toLeftOf", RelativeLayout.LEFT_OF);
    243     mRelative.put("toRightOf", RelativeLayout.RIGHT_OF);
    244   }
    245 
    246   public static XmlPullParserFactory getFactory() throws XmlPullParserException {
    247     if (mFactory == null) {
    248       mFactory = XmlPullParserFactory.newInstance();
    249       mFactory.setNamespaceAware(true);
    250     }
    251     return mFactory;
    252   }
    253 
    254   public static XmlPullParser getXml() throws XmlPullParserException {
    255     return getFactory().newPullParser();
    256   }
    257 
    258   public static XmlPullParser getXml(InputStream is) throws XmlPullParserException {
    259     XmlPullParser xml = getXml();
    260     xml.setInput(is, null);
    261     return xml;
    262   }
    263 
    264   public static XmlPullParser getXml(Reader ir) throws XmlPullParserException {
    265     XmlPullParser xml = getXml();
    266     xml.setInput(ir);
    267     return xml;
    268   }
    269 
    270   public View inflate(Activity context, XmlPullParser xml) throws XmlPullParserException,
    271       IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    272     int event;
    273     mContext = context;
    274     mErrors.clear();
    275     mMetrics = new DisplayMetrics();
    276     context.getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
    277     do {
    278       event = xml.next();
    279       if (event == XmlPullParser.END_DOCUMENT) {
    280         return null;
    281       }
    282     } while (event != XmlPullParser.START_TAG);
    283     View view = inflateView(context, xml, null);
    284     return view;
    285   }
    286 
    287   private void addln(Object msg) {
    288     Log.d(msg.toString());
    289   }
    290 
    291   @SuppressWarnings("rawtypes")
    292   public void setClickListener(View v, android.view.View.OnClickListener listener,
    293       OnItemClickListener itemListener) {
    294     if (v.isClickable()) {
    295 
    296       if (v instanceof AdapterView) {
    297         try {
    298           ((AdapterView) v).setOnItemClickListener(itemListener);
    299         } catch (RuntimeException e) {
    300           // Ignore this, not all controls support OnItemClickListener
    301         }
    302       }
    303       try {
    304         v.setOnClickListener(listener);
    305       } catch (RuntimeException e) {
    306         // And not all controls support OnClickListener.
    307       }
    308     }
    309     if (v instanceof ViewGroup) {
    310       ViewGroup vg = (ViewGroup) v;
    311       for (int i = 0; i < vg.getChildCount(); i++) {
    312         setClickListener(vg.getChildAt(i), listener, itemListener);
    313       }
    314     }
    315   }
    316 
    317   private View inflateView(Context context, XmlPullParser xml, ViewGroup root)
    318       throws IllegalArgumentException, IllegalAccessException, InvocationTargetException,
    319       XmlPullParserException, IOException {
    320     View view = buildView(context, xml, root);
    321     if (view == null) {
    322       return view;
    323     }
    324     int event;
    325     while ((event = xml.next()) != XmlPullParser.END_DOCUMENT) {
    326       switch (event) {
    327       case XmlPullParser.START_TAG:
    328         if (view == null || view instanceof ViewGroup) {
    329           inflateView(context, xml, (ViewGroup) view);
    330         } else {
    331           skipTag(xml); // Not really a view, probably, skip it.
    332         }
    333         break;
    334       case XmlPullParser.END_TAG:
    335         return view;
    336       }
    337     }
    338     return view;
    339   }
    340 
    341   private void skipTag(XmlPullParser xml) throws XmlPullParserException, IOException {
    342     int depth = xml.getDepth();
    343     int event;
    344     while ((event = xml.next()) != XmlPullParser.END_DOCUMENT) {
    345       if (event == XmlPullParser.END_TAG && xml.getDepth() <= depth) {
    346         break;
    347       }
    348     }
    349   }
    350 
    351   private View buildView(Context context, XmlPullParser xml, ViewGroup root)
    352       throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    353     View view = viewClass(context, xml.getName());
    354     if (view != null) {
    355       getLayoutParams(view, root); // Make quite sure every view has a layout param.
    356       for (int i = 0; i < xml.getAttributeCount(); i++) {
    357         String ns = xml.getAttributeNamespace(i);
    358         String attr = xml.getAttributeName(i);
    359         if (ANDROID.equals(ns)) {
    360           setProperty(view, root, attr, xml.getAttributeValue(i));
    361         }
    362       }
    363       if (root != null) {
    364         root.addView(view);
    365       }
    366     }
    367 
    368     return view;
    369   }
    370 
    371   private int getLayoutValue(String value) {
    372     if (value == null) {
    373       return 0;
    374     }
    375     if (value.equals("match_parent")) {
    376       return LayoutParams.MATCH_PARENT;
    377     }
    378     if (value.equals("wrap_content")) {
    379       return LayoutParams.WRAP_CONTENT;
    380     }
    381     if (value.equals("fill_parent")) {
    382       return LayoutParams.MATCH_PARENT;
    383     }
    384     return (int) getFontSize(value);
    385   }
    386 
    387   private float getFontSize(String value) {
    388     int i;
    389     float size;
    390     String unit = "px";
    391     for (i = 0; i < value.length(); i++) {
    392       char c = value.charAt(i);
    393       if (!(Character.isDigit(c) || c == '.')) {
    394         break;
    395       }
    396     }
    397     size = Float.parseFloat(value.substring(0, i));
    398     if (i < value.length()) {
    399       unit = value.substring(i).trim();
    400     }
    401     if (unit.equals("px")) {
    402       return size;
    403     }
    404     if (unit.equals("sp")) {
    405       return mMetrics.scaledDensity * size;
    406     }
    407     if (unit.equals("dp") || unit.equals("dip")) {
    408       return mMetrics.density * size;
    409     }
    410     float inches = mMetrics.ydpi * size;
    411     if (unit.equals("in")) {
    412       return inches;
    413     }
    414     if (unit.equals("pt")) {
    415       return inches / 72;
    416     }
    417     if (unit.equals("mm")) {
    418       return (float) (inches / 2.54);
    419     }
    420     return 0;
    421   }
    422 
    423   private int calcId(String value) {
    424     if (value == null) {
    425       return 0;
    426     }
    427     if (value.startsWith("@+id/")) {
    428       return tryGetId(value.substring(5));
    429     }
    430     if (value.startsWith("@id/")) {
    431       return tryGetId(value.substring(4));
    432     }
    433     try {
    434       return Integer.parseInt(value);
    435     } catch (NumberFormatException e) {
    436       return 0;
    437     }
    438   }
    439 
    440   private int tryGetId(String value) {
    441     Integer id = mIdList.get(value);
    442     if (id == null) {
    443       id = new Integer(mNextSeq++);
    444       mIdList.put(value, id);
    445     }
    446     return id;
    447   }
    448 
    449   private LayoutParams getLayoutParams(View view, ViewGroup root) {
    450     LayoutParams result = view.getLayoutParams();
    451     if (result == null) {
    452       result = createLayoutParams(root);
    453       view.setLayoutParams(result);
    454     }
    455     return result;
    456   }
    457 
    458   private LayoutParams createLayoutParams(ViewGroup root) {
    459     LayoutParams result = null;
    460     if (root != null) {
    461       try {
    462         String lookfor = root.getClass().getName() + "$LayoutParams";
    463         addln(lookfor);
    464         Class<? extends LayoutParams> clazz = Class.forName(lookfor).asSubclass(LayoutParams.class);
    465         if (clazz != null) {
    466           Constructor<? extends LayoutParams> ct = clazz.getConstructor(int.class, int.class);
    467           result = ct.newInstance(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    468         }
    469       } catch (Exception e) {
    470         result = null;
    471       }
    472     }
    473     if (result == null) {
    474       result = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    475     }
    476     return result;
    477   }
    478 
    479   public void setProperty(View view, String attr, String value) {
    480     try {
    481       setProperty(view, (ViewGroup) view.getParent(), attr, value);
    482     } catch (Exception e) {
    483       mErrors.add(e.toString());
    484     }
    485   }
    486 
    487   private void setProperty(View view, ViewGroup root, String attr, String value)
    488       throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    489     addln(attr + ":" + value);
    490     if (attr.startsWith("layout_")) {
    491       setLayoutProperty(view, root, attr, value);
    492     } else if (attr.equals("id")) {
    493       view.setId(calcId(value));
    494     } else if (attr.equals("gravity")) {
    495       setInteger(view, attr, getInteger(Gravity.class, value));
    496     } else if (attr.equals("width") || attr.equals("height")) {
    497       setInteger(view, attr, (int) getFontSize(value));
    498     } else if (attr.equals("inputType")) {
    499       setInteger(view, attr, getInteger(InputType.class, value));
    500     } else if (attr.equals("background")) {
    501       setBackground(view, value);
    502     } else if (attr.equals("digits") && view instanceof TextView) {
    503       ((TextView) view).setKeyListener(DigitsKeyListener.getInstance(value));
    504     } else if (attr.startsWith("nextFocus")) {
    505       setInteger(view, attr + "Id", calcId(value));
    506     } else if (attr.equals("padding")) {
    507       int size = (int) getFontSize(value);
    508       view.setPadding(size, size, size, size);
    509     } else if (attr.equals("stretchColumns")) {
    510       setStretchColumns(view, value);
    511     } else if (attr.equals("textSize")) {
    512       setFloat(view, attr, getFontSize(value));
    513     } else if (attr.equals("textColor")) {
    514       setInteger(view, attr, getColor(value));
    515     } else if (attr.equals("textHighlightColor")) {
    516       setInteger(view, "HighlightColor", getColor(value));
    517     } else if (attr.equals("textColorHint")) {
    518       setInteger(view, "LinkTextColor", getColor(value));
    519     } else if (attr.equals("textStyle")) {
    520       TextView textview = (TextView) view;
    521       int style = getInteger(Typeface.class, value);
    522       if (style == 0) {
    523         textview.setTypeface(Typeface.DEFAULT);
    524       } else {
    525         textview.setTypeface(textview.getTypeface(), style);
    526       }
    527     } else if (attr.equals("typeface")) {
    528       TextView textview = (TextView) view;
    529       Typeface typeface = textview.getTypeface();
    530       int style = typeface == null ? 0 : typeface.getStyle();
    531       textview.setTypeface(Typeface.create(value, style));
    532     } else if (attr.equals("src")) {
    533       setImage(view, value);
    534     } else {
    535       setDynamicProperty(view, attr, value);
    536     }
    537   }
    538 
    539   private void setStretchColumns(View view, String value) {
    540     TableLayout table = (TableLayout) view;
    541     String[] values = value.split(",");
    542     for (String column : values) {
    543       table.setColumnStretchable(Integer.parseInt(column), true);
    544     }
    545   }
    546 
    547   private void setLayoutProperty(View view, ViewGroup root, String attr, String value) {
    548     LayoutParams layout = getLayoutParams(view, root);
    549     String layoutAttr = attr.substring(7);
    550     if (layoutAttr.equals("width")) {
    551       layout.width = getLayoutValue(value);
    552     } else if (layoutAttr.equals("height")) {
    553       layout.height = getLayoutValue(value);
    554     } else if (layoutAttr.equals("gravity")) {
    555       setIntegerField(layout, "gravity", getInteger(Gravity.class, value));
    556     } else {
    557       if (layoutAttr.startsWith("margin") && layout instanceof MarginLayoutParams) {
    558         int size = (int) getFontSize(value);
    559         MarginLayoutParams margins = (MarginLayoutParams) layout;
    560         if (layoutAttr.equals("marginBottom")) {
    561           margins.bottomMargin = size;
    562         } else if (layoutAttr.equals("marginTop")) {
    563           margins.topMargin = size;
    564         } else if (layoutAttr.equals("marginLeft")) {
    565           margins.leftMargin = size;
    566         } else if (layoutAttr.equals("marginRight")) {
    567           margins.rightMargin = size;
    568         }
    569       } else if (layout instanceof RelativeLayout.LayoutParams) {
    570         int anchor = calcId(value);
    571         if (anchor == 0) {
    572           anchor = getInteger(RelativeLayout.class, value);
    573         }
    574         int rule = mRelative.get(layoutAttr);
    575         ((RelativeLayout.LayoutParams) layout).addRule(rule, anchor);
    576       } else {
    577         setIntegerField(layout, layoutAttr, getInteger(layout.getClass(), value));
    578       }
    579     }
    580   }
    581 
    582   private void setBackground(View view, String value) {
    583     if (value.startsWith("#")) {
    584       view.setBackgroundColor(getColor(value));
    585     } else if (value.startsWith("@")) {
    586       setInteger(view, "backgroundResource", getInteger(view, value));
    587     } else {
    588       view.setBackground(getDrawable(value));
    589     }
    590   }
    591 
    592   private Drawable getDrawable(String value) {
    593     try {
    594       Uri uri = Uri.parse(value);
    595       if ("file".equals(uri.getScheme())) {
    596         BitmapDrawable bd = new BitmapDrawable(mContext.getResources(), uri.getPath());
    597         return bd;
    598       }
    599     } catch (Exception e) {
    600       mErrors.add("failed to load drawable " + value);
    601     }
    602     return null;
    603   }
    604 
    605   private void setImage(View view, String value) {
    606     if (value.startsWith("@")) {
    607       setInteger(view, "imageResource", getInteger(view, value));
    608     } else {
    609       try {
    610         Uri uri = Uri.parse(value);
    611         if ("file".equals(uri.getScheme())) {
    612           Bitmap bm = BitmapFactory.decodeFile(uri.getPath());
    613           Method method = view.getClass().getMethod("setImageBitmap", Bitmap.class);
    614           method.invoke(view, bm);
    615         } else {
    616           mErrors.add("Only 'file' currently supported for images");
    617         }
    618       } catch (Exception e) {
    619         mErrors.add("failed to set image " + value);
    620       }
    621     }
    622   }
    623 
    624   private void setIntegerField(Object target, String fieldName, int value) {
    625     try {
    626       Field f = target.getClass().getField(fieldName);
    627       f.setInt(target, value);
    628     } catch (Exception e) {
    629       mErrors.add("set field)" + fieldName + " failed. " + e.toString());
    630     }
    631   }
    632 
    633   /** Expand single digit color to 2 digits. */
    634   private int expandColor(String colorValue) {
    635     return Integer.parseInt(colorValue + colorValue, 16);
    636   }
    637 
    638   private int getColor(String value) {
    639     int a = 0xff, r = 0, g = 0, b = 0;
    640     if (value.startsWith("#")) {
    641       try {
    642         value = value.substring(1);
    643         if (value.length() == 4) {
    644           a = expandColor(value.substring(0, 1));
    645           value = value.substring(1);
    646         }
    647         if (value.length() == 3) {
    648           r = expandColor(value.substring(0, 1));
    649           g = expandColor(value.substring(1, 2));
    650           b = expandColor(value.substring(2, 3));
    651         } else {
    652           if (value.length() == 8) {
    653             a = Integer.parseInt(value.substring(0, 2), 16);
    654             value = value.substring(2);
    655           }
    656           if (value.length() == 6) {
    657             r = Integer.parseInt(value.substring(0, 2), 16);
    658             g = Integer.parseInt(value.substring(2, 4), 16);
    659             b = Integer.parseInt(value.substring(4, 6), 16);
    660           }
    661         }
    662         long result = (a << 24) | (r << 16) | (g << 8) | b;
    663         return (int) result;
    664       } catch (Exception e) {
    665       }
    666     } else if (mColorNames.containsKey(value.toLowerCase())) {
    667       return getColor(mColorNames.get(value.toLowerCase()));
    668     }
    669     mErrors.add("Unknown color " + value);
    670     return 0;
    671   }
    672 
    673   private int getInputType(String value) {
    674     int result = 0;
    675     Integer v = getInputTypes().get(value);
    676     if (v == null) {
    677       mErrors.add("Unkown input type " + value);
    678     } else {
    679       result = v;
    680     }
    681     return result;
    682   }
    683 
    684   private void setInteger(View view, String attr, int value) {
    685     String name = "set" + PCase(attr);
    686     Method m;
    687     try {
    688       if ((m = tryMethod(view, name, Context.class, int.class)) != null) {
    689         m.invoke(view, mContext, value);
    690       } else if ((m = tryMethod(view, name, int.class)) != null) {
    691         m.invoke(view, value);
    692       }
    693     } catch (Exception e) {
    694       addln(name + ":" + value + ":" + e.toString());
    695     }
    696 
    697   }
    698 
    699   private void setFloat(View view, String attr, float value) {
    700     String name = "set" + PCase(attr);
    701     Method m;
    702     try {
    703       if ((m = tryMethod(view, name, Context.class, float.class)) != null) {
    704         m.invoke(view, mContext, value);
    705       } else if ((m = tryMethod(view, name, float.class)) != null) {
    706         m.invoke(view, value);
    707       }
    708     } catch (Exception e) {
    709       addln(name + ":" + value + ":" + e.toString());
    710     }
    711 
    712   }
    713 
    714   private void setDynamicProperty(View view, String attr, String value)
    715       throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    716     String name = "set" + PCase(attr);
    717     try {
    718       Method m = tryMethod(view, name, CharSequence.class);
    719       if (m != null) {
    720         m.invoke(view, value);
    721       } else if ((m = tryMethod(view, name, Context.class, int.class)) != null) {
    722         m.invoke(view, mContext, getInteger(view, value));
    723       } else if ((m = tryMethod(view, name, int.class)) != null) {
    724         m.invoke(view, getInteger(view, value));
    725       } else if ((m = tryMethod(view, name, float.class)) != null) {
    726         m.invoke(view, Float.parseFloat(value));
    727       } else if ((m = tryMethod(view, name, boolean.class)) != null) {
    728         m.invoke(view, Boolean.parseBoolean(value));
    729       } else if ((m = tryMethod(view, name, Object.class)) != null) {
    730         m.invoke(view, value);
    731       } else {
    732         mErrors.add(view.getClass().getSimpleName() + ":" + attr + " Property not found.");
    733       }
    734     } catch (Exception e) {
    735       addln(name + ":" + value + ":" + e.toString());
    736       mErrors.add(name + ":" + value + ":" + e.toString());
    737     }
    738   }
    739 
    740   private String PCase(String s) {
    741     if (s == null) {
    742       return null;
    743     }
    744     if (s.length() > 0) {
    745       return s.substring(0, 1).toUpperCase() + s.substring(1);
    746     }
    747     return "";
    748   }
    749 
    750   private Method tryMethod(Object o, String name, Class<?>... parameters) {
    751     Method result;
    752     try {
    753       result = o.getClass().getMethod(name, parameters);
    754     } catch (Exception e) {
    755       result = null;
    756     }
    757     return result;
    758   }
    759 
    760   public String camelCase(String s) {
    761     if (s == null) {
    762       return "";
    763     } else if (s.length() < 2) {
    764       return s.toUpperCase();
    765     } else {
    766       return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
    767     }
    768   }
    769 
    770   private Integer getInteger(Class<?> clazz, String value) {
    771     Integer result = null;
    772     if (value.contains("|")) {
    773       int work = 0;
    774       for (String s : value.split("\\|")) {
    775         work |= getInteger(clazz, s);
    776       }
    777       result = work;
    778     } else {
    779       if (value.startsWith("?")) {
    780         result = parseTheme(value);
    781       } else if (value.startsWith("@")) {
    782         result = parseTheme(value);
    783       } else if (value.startsWith("0x")) {
    784         try {
    785           result = (int) Long.parseLong(value.substring(2), 16);
    786         } catch (NumberFormatException e) {
    787           result = 0;
    788         }
    789       } else {
    790         try {
    791           result = Integer.parseInt(value);
    792         } catch (NumberFormatException e) {
    793           if (clazz == InputType.class) {
    794             return getInputType(value);
    795           }
    796           try {
    797             Field f = clazz.getField(value.toUpperCase());
    798             result = f.getInt(null);
    799           } catch (Exception ex) {
    800             mErrors.add("Unknown value: " + value);
    801             result = 0;
    802           }
    803         }
    804       }
    805     }
    806     return result;
    807   }
    808 
    809   private Integer getInteger(View view, String value) {
    810     return getInteger(view.getClass(), value);
    811   }
    812 
    813   private Integer parseTheme(String value) {
    814     int result;
    815     try {
    816       String query = "";
    817       int i;
    818       value = value.substring(1); // skip past "?"
    819       i = value.indexOf(":");
    820       if (i >= 0) {
    821         query = value.substring(0, i) + ".";
    822         value = value.substring(i + 1);
    823       }
    824       query += "R";
    825       i = value.indexOf("/");
    826       if (i >= 0) {
    827         query += "$" + value.substring(0, i);
    828         value = value.substring(i + 1);
    829       }
    830       Class<?> clazz = Class.forName(query);
    831       Field f = clazz.getField(value);
    832       result = f.getInt(null);
    833     } catch (Exception e) {
    834       result = 0;
    835     }
    836     return result;
    837   }
    838 
    839   private View viewClass(Context context, String name) {
    840     View result = null;
    841     result = viewClassTry(context, "android.view." + name);
    842     if (result == null) {
    843       result = viewClassTry(context, "android.widget." + name);
    844     }
    845     if (result == null) {
    846       result = viewClassTry(context, name);
    847     }
    848     return result;
    849   }
    850 
    851   private View viewClassTry(Context context, String name) {
    852     View result = null;
    853     try {
    854       Class<? extends View> viewclass = Class.forName(name).asSubclass(View.class);
    855       if (viewclass != null) {
    856         Constructor<? extends View> ct = viewclass.getConstructor(Context.class);
    857         result = ct.newInstance(context);
    858       }
    859     } catch (Exception e) {
    860     }
    861     return result;
    862 
    863   }
    864 
    865   public Map<String, Integer> getIdList() {
    866     return mIdList;
    867   }
    868 
    869   public List<String> getErrors() {
    870     return mErrors;
    871   }
    872 
    873   public String getIdName(int id) {
    874     for (String key : mIdList.keySet()) {
    875       if (mIdList.get(key) == id) {
    876         return key;
    877       }
    878     }
    879     return null;
    880   }
    881 
    882   public int getId(String name) {
    883     return mIdList.get(name);
    884   }
    885 
    886   public Map<String, Map<String, String>> getViewAsMap(View v) {
    887     Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
    888     for (Entry<String, Integer> entry : mIdList.entrySet()) {
    889       View tmp = v.findViewById(entry.getValue());
    890       if (tmp != null) {
    891         result.put(entry.getKey(), getViewInfo(tmp));
    892       }
    893     }
    894     return result;
    895   }
    896 
    897   public Map<String, String> getViewInfo(View v) {
    898     Map<String, String> result = new HashMap<String, String>();
    899     if (v.getId() != 0) {
    900       result.put("id", getIdName(v.getId()));
    901     }
    902     result.put("type", v.getClass().getSimpleName());
    903     addProperty(v, "text", result);
    904     addProperty(v, "visibility", result);
    905     addProperty(v, "checked", result);
    906     addProperty(v, "tag", result);
    907     addProperty(v, "selectedItemPosition", result);
    908     addProperty(v, "progress", result);
    909     return result;
    910   }
    911 
    912   private void addProperty(View v, String attr, Map<String, String> dest) {
    913     String result = getProperty(v, attr);
    914     if (result != null) {
    915       dest.put(attr, result);
    916     }
    917   }
    918 
    919   private String getProperty(View v, String attr) {
    920     String name = PCase(attr);
    921     Method m = tryMethod(v, "get" + name);
    922     if (m == null) {
    923       m = tryMethod(v, "is" + name);
    924     }
    925     String result = null;
    926     if (m != null) {
    927       try {
    928         Object o = m.invoke(v);
    929         if (o != null) {
    930           result = o.toString();
    931         }
    932       } catch (Exception e) {
    933         result = null;
    934       }
    935     }
    936     return result;
    937   }
    938 
    939   public static Map<String, Integer> getInputTypes() {
    940     if (mInputTypes.size() == 0) {
    941       mInputTypes.put("none", 0x00000000);
    942       mInputTypes.put("text", 0x00000001);
    943       mInputTypes.put("textCapCharacters", 0x00001001);
    944       mInputTypes.put("textCapWords", 0x00002001);
    945       mInputTypes.put("textCapSentences", 0x00004001);
    946       mInputTypes.put("textAutoCorrect", 0x00008001);
    947       mInputTypes.put("textAutoComplete", 0x00010001);
    948       mInputTypes.put("textMultiLine", 0x00020001);
    949       mInputTypes.put("textImeMultiLine", 0x00040001);
    950       mInputTypes.put("textNoSuggestions", 0x00080001);
    951       mInputTypes.put("textUri", 0x00000011);
    952       mInputTypes.put("textEmailAddress", 0x00000021);
    953       mInputTypes.put("textEmailSubject", 0x00000031);
    954       mInputTypes.put("textShortMessage", 0x00000041);
    955       mInputTypes.put("textLongMessage", 0x00000051);
    956       mInputTypes.put("textPersonName", 0x00000061);
    957       mInputTypes.put("textPostalAddress", 0x00000071);
    958       mInputTypes.put("textPassword", 0x00000081);
    959       mInputTypes.put("textVisiblePassword", 0x00000091);
    960       mInputTypes.put("textWebEditText", 0x000000a1);
    961       mInputTypes.put("textFilter", 0x000000b1);
    962       mInputTypes.put("textPhonetic", 0x000000c1);
    963       mInputTypes.put("textWebEmailAddress", 0x000000d1);
    964       mInputTypes.put("textWebPassword", 0x000000e1);
    965       mInputTypes.put("number", 0x00000002);
    966       mInputTypes.put("numberSigned", 0x00001002);
    967       mInputTypes.put("numberDecimal", 0x00002002);
    968       mInputTypes.put("numberPassword", 0x00000012);
    969       mInputTypes.put("phone", 0x00000003);
    970       mInputTypes.put("datetime", 0x00000004);
    971       mInputTypes.put("date", 0x00000014);
    972       mInputTypes.put("time", 0x00000024);
    973     }
    974     return mInputTypes;
    975   }
    976 
    977   /** Query class (typically R.id) to extract id names */
    978   public void setIdList(Class<?> idClass) {
    979     mIdList.clear();
    980     for (Field f : idClass.getDeclaredFields()) {
    981       try {
    982         String name = f.getName();
    983         int value = f.getInt(null);
    984         mIdList.put(name, value);
    985       } catch (Exception e) {
    986         // Ignore
    987       }
    988     }
    989   }
    990 
    991   public void setListAdapter(View view, JSONArray items) {
    992     List<String> list = new ArrayList<String>();
    993     try {
    994       for (int i = 0; i < items.length(); i++) {
    995         list.add(items.get(i).toString());
    996       }
    997       ArrayAdapter<String> adapter;
    998       if (view instanceof Spinner) {
    999         adapter =
   1000             new ArrayAdapter<String>(mContext, android.R.layout.simple_spinner_item,
   1001                 android.R.id.text1, list);
   1002       } else {
   1003         adapter =
   1004             new ArrayAdapter<String>(mContext, android.R.layout.simple_list_item_1,
   1005                 android.R.id.text1, list);
   1006       }
   1007       adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
   1008       Method m = tryMethod(view, "setAdapter", SpinnerAdapter.class);
   1009       if (m == null) {
   1010         m = view.getClass().getMethod("setAdapter", ListAdapter.class);
   1011       }
   1012       m.invoke(view, adapter);
   1013     } catch (Exception e) {
   1014       mErrors.add("failed to load list " + e.getMessage());
   1015     }
   1016   }
   1017 
   1018   public void clearAll() {
   1019     getErrors().clear();
   1020     mIdList.clear();
   1021     mNextSeq = BASESEQ;
   1022   }
   1023 }
   1024