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