• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getFactory()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 
getXml()254   public static XmlPullParser getXml() throws XmlPullParserException {
255     return getFactory().newPullParser();
256   }
257 
getXml(InputStream is)258   public static XmlPullParser getXml(InputStream is) throws XmlPullParserException {
259     XmlPullParser xml = getXml();
260     xml.setInput(is, null);
261     return xml;
262   }
263 
getXml(Reader ir)264   public static XmlPullParser getXml(Reader ir) throws XmlPullParserException {
265     XmlPullParser xml = getXml();
266     xml.setInput(ir);
267     return xml;
268   }
269 
inflate(Activity context, XmlPullParser xml)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 
addln(Object msg)287   private void addln(Object msg) {
288     Log.d(msg.toString());
289   }
290 
291   @SuppressWarnings("rawtypes")
setClickListener(View v, android.view.View.OnClickListener listener, OnItemClickListener itemListener)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 
inflateView(Context context, XmlPullParser xml, ViewGroup root)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 
skipTag(XmlPullParser xml)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 
buildView(Context context, XmlPullParser xml, ViewGroup root)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 
getLayoutValue(String value)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 
getFontSize(String value)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 
calcId(String value)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 
tryGetId(String value)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 
getLayoutParams(View view, ViewGroup root)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 
createLayoutParams(ViewGroup root)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 
setProperty(View view, String attr, String value)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 
setProperty(View view, ViewGroup root, String attr, String value)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 
setStretchColumns(View view, String value)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 
setLayoutProperty(View view, ViewGroup root, String attr, String value)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 
setBackground(View view, String value)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 
getDrawable(String value)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 
setImage(View view, String value)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 
setIntegerField(Object target, String fieldName, int value)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. */
expandColor(String colorValue)634   private int expandColor(String colorValue) {
635     return Integer.parseInt(colorValue + colorValue, 16);
636   }
637 
getColor(String value)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 
getInputType(String value)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 
setInteger(View view, String attr, int value)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 
setFloat(View view, String attr, float value)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 
setDynamicProperty(View view, String attr, String value)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 
PCase(String s)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 
tryMethod(Object o, String name, Class<?>... parameters)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 
camelCase(String s)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 
getInteger(Class<?> clazz, String value)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 
getInteger(View view, String value)809   private Integer getInteger(View view, String value) {
810     return getInteger(view.getClass(), value);
811   }
812 
parseTheme(String value)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 
viewClass(Context context, String name)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 
viewClassTry(Context context, String name)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 
getIdList()865   public Map<String, Integer> getIdList() {
866     return mIdList;
867   }
868 
getErrors()869   public List<String> getErrors() {
870     return mErrors;
871   }
872 
getIdName(int id)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 
getId(String name)882   public int getId(String name) {
883     return mIdList.get(name);
884   }
885 
getViewAsMap(View v)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 
getViewInfo(View v)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 
addProperty(View v, String attr, Map<String, String> dest)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 
getProperty(View v, String attr)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 
getInputTypes()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 */
setIdList(Class<?> idClass)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 
setListAdapter(View view, JSONArray items)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 
clearAll()1018   public void clearAll() {
1019     getErrors().clear();
1020     mIdList.clear();
1021     mNextSeq = BASESEQ;
1022   }
1023 }
1024