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