ResourceHelper.java revision 4ccc4bd54f85d86818f61d728c6361d2003ddd8e
10d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams/*
20d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * Copyright (C) 2008 The Android Open Source Project
30d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams *
40d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * Licensed under the Apache License, Version 2.0 (the "License");
50d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * you may not use this file except in compliance with the License.
60d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * You may obtain a copy of the License at
70d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams *
80d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams *      http://www.apache.org/licenses/LICENSE-2.0
90d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams *
100d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * Unless required by applicable law or agreed to in writing, software
110d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * distributed under the License is distributed on an "AS IS" BASIS,
120d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * See the License for the specific language governing permissions and
140d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * limitations under the License.
150d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams */
160d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
170d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samspackage com.android.layoutlib.bridge.impl;
180d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
190d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.ide.common.rendering.api.DensityBasedResourceValue;
200d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.ide.common.rendering.api.LayoutLog;
210d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.ide.common.rendering.api.RenderResources;
220d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.ide.common.rendering.api.ResourceValue;
230d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.layoutlib.bridge.Bridge;
240d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.layoutlib.bridge.android.BridgeContext;
250d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
260d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.ninepatch.NinePatch;
270d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.ninepatch.NinePatchChunk;
280d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport com.android.resources.Density;
290d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
300d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport org.xmlpull.v1.XmlPullParser;
310d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport org.xmlpull.v1.XmlPullParserException;
320d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
330d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.content.res.ColorStateList;
340d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.graphics.Bitmap;
350d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.graphics.Bitmap_Delegate;
360d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.graphics.NinePatch_Delegate;
370d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.graphics.Rect;
380d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.graphics.drawable.BitmapDrawable;
390d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.graphics.drawable.ColorDrawable;
400d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.graphics.drawable.Drawable;
410d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.graphics.drawable.NinePatchDrawable;
420d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport android.util.TypedValue;
430d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
4403089179a9e664c898fbb0d4ebcca15218a5e9bfMiao Wangimport java.io.File;
4503089179a9e664c898fbb0d4ebcca15218a5e9bfMiao Wangimport java.io.FileInputStream;
460d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport java.io.IOException;
4703089179a9e664c898fbb0d4ebcca15218a5e9bfMiao Wangimport java.io.InputStream;
4803089179a9e664c898fbb0d4ebcca15218a5e9bfMiao Wangimport java.net.MalformedURLException;
490d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport java.util.regex.Matcher;
500d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samsimport java.util.regex.Pattern;
510d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
520d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams/**
530d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams * Helper class to provide various conversion method used in handling android resources.
540d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams */
550d6043caef208ee6c661eb17bcb376abfe90cd48Jason Samspublic final class ResourceHelper {
560d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
570d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams    private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)");
580d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams    private final static float[] sFloatOut = new float[1];
590d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
600d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams    private final static TypedValue mValue = new TypedValue();
610d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
620d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams    /**
630d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams     * Returns the color value represented by the given string value
640d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams     * @param value the color value
650d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams     * @return the color as an int
660d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams     * @throw NumberFormatException if the conversion failed.
670d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams     */
680ef64c5373a119eb73cbf7b1f7cf7d1da12d97d3Jason Sams    public static int getColor(String value) {
690d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams        if (value != null) {
700d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams            if (value.startsWith("#") == false) {
710d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams                throw new NumberFormatException(
720d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams                        String.format("Color value '%s' must start with #", value));
730d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams            }
740ef64c5373a119eb73cbf7b1f7cf7d1da12d97d3Jason Sams
750d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams            value = value.substring(1);
760d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
770d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams            // make sure it's not longer than 32bit
780d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams            if (value.length() > 8) {
790d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams                throw new NumberFormatException(String.format(
800ef64c5373a119eb73cbf7b1f7cf7d1da12d97d3Jason Sams                        "Color value '%s' is too long. Format is either" +
810d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams                        "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
820d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams                        value));
830d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams            }
840d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams
850d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams            if (value.length() == 3) { // RGB format
860ef64c5373a119eb73cbf7b1f7cf7d1da12d97d3Jason Sams                char[] color = new char[8];
870d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams                color[0] = color[1] = 'F';
880ef64c5373a119eb73cbf7b1f7cf7d1da12d97d3Jason Sams                color[2] = color[3] = value.charAt(0);
89ef05d4666eb87a924c8883e193fd505245101414Miao Wang                color[4] = color[5] = value.charAt(1);
900d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams                color[6] = color[7] = value.charAt(2);
910d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams                value = new String(color);
920d6043caef208ee6c661eb17bcb376abfe90cd48Jason Sams            } else if (value.length() == 4) { // ARGB format
93                char[] color = new char[8];
94                color[0] = color[1] = value.charAt(0);
95                color[2] = color[3] = value.charAt(1);
96                color[4] = color[5] = value.charAt(2);
97                color[6] = color[7] = value.charAt(3);
98                value = new String(color);
99            } else if (value.length() == 6) {
100                value = "FF" + value;
101            }
102
103            // this is a RRGGBB or AARRGGBB value
104
105            // Integer.parseInt will fail to parse strings like "ff191919", so we use
106            // a Long, but cast the result back into an int, since we know that we're only
107            // dealing with 32 bit values.
108            return (int)Long.parseLong(value, 16);
109        }
110
111        throw new NumberFormatException();
112    }
113
114    public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) {
115        String value = resValue.getValue();
116        if (value != null && RenderResources.REFERENCE_NULL.equals(value) == false) {
117            // first check if the value is a file (xml most likely)
118            File f = new File(value);
119            if (f.isFile()) {
120                try {
121                    // let the framework inflate the ColorStateList from the XML file, by
122                    // providing an XmlPullParser
123                    XmlPullParser parser = ParserFactory.create(f);
124
125                    BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
126                            parser, context, resValue.isFramework());
127                    try {
128                        return ColorStateList.createFromXml(context.getResources(), blockParser);
129                    } finally {
130                        blockParser.ensurePopped();
131                    }
132                } catch (XmlPullParserException e) {
133                    Bridge.getLog().error(LayoutLog.TAG_BROKEN,
134                            "Failed to configure parser for " + value, e, null /*data*/);
135                    // we'll return null below.
136                } catch (Exception e) {
137                    // this is an error and not warning since the file existence is
138                    // checked before attempting to parse it.
139                    Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
140                            "Failed to parse file " + value, e, null /*data*/);
141
142                    return null;
143                }
144            } else {
145                // try to load the color state list from an int
146                try {
147                    int color = ResourceHelper.getColor(value);
148                    return ColorStateList.valueOf(color);
149                } catch (NumberFormatException e) {
150                    Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
151                            "Failed to convert " + value + " into a ColorStateList", e,
152                            null /*data*/);
153                    return null;
154                }
155            }
156        }
157
158        return null;
159    }
160
161    /**
162     * Returns a drawable from the given value.
163     * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
164     * or an hexadecimal color
165     * @param context the current context
166     */
167    public static Drawable getDrawable(ResourceValue value, BridgeContext context) {
168        if (value == null) {
169            return null;
170        }
171        String stringValue = value.getValue();
172        if (RenderResources.REFERENCE_NULL.equals(stringValue)) {
173            return null;
174        }
175
176        String lowerCaseValue = stringValue.toLowerCase();
177
178        Density density = Density.MEDIUM;
179        if (value instanceof DensityBasedResourceValue) {
180            density =
181                ((DensityBasedResourceValue)value).getResourceDensity();
182        }
183
184
185        if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) {
186            File file = new File(stringValue);
187            if (file.isFile()) {
188                try {
189                    return getNinePatchDrawable(
190                            new FileInputStream(file), density, value.isFramework(),
191                            stringValue, context);
192                } catch (IOException e) {
193                    // failed to read the file, we'll return null below.
194                    Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
195                            "Failed lot load " + file.getAbsolutePath(), e, null /*data*/);
196                }
197            }
198
199            return null;
200        } else if (lowerCaseValue.endsWith(".xml")) {
201            // create a block parser for the file
202            File f = new File(stringValue);
203            if (f.isFile()) {
204                try {
205                    // let the framework inflate the Drawable from the XML file.
206                    XmlPullParser parser = ParserFactory.create(f);
207
208                    BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
209                            parser, context, value.isFramework());
210                    try {
211                        return Drawable.createFromXml(context.getResources(), blockParser);
212                    } finally {
213                        blockParser.ensurePopped();
214                    }
215                } catch (Exception e) {
216                    // this is an error and not warning since the file existence is checked before
217                    // attempting to parse it.
218                    Bridge.getLog().error(null, "Failed to parse file " + stringValue,
219                            e, null /*data*/);
220                }
221            } else {
222                Bridge.getLog().error(LayoutLog.TAG_BROKEN,
223                        String.format("File %s does not exist (or is not a file)", stringValue),
224                        null /*data*/);
225            }
226
227            return null;
228        } else {
229            File bmpFile = new File(stringValue);
230            if (bmpFile.isFile()) {
231                try {
232                    Bitmap bitmap = Bridge.getCachedBitmap(stringValue,
233                            value.isFramework() ? null : context.getProjectKey());
234
235                    if (bitmap == null) {
236                        bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/,
237                                density);
238                        Bridge.setCachedBitmap(stringValue, bitmap,
239                                value.isFramework() ? null : context.getProjectKey());
240                    }
241
242                    return new BitmapDrawable(context.getResources(), bitmap);
243                } catch (IOException e) {
244                    // we'll return null below
245                    Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
246                            "Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/);
247                }
248            } else {
249                // attempt to get a color from the value
250                try {
251                    int color = getColor(stringValue);
252                    return new ColorDrawable(color);
253                } catch (NumberFormatException e) {
254                    // we'll return null below.
255                    Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
256                            "Failed to convert " + stringValue + " into a drawable", e,
257                            null /*data*/);
258                }
259            }
260        }
261
262        return null;
263    }
264
265    private static Drawable getNinePatchDrawable(InputStream inputStream, Density density,
266            boolean isFramework, String cacheKey, BridgeContext context) throws IOException {
267        // see if we still have both the chunk and the bitmap in the caches
268        NinePatchChunk chunk = Bridge.getCached9Patch(cacheKey,
269                isFramework ? null : context.getProjectKey());
270        Bitmap bitmap = Bridge.getCachedBitmap(cacheKey,
271                isFramework ? null : context.getProjectKey());
272
273        // if either chunk or bitmap is null, then we reload the 9-patch file.
274        if (chunk == null || bitmap == null) {
275            try {
276                NinePatch ninePatch = NinePatch.load(inputStream, true /*is9Patch*/,
277                        false /* convert */);
278                if (ninePatch != null) {
279                    if (chunk == null) {
280                        chunk = ninePatch.getChunk();
281
282                        Bridge.setCached9Patch(cacheKey, chunk,
283                                isFramework ? null : context.getProjectKey());
284                    }
285
286                    if (bitmap == null) {
287                        bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(),
288                                false /*isMutable*/,
289                                density);
290
291                        Bridge.setCachedBitmap(cacheKey, bitmap,
292                                isFramework ? null : context.getProjectKey());
293                    }
294                }
295            } catch (MalformedURLException e) {
296                // URL is wrong, we'll return null below
297            }
298        }
299
300        if (chunk != null && bitmap != null) {
301            int[] padding = chunk.getPadding();
302            Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]);
303
304            return new NinePatchDrawable(context.getResources(), bitmap,
305                    NinePatch_Delegate.serialize(chunk),
306                    paddingRect, null);
307        }
308
309        return null;
310    }
311
312    // ------- TypedValue stuff
313    // This is taken from //device/libs/utils/ResourceTypes.cpp
314
315    private static final class UnitEntry {
316        String name;
317        int type;
318        int unit;
319        float scale;
320
321        UnitEntry(String name, int type, int unit, float scale) {
322            this.name = name;
323            this.type = type;
324            this.unit = unit;
325            this.scale = scale;
326        }
327    }
328
329    private final static UnitEntry[] sUnitNames = new UnitEntry[] {
330        new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
331        new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
332        new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
333        new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f),
334        new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f),
335        new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f),
336        new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f),
337        new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100),
338        new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100),
339    };
340
341    /**
342     * Returns the raw value from the given attribute float-type value string.
343     * This object is only valid until the next call on to {@link ResourceHelper}.
344     */
345    public static TypedValue getValue(String attribute, String value, boolean requireUnit) {
346        if (parseFloatAttribute(attribute, value, mValue, requireUnit)) {
347            return mValue;
348        }
349
350        return null;
351    }
352
353    /**
354     * Parse a float attribute and return the parsed value into a given TypedValue.
355     * @param attribute the name of the attribute. Can be null if <var>requireUnit</var> is false.
356     * @param value the string value of the attribute
357     * @param outValue the TypedValue to receive the parsed value
358     * @param requireUnit whether the value is expected to contain a unit.
359     * @return true if success.
360     */
361    public static boolean parseFloatAttribute(String attribute, String value,
362            TypedValue outValue, boolean requireUnit) {
363        assert requireUnit == false || attribute != null;
364
365        // remove the space before and after
366        value = value.trim();
367        int len = value.length();
368
369        if (len <= 0) {
370            return false;
371        }
372
373        // check that there's no non ascii characters.
374        char[] buf = value.toCharArray();
375        for (int i = 0 ; i < len ; i++) {
376            if (buf[i] > 255) {
377                return false;
378            }
379        }
380
381        // check the first character
382        if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.' && buf[0] != '-') {
383            return false;
384        }
385
386        // now look for the string that is after the float...
387        Matcher m = sFloatPattern.matcher(value);
388        if (m.matches()) {
389            String f_str = m.group(1);
390            String end = m.group(2);
391
392            float f;
393            try {
394                f = Float.parseFloat(f_str);
395            } catch (NumberFormatException e) {
396                // this shouldn't happen with the regexp above.
397                return false;
398            }
399
400            if (end.length() > 0 && end.charAt(0) != ' ') {
401                // Might be a unit...
402                if (parseUnit(end, outValue, sFloatOut)) {
403                    computeTypedValue(outValue, f, sFloatOut[0]);
404                    return true;
405                }
406                return false;
407            }
408
409            // make sure it's only spaces at the end.
410            end = end.trim();
411
412            if (end.length() == 0) {
413                if (outValue != null) {
414                    if (requireUnit == false) {
415                        outValue.type = TypedValue.TYPE_FLOAT;
416                        outValue.data = Float.floatToIntBits(f);
417                    } else {
418                        // no unit when required? Use dp and out an error.
419                        applyUnit(sUnitNames[1], outValue, sFloatOut);
420                        computeTypedValue(outValue, f, sFloatOut[0]);
421
422                        Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
423                                String.format(
424                                        "Dimension \"%1$s\" in attribute \"%2$s\" is missing unit!",
425                                        value, attribute),
426                                null);
427                    }
428                    return true;
429                }
430            }
431        }
432
433        return false;
434    }
435
436    private static void computeTypedValue(TypedValue outValue, float value, float scale) {
437        value *= scale;
438        boolean neg = value < 0;
439        if (neg) {
440            value = -value;
441        }
442        long bits = (long)(value*(1<<23)+.5f);
443        int radix;
444        int shift;
445        if ((bits&0x7fffff) == 0) {
446            // Always use 23p0 if there is no fraction, just to make
447            // things easier to read.
448            radix = TypedValue.COMPLEX_RADIX_23p0;
449            shift = 23;
450        } else if ((bits&0xffffffffff800000L) == 0) {
451            // Magnitude is zero -- can fit in 0 bits of precision.
452            radix = TypedValue.COMPLEX_RADIX_0p23;
453            shift = 0;
454        } else if ((bits&0xffffffff80000000L) == 0) {
455            // Magnitude can fit in 8 bits of precision.
456            radix = TypedValue.COMPLEX_RADIX_8p15;
457            shift = 8;
458        } else if ((bits&0xffffff8000000000L) == 0) {
459            // Magnitude can fit in 16 bits of precision.
460            radix = TypedValue.COMPLEX_RADIX_16p7;
461            shift = 16;
462        } else {
463            // Magnitude needs entire range, so no fractional part.
464            radix = TypedValue.COMPLEX_RADIX_23p0;
465            shift = 23;
466        }
467        int mantissa = (int)(
468            (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK);
469        if (neg) {
470            mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK;
471        }
472        outValue.data |=
473            (radix<<TypedValue.COMPLEX_RADIX_SHIFT)
474            | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT);
475    }
476
477    private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) {
478        str = str.trim();
479
480        for (UnitEntry unit : sUnitNames) {
481            if (unit.name.equals(str)) {
482                applyUnit(unit, outValue, outScale);
483                return true;
484            }
485        }
486
487        return false;
488    }
489
490    private static void applyUnit(UnitEntry unit, TypedValue outValue, float[] outScale) {
491        outValue.type = unit.type;
492        outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT;
493        outScale[0] = unit.scale;
494    }
495}
496
497