| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.layoutlib.bridge; |
| |
| import com.android.ninepatch.NinePatch; |
| |
| import org.kxml2.io.KXmlParser; |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.util.TypedValue; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Helper class to provide various convertion method used in handling android resources. |
| */ |
| public final class ResourceHelper { |
| |
| private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); |
| private final static float[] sFloatOut = new float[1]; |
| |
| private final static TypedValue mValue = new TypedValue(); |
| |
| /** |
| * Returns the color value represented by the given string value |
| * @param value the color value |
| * @return the color as an int |
| * @throw NumberFormatException if the conversion failed. |
| */ |
| static int getColor(String value) { |
| if (value != null) { |
| if (value.startsWith("#") == false) { |
| throw new NumberFormatException(); |
| } |
| |
| value = value.substring(1); |
| |
| // make sure it's not longer than 32bit |
| if (value.length() > 8) { |
| throw new NumberFormatException(); |
| } |
| |
| if (value.length() == 3) { // RGB format |
| char[] color = new char[8]; |
| color[0] = color[1] = 'F'; |
| color[2] = color[3] = value.charAt(0); |
| color[4] = color[5] = value.charAt(1); |
| color[6] = color[7] = value.charAt(2); |
| value = new String(color); |
| } else if (value.length() == 4) { // ARGB format |
| char[] color = new char[8]; |
| color[0] = color[1] = value.charAt(0); |
| color[2] = color[3] = value.charAt(1); |
| color[4] = color[5] = value.charAt(2); |
| color[6] = color[7] = value.charAt(3); |
| value = new String(color); |
| } else if (value.length() == 6) { |
| value = "FF" + value; |
| } |
| |
| // this is a RRGGBB or AARRGGBB value |
| |
| // Integer.parseInt will fail to parse strings like "ff191919", so we use |
| // a Long, but cast the result back into an int, since we know that we're only |
| // dealing with 32 bit values. |
| return (int)Long.parseLong(value, 16); |
| } |
| |
| throw new NumberFormatException(); |
| } |
| |
| /** |
| * Returns a drawable from the given value. |
| * @param value The value. A path to a 9 patch, a bitmap or a xml based drawable, |
| * or an hexadecimal color |
| * @param context |
| * @param isFramework indicates whether the resource is a framework resources. |
| * Framework resources are cached, and loaded only once. |
| */ |
| public static Drawable getDrawable(String value, BridgeContext context, boolean isFramework) { |
| Drawable d = null; |
| |
| String lowerCaseValue = value.toLowerCase(); |
| |
| if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) { |
| File f = new File(value); |
| if (f.isFile()) { |
| NinePatch ninePatch = Bridge.getCached9Patch(value, |
| isFramework ? null : context.getProjectKey()); |
| |
| if (ninePatch == null) { |
| try { |
| ninePatch = NinePatch.load(new File(value).toURL(), false /* convert */); |
| |
| Bridge.setCached9Patch(value, ninePatch, |
| isFramework ? null : context.getProjectKey()); |
| } catch (MalformedURLException e) { |
| // URL is wrong, we'll return null below |
| } catch (IOException e) { |
| // failed to read the file, we'll return null below. |
| } |
| } |
| |
| if (ninePatch != null) { |
| return new NinePatchDrawable(ninePatch); |
| } |
| } |
| |
| return null; |
| } else if (lowerCaseValue.endsWith(".xml")) { |
| // create a blockparser for the file |
| File f = new File(value); |
| if (f.isFile()) { |
| try { |
| // let the framework inflate the Drawable from the XML file. |
| KXmlParser parser = new KXmlParser(); |
| parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); |
| parser.setInput(new FileReader(f)); |
| |
| d = Drawable.createFromXml(context.getResources(), |
| // FIXME: we need to know if this resource is platform or not |
| new BridgeXmlBlockParser(parser, context, false)); |
| return d; |
| } catch (XmlPullParserException e) { |
| context.getLogger().error(e); |
| } catch (FileNotFoundException e) { |
| // will not happen, since we pre-check |
| } catch (IOException e) { |
| context.getLogger().error(e); |
| } |
| } |
| |
| return null; |
| } else { |
| File bmpFile = new File(value); |
| if (bmpFile.isFile()) { |
| try { |
| Bitmap bitmap = Bridge.getCachedBitmap(value, |
| isFramework ? null : context.getProjectKey()); |
| |
| if (bitmap == null) { |
| bitmap = new Bitmap(bmpFile); |
| Bridge.setCachedBitmap(value, bitmap, |
| isFramework ? null : context.getProjectKey()); |
| } |
| |
| return new BitmapDrawable(bitmap); |
| } catch (IOException e) { |
| // we'll return null below |
| // TODO: log the error. |
| } |
| } else { |
| // attempt to get a color from the value |
| try { |
| int color = getColor(value); |
| return new ColorDrawable(color); |
| } catch (NumberFormatException e) { |
| // we'll return null below. |
| // TODO: log the error |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| // ------- TypedValue stuff |
| // This is taken from //device/libs/utils/ResourceTypes.cpp |
| |
| private static final class UnitEntry { |
| String name; |
| int type; |
| int unit; |
| float scale; |
| |
| UnitEntry(String name, int type, int unit, float scale) { |
| this.name = name; |
| this.type = type; |
| this.unit = unit; |
| this.scale = scale; |
| } |
| } |
| |
| private final static UnitEntry[] sUnitNames = new UnitEntry[] { |
| new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f), |
| new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), |
| new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), |
| new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f), |
| new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f), |
| new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f), |
| new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f), |
| new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100), |
| new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100), |
| }; |
| |
| /** |
| * Returns the raw value from the given string. |
| * This object is only valid until the next call on to {@link ResourceHelper}. |
| */ |
| public static TypedValue getValue(String s) { |
| if (stringToFloat(s, mValue)) { |
| return mValue; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Convert the string into a {@link TypedValue}. |
| * @param s |
| * @param outValue |
| * @return true if success. |
| */ |
| public static boolean stringToFloat(String s, TypedValue outValue) { |
| // remove the space before and after |
| s.trim(); |
| int len = s.length(); |
| |
| if (len <= 0) { |
| return false; |
| } |
| |
| // check that there's no non ascii characters. |
| char[] buf = s.toCharArray(); |
| for (int i = 0 ; i < len ; i++) { |
| if (buf[i] > 255) { |
| return false; |
| } |
| } |
| |
| // check the first character |
| if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.') { |
| return false; |
| } |
| |
| // now look for the string that is after the float... |
| Matcher m = sFloatPattern.matcher(s); |
| if (m.matches()) { |
| String f_str = m.group(1); |
| String end = m.group(2); |
| |
| float f; |
| try { |
| f = Float.parseFloat(f_str); |
| } catch (NumberFormatException e) { |
| // this shouldn't happen with the regexp above. |
| return false; |
| } |
| |
| if (end.length() > 0 && end.charAt(0) != ' ') { |
| // Might be a unit... |
| if (parseUnit(end, outValue, sFloatOut)) { |
| |
| f *= sFloatOut[0]; |
| boolean neg = f < 0; |
| if (neg) { |
| f = -f; |
| } |
| long bits = (long)(f*(1<<23)+.5f); |
| int radix; |
| int shift; |
| if ((bits&0x7fffff) == 0) { |
| // Always use 23p0 if there is no fraction, just to make |
| // things easier to read. |
| radix = TypedValue.COMPLEX_RADIX_23p0; |
| shift = 23; |
| } else if ((bits&0xffffffffff800000L) == 0) { |
| // Magnitude is zero -- can fit in 0 bits of precision. |
| radix = TypedValue.COMPLEX_RADIX_0p23; |
| shift = 0; |
| } else if ((bits&0xffffffff80000000L) == 0) { |
| // Magnitude can fit in 8 bits of precision. |
| radix = TypedValue.COMPLEX_RADIX_8p15; |
| shift = 8; |
| } else if ((bits&0xffffff8000000000L) == 0) { |
| // Magnitude can fit in 16 bits of precision. |
| radix = TypedValue.COMPLEX_RADIX_16p7; |
| shift = 16; |
| } else { |
| // Magnitude needs entire range, so no fractional part. |
| radix = TypedValue.COMPLEX_RADIX_23p0; |
| shift = 23; |
| } |
| int mantissa = (int)( |
| (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK); |
| if (neg) { |
| mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK; |
| } |
| outValue.data |= |
| (radix<<TypedValue.COMPLEX_RADIX_SHIFT) |
| | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT); |
| return true; |
| } |
| return false; |
| } |
| |
| // make sure it's only spaces at the end. |
| end = end.trim(); |
| |
| if (end.length() == 0) { |
| if (outValue != null) { |
| outValue.type = TypedValue.TYPE_FLOAT; |
| outValue.data = Float.floatToIntBits(f); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) { |
| str = str.trim(); |
| |
| for (UnitEntry unit : sUnitNames) { |
| if (unit.name.equals(str)) { |
| outValue.type = unit.type; |
| outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT; |
| outScale[0] = unit.scale; |
| |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| } |