1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * 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