ResourceHelper.java revision c2e9651bf386a1f7bf7fc706cf5424950570470c
1/* 2 * Copyright (C) 2008 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 17package com.android.layoutlib.bridge.impl; 18 19import com.android.layoutlib.api.IDensityBasedResourceValue; 20import com.android.layoutlib.api.IDensityBasedResourceValue.Density; 21import com.android.layoutlib.api.IResourceValue; 22import com.android.layoutlib.bridge.Bridge; 23import com.android.layoutlib.bridge.android.BridgeContext; 24import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 25import com.android.layoutlib.bridge.android.NinePatchDrawable; 26import com.android.ninepatch.NinePatch; 27 28import org.kxml2.io.KXmlParser; 29import org.xmlpull.v1.XmlPullParser; 30import org.xmlpull.v1.XmlPullParserException; 31 32import android.graphics.Bitmap; 33import android.graphics.Bitmap_Delegate; 34import android.graphics.drawable.BitmapDrawable; 35import android.graphics.drawable.ColorDrawable; 36import android.graphics.drawable.Drawable; 37import android.util.TypedValue; 38 39import java.io.File; 40import java.io.FileNotFoundException; 41import java.io.FileReader; 42import java.io.IOException; 43import java.net.MalformedURLException; 44import java.util.regex.Matcher; 45import java.util.regex.Pattern; 46 47/** 48 * Helper class to provide various convertion method used in handling android resources. 49 */ 50public final class ResourceHelper { 51 52 private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); 53 private final static float[] sFloatOut = new float[1]; 54 55 private final static TypedValue mValue = new TypedValue(); 56 57 /** 58 * Returns the color value represented by the given string value 59 * @param value the color value 60 * @return the color as an int 61 * @throw NumberFormatException if the conversion failed. 62 */ 63 public static int getColor(String value) { 64 if (value != null) { 65 if (value.startsWith("#") == false) { 66 throw new NumberFormatException(); 67 } 68 69 value = value.substring(1); 70 71 // make sure it's not longer than 32bit 72 if (value.length() > 8) { 73 throw new NumberFormatException(); 74 } 75 76 if (value.length() == 3) { // RGB format 77 char[] color = new char[8]; 78 color[0] = color[1] = 'F'; 79 color[2] = color[3] = value.charAt(0); 80 color[4] = color[5] = value.charAt(1); 81 color[6] = color[7] = value.charAt(2); 82 value = new String(color); 83 } else if (value.length() == 4) { // ARGB format 84 char[] color = new char[8]; 85 color[0] = color[1] = value.charAt(0); 86 color[2] = color[3] = value.charAt(1); 87 color[4] = color[5] = value.charAt(2); 88 color[6] = color[7] = value.charAt(3); 89 value = new String(color); 90 } else if (value.length() == 6) { 91 value = "FF" + value; 92 } 93 94 // this is a RRGGBB or AARRGGBB value 95 96 // Integer.parseInt will fail to parse strings like "ff191919", so we use 97 // a Long, but cast the result back into an int, since we know that we're only 98 // dealing with 32 bit values. 99 return (int)Long.parseLong(value, 16); 100 } 101 102 throw new NumberFormatException(); 103 } 104 105 /** 106 * Returns a drawable from the given value. 107 * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable, 108 * or an hexadecimal color 109 * @param context 110 * @param isFramework indicates whether the resource is a framework resources. 111 * Framework resources are cached, and loaded only once. 112 */ 113 public static Drawable getDrawable(IResourceValue value, BridgeContext context, 114 boolean isFramework) { 115 Drawable d = null; 116 117 String stringValue = value.getValue(); 118 119 String lowerCaseValue = stringValue.toLowerCase(); 120 121 if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) { 122 File file = new File(stringValue); 123 if (file.isFile()) { 124 NinePatch ninePatch = Bridge.getCached9Patch(stringValue, 125 isFramework ? null : context.getProjectKey()); 126 127 if (ninePatch == null) { 128 try { 129 ninePatch = NinePatch.load(file.toURL(), false /* convert */); 130 131 Bridge.setCached9Patch(stringValue, ninePatch, 132 isFramework ? null : context.getProjectKey()); 133 } catch (MalformedURLException e) { 134 // URL is wrong, we'll return null below 135 } catch (IOException e) { 136 // failed to read the file, we'll return null below. 137 } 138 } 139 140 if (ninePatch != null) { 141 return new NinePatchDrawable(ninePatch); 142 } 143 } 144 145 return null; 146 } else if (lowerCaseValue.endsWith(".xml")) { 147 // create a blockparser for the file 148 File f = new File(stringValue); 149 if (f.isFile()) { 150 try { 151 // let the framework inflate the Drawable from the XML file. 152 KXmlParser parser = new KXmlParser(); 153 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 154 parser.setInput(new FileReader(f)); 155 156 d = Drawable.createFromXml(context.getResources(), 157 new BridgeXmlBlockParser(parser, context, isFramework)); 158 return d; 159 } catch (XmlPullParserException e) { 160 context.getLogger().error(e); 161 } catch (FileNotFoundException e) { 162 // will not happen, since we pre-check 163 } catch (IOException e) { 164 context.getLogger().error(e); 165 } 166 } 167 168 return null; 169 } else { 170 File bmpFile = new File(stringValue); 171 if (bmpFile.isFile()) { 172 try { 173 Bitmap bitmap = Bridge.getCachedBitmap(stringValue, 174 isFramework ? null : context.getProjectKey()); 175 176 if (bitmap == null) { 177 // always create the cache copy in the original density. 178 bitmap = Bitmap_Delegate.createBitmap(bmpFile, Density.MEDIUM); 179 Bridge.setCachedBitmap(stringValue, bitmap, 180 isFramework ? null : context.getProjectKey()); 181 } 182 183 try { 184 if (value instanceof IDensityBasedResourceValue) { 185 Density density = ((IDensityBasedResourceValue)value).getDensity(); 186 if (density != Density.MEDIUM) { 187 // create a copy of the bitmap 188 bitmap = Bitmap.createBitmap(bitmap); 189 190 // apply the density 191 bitmap.setDensity(density.getValue()); 192 } 193 } 194 } catch (NoClassDefFoundError error) { 195 // look like we're running in an older version of ADT that doesn't include 196 // the new layoutlib_api. Let's just ignore this, the drawing will just be 197 // wrong. 198 } 199 200 return new BitmapDrawable(context.getResources(), bitmap); 201 } catch (IOException e) { 202 // we'll return null below 203 // TODO: log the error. 204 } 205 } else { 206 // attempt to get a color from the value 207 try { 208 int color = getColor(stringValue); 209 return new ColorDrawable(color); 210 } catch (NumberFormatException e) { 211 // we'll return null below. 212 // TODO: log the error 213 } 214 } 215 } 216 217 return null; 218 } 219 220 221 // ------- TypedValue stuff 222 // This is taken from //device/libs/utils/ResourceTypes.cpp 223 224 private static final class UnitEntry { 225 String name; 226 int type; 227 int unit; 228 float scale; 229 230 UnitEntry(String name, int type, int unit, float scale) { 231 this.name = name; 232 this.type = type; 233 this.unit = unit; 234 this.scale = scale; 235 } 236 } 237 238 private final static UnitEntry[] sUnitNames = new UnitEntry[] { 239 new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f), 240 new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), 241 new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), 242 new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f), 243 new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f), 244 new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f), 245 new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f), 246 new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100), 247 new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100), 248 }; 249 250 /** 251 * Returns the raw value from the given string. 252 * This object is only valid until the next call on to {@link ResourceHelper}. 253 */ 254 public static TypedValue getValue(String s) { 255 if (stringToFloat(s, mValue)) { 256 return mValue; 257 } 258 259 return null; 260 } 261 262 /** 263 * Convert the string into a {@link TypedValue}. 264 * @param s 265 * @param outValue 266 * @return true if success. 267 */ 268 public static boolean stringToFloat(String s, TypedValue outValue) { 269 // remove the space before and after 270 s.trim(); 271 int len = s.length(); 272 273 if (len <= 0) { 274 return false; 275 } 276 277 // check that there's no non ascii characters. 278 char[] buf = s.toCharArray(); 279 for (int i = 0 ; i < len ; i++) { 280 if (buf[i] > 255) { 281 return false; 282 } 283 } 284 285 // check the first character 286 if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.') { 287 return false; 288 } 289 290 // now look for the string that is after the float... 291 Matcher m = sFloatPattern.matcher(s); 292 if (m.matches()) { 293 String f_str = m.group(1); 294 String end = m.group(2); 295 296 float f; 297 try { 298 f = Float.parseFloat(f_str); 299 } catch (NumberFormatException e) { 300 // this shouldn't happen with the regexp above. 301 return false; 302 } 303 304 if (end.length() > 0 && end.charAt(0) != ' ') { 305 // Might be a unit... 306 if (parseUnit(end, outValue, sFloatOut)) { 307 308 f *= sFloatOut[0]; 309 boolean neg = f < 0; 310 if (neg) { 311 f = -f; 312 } 313 long bits = (long)(f*(1<<23)+.5f); 314 int radix; 315 int shift; 316 if ((bits&0x7fffff) == 0) { 317 // Always use 23p0 if there is no fraction, just to make 318 // things easier to read. 319 radix = TypedValue.COMPLEX_RADIX_23p0; 320 shift = 23; 321 } else if ((bits&0xffffffffff800000L) == 0) { 322 // Magnitude is zero -- can fit in 0 bits of precision. 323 radix = TypedValue.COMPLEX_RADIX_0p23; 324 shift = 0; 325 } else if ((bits&0xffffffff80000000L) == 0) { 326 // Magnitude can fit in 8 bits of precision. 327 radix = TypedValue.COMPLEX_RADIX_8p15; 328 shift = 8; 329 } else if ((bits&0xffffff8000000000L) == 0) { 330 // Magnitude can fit in 16 bits of precision. 331 radix = TypedValue.COMPLEX_RADIX_16p7; 332 shift = 16; 333 } else { 334 // Magnitude needs entire range, so no fractional part. 335 radix = TypedValue.COMPLEX_RADIX_23p0; 336 shift = 23; 337 } 338 int mantissa = (int)( 339 (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK); 340 if (neg) { 341 mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK; 342 } 343 outValue.data |= 344 (radix<<TypedValue.COMPLEX_RADIX_SHIFT) 345 | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT); 346 return true; 347 } 348 return false; 349 } 350 351 // make sure it's only spaces at the end. 352 end = end.trim(); 353 354 if (end.length() == 0) { 355 if (outValue != null) { 356 outValue.type = TypedValue.TYPE_FLOAT; 357 outValue.data = Float.floatToIntBits(f); 358 return true; 359 } 360 } 361 } 362 363 return false; 364 } 365 366 private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) { 367 str = str.trim(); 368 369 for (UnitEntry unit : sUnitNames) { 370 if (unit.name.equals(str)) { 371 outValue.type = unit.type; 372 outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT; 373 outScale[0] = unit.scale; 374 375 return true; 376 } 377 } 378 379 return false; 380 } 381} 382