| /* |
| * Copyright (C) 2016 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 android.content.res; |
| |
| import com.android.SdkConstants; |
| import com.android.ide.common.rendering.api.ArrayResourceValue; |
| import com.android.ide.common.rendering.api.AssetRepository; |
| import com.android.ide.common.rendering.api.DensityBasedResourceValue; |
| import com.android.ide.common.rendering.api.LayoutLog; |
| import com.android.ide.common.rendering.api.LayoutlibCallback; |
| import com.android.ide.common.rendering.api.PluralsResourceValue; |
| import com.android.ide.common.rendering.api.RenderResources; |
| import com.android.ide.common.rendering.api.ResourceNamespace; |
| import com.android.ide.common.rendering.api.ResourceNamespace.Resolver; |
| import com.android.ide.common.rendering.api.ResourceReference; |
| import com.android.ide.common.rendering.api.ResourceValue; |
| import com.android.ide.common.rendering.api.ResourceValueImpl; |
| import com.android.layoutlib.bridge.Bridge; |
| import com.android.layoutlib.bridge.BridgeConstants; |
| import com.android.layoutlib.bridge.android.BridgeContext; |
| import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; |
| import com.android.layoutlib.bridge.android.UnresolvedResourceValue; |
| import com.android.layoutlib.bridge.impl.ParserFactory; |
| import com.android.layoutlib.bridge.impl.ResourceHelper; |
| import com.android.layoutlib.bridge.util.NinePatchInputStream; |
| import com.android.ninepatch.NinePatch; |
| import com.android.resources.ResourceType; |
| import com.android.resources.ResourceUrl; |
| import com.android.tools.layoutlib.annotations.LayoutlibDelegate; |
| import com.android.tools.layoutlib.annotations.VisibleForTesting; |
| import com.android.util.Pair; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.res.Resources.NotFoundException; |
| import android.content.res.Resources.Theme; |
| import android.graphics.Color; |
| import android.graphics.Typeface; |
| import android.graphics.drawable.Drawable; |
| import android.icu.text.PluralRules; |
| import android.util.AttributeSet; |
| import android.util.DisplayMetrics; |
| import android.util.LruCache; |
| import android.util.TypedValue; |
| import android.view.DisplayAdjustments; |
| import android.view.ViewGroup.LayoutParams; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Objects; |
| import java.util.WeakHashMap; |
| |
| import static android.content.res.AssetManager.ACCESS_STREAMING; |
| import static com.android.SdkConstants.ANDROID_PKG; |
| import static com.android.SdkConstants.PREFIX_RESOURCE_REF; |
| |
| @SuppressWarnings("deprecation") |
| public class Resources_Delegate { |
| private static WeakHashMap<Resources, LayoutlibCallback> sLayoutlibCallbacks = |
| new WeakHashMap<>(); |
| private static WeakHashMap<Resources, BridgeContext> sContexts = new WeakHashMap<>(); |
| |
| // TODO: This cache is cleared every time a render session is disposed. Look into making this |
| // more long lived. |
| private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50); |
| |
| public static Resources initSystem(@NonNull BridgeContext context, |
| @NonNull AssetManager assets, |
| @NonNull DisplayMetrics metrics, |
| @NonNull Configuration config, |
| @NonNull LayoutlibCallback layoutlibCallback) { |
| assert Resources.mSystem == null : |
| "Resources_Delegate.initSystem called twice before disposeSystem was called"; |
| Resources resources = new Resources(Resources_Delegate.class.getClassLoader()); |
| resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments())); |
| sContexts.put(resources, Objects.requireNonNull(context)); |
| sLayoutlibCallbacks.put(resources, Objects.requireNonNull(layoutlibCallback)); |
| return Resources.mSystem = resources; |
| } |
| |
| /** Returns the {@link BridgeContext} associated to the given {@link Resources} */ |
| @VisibleForTesting |
| @NonNull |
| public static BridgeContext getContext(@NonNull Resources resources) { |
| assert sContexts.containsKey(resources) : |
| "Resources_Delegate.getContext called before initSystem"; |
| return sContexts.get(resources); |
| } |
| |
| /** Returns the {@link LayoutlibCallback} associated to the given {@link Resources} */ |
| @VisibleForTesting |
| @NonNull |
| public static LayoutlibCallback getLayoutlibCallback(@NonNull Resources resources) { |
| assert sLayoutlibCallbacks.containsKey(resources) : |
| "Resources_Delegate.getLayoutlibCallback called before initSystem"; |
| return sLayoutlibCallbacks.get(resources); |
| } |
| |
| /** |
| * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that |
| * would prevent us from unloading the library. |
| */ |
| public static void disposeSystem() { |
| sDrawableCache.evictAll(); |
| sContexts.clear(); |
| sLayoutlibCallbacks.clear(); |
| Resources.mSystem = null; |
| } |
| |
| public static BridgeTypedArray newTypeArray(Resources resources, int numEntries) { |
| return new BridgeTypedArray(resources, getContext(resources), numEntries); |
| } |
| |
| private static ResourceReference getResourceInfo(Resources resources, int id) { |
| // first get the String related to this id in the framework |
| ResourceReference resourceInfo = Bridge.resolveResourceId(id); |
| |
| assert Resources.mSystem != null : "Resources_Delegate.initSystem wasn't called"; |
| // Set the layoutlib callback and context for resources |
| if (resources != Resources.mSystem && |
| (!sContexts.containsKey(resources) || !sLayoutlibCallbacks.containsKey(resources))) { |
| sLayoutlibCallbacks.put(resources, getLayoutlibCallback(Resources.mSystem)); |
| sContexts.put(resources, getContext(Resources.mSystem)); |
| } |
| |
| if (resourceInfo == null) { |
| // Didn't find a match in the framework? Look in the project. |
| resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id); |
| } |
| |
| return resourceInfo; |
| } |
| |
| private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id) { |
| ResourceReference resourceInfo = getResourceInfo(resources, id); |
| |
| if (resourceInfo != null) { |
| String attributeName = resourceInfo.getName(); |
| RenderResources renderResources = getContext(resources).getRenderResources(); |
| ResourceValue value = renderResources.getResolvedResource(resourceInfo); |
| if (value == null) { |
| // Unable to resolve the attribute, just leave the unresolved value. |
| value = new ResourceValueImpl(resourceInfo.getNamespace(), |
| resourceInfo.getResourceType(), attributeName, attributeName); |
| } |
| return Pair.of(attributeName, value); |
| } |
| |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static Drawable getDrawable(Resources resources, int id) { |
| return getDrawable(resources, id, null); |
| } |
| |
| @LayoutlibDelegate |
| static Drawable getDrawable(Resources resources, int id, Theme theme) { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| if (value != null) { |
| String key = value.getSecond().getValue(); |
| |
| Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null; |
| Drawable drawable; |
| if (constantState != null) { |
| drawable = constantState.newDrawable(resources, theme); |
| } else { |
| drawable = |
| ResourceHelper.getDrawable(value.getSecond(), getContext(resources), theme); |
| |
| if (key != null) { |
| sDrawableCache.put(key, drawable.getConstantState()); |
| } |
| } |
| |
| return drawable; |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static int getColor(Resources resources, int id) { |
| return getColor(resources, id, null); |
| } |
| |
| @LayoutlibDelegate |
| static int getColor(Resources resources, int id, Theme theme) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resourceValue = value.getSecond(); |
| try { |
| return ResourceHelper.getColor(resourceValue.getValue()); |
| } catch (NumberFormatException e) { |
| // Check if the value passed is a file. If it is, mostly likely, user is referencing |
| // a color state list from a place where they should reference only a pure color. |
| AssetRepository repository = getAssetRepository(resources); |
| String message; |
| if (repository.isFileResource(resourceValue.getValue())) { |
| String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/" |
| + resourceValue.getName(); |
| message = "Hexadecimal color expected, found Color State List for " + resource; |
| } else { |
| message = e.getMessage(); |
| } |
| Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, message, e, null); |
| return 0; |
| } |
| } |
| |
| // Suppress possible NPE. getColorStateList will never return null, it will instead |
| // throw an exception, but intelliJ can't figure that out |
| //noinspection ConstantConditions |
| return getColorStateList(resources, id, theme).getDefaultColor(); |
| } |
| |
| @LayoutlibDelegate |
| static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException { |
| return getColorStateList(resources, id, null); |
| } |
| |
| @LayoutlibDelegate |
| static ColorStateList getColorStateList(Resources resources, int id, Theme theme) |
| throws NotFoundException { |
| Pair<String, ResourceValue> resValue = getResourceValue(resources, id); |
| |
| if (resValue != null) { |
| ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(), |
| getContext(resources), theme); |
| if (stateList != null) { |
| return stateList; |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static CharSequence getText(Resources resources, int id, CharSequence def) { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resValue = value.getSecond(); |
| |
| assert resValue != null; |
| if (resValue != null) { |
| String v = resValue.getValue(); |
| if (v != null) { |
| return v; |
| } |
| } |
| } |
| |
| return def; |
| } |
| |
| @LayoutlibDelegate |
| static CharSequence getText(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resValue = value.getSecond(); |
| |
| assert resValue != null; |
| if (resValue != null) { |
| String v = resValue.getValue(); |
| if (v != null) { |
| return v; |
| } |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static CharSequence[] getTextArray(Resources resources, int id) throws NotFoundException { |
| ResourceValue resValue = getArrayResourceValue(resources, id); |
| if (resValue == null) { |
| // Error already logged by getArrayResourceValue. |
| return new CharSequence[0]; |
| } |
| if (resValue instanceof ArrayResourceValue) { |
| ArrayResourceValue arrayValue = (ArrayResourceValue) resValue; |
| return resolveValues(resources, arrayValue); |
| } |
| RenderResources renderResources = getContext(resources).getRenderResources(); |
| return new CharSequence[] { renderResources.resolveResValue(resValue).getValue() }; |
| } |
| |
| @LayoutlibDelegate |
| static String[] getStringArray(Resources resources, int id) throws NotFoundException { |
| ResourceValue resValue = getArrayResourceValue(resources, id); |
| if (resValue == null) { |
| // Error already logged by getArrayResourceValue. |
| return new String[0]; |
| } |
| if (resValue instanceof ArrayResourceValue) { |
| ArrayResourceValue arv = (ArrayResourceValue) resValue; |
| return resolveValues(resources, arv); |
| } |
| return new String[] { resolveReference(resources, resValue) }; |
| } |
| |
| /** |
| * Resolves each element in resValue and returns an array of resolved values. The returned array |
| * may contain nulls. |
| */ |
| @NonNull |
| static String[] resolveValues(@NonNull Resources resources, |
| @NonNull ArrayResourceValue resValue) { |
| String[] result = new String[resValue.getElementCount()]; |
| for (int i = 0; i < resValue.getElementCount(); i++) { |
| String value = resValue.getElement(i); |
| result[i] = resolveReference(resources, value, |
| resValue.getNamespace(), resValue.getNamespaceResolver()); |
| } |
| return result; |
| } |
| |
| @LayoutlibDelegate |
| static int[] getIntArray(Resources resources, int id) throws NotFoundException { |
| ResourceValue rv = getArrayResourceValue(resources, id); |
| if (rv == null) { |
| // Error already logged by getArrayResourceValue. |
| return new int[0]; |
| } |
| if (rv instanceof ArrayResourceValue) { |
| ArrayResourceValue resValue = (ArrayResourceValue) rv; |
| int n = resValue.getElementCount(); |
| int[] values = new int[n]; |
| for (int i = 0; i < n; i++) { |
| String element = resolveReference(resources, resValue.getElement(i), |
| resValue.getNamespace(), resValue.getNamespaceResolver()); |
| if (element != null) { |
| try { |
| if (element.startsWith("#")) { |
| // This integer represents a color (starts with #). |
| values[i] = Color.parseColor(element); |
| } else { |
| values[i] = getInt(element); |
| } |
| } catch (NumberFormatException e) { |
| Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, |
| "Integer resource array contains non-integer value: \"" + element + |
| "\"", null); |
| } catch (IllegalArgumentException e) { |
| Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, |
| "Integer resource array contains wrong color format: \"" + element + |
| "\"", null); |
| } |
| } else { |
| Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, |
| "Integer resource array contains non-integer value: \"" + |
| resValue.getElement(i) + "\"", null); |
| } |
| } |
| return values; |
| } |
| |
| // This is an older IDE that can only give us the first element of the array. |
| String firstValue = resolveReference(resources, rv); |
| if (firstValue != null) { |
| try { |
| return new int[]{getInt(firstValue)}; |
| } catch (NumberFormatException e) { |
| Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, |
| "Integer resource array contains non-integer value: \"" + firstValue + "\"", |
| null); |
| return new int[1]; |
| } |
| } else { |
| Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, |
| "Integer resource array contains non-integer value: \"" + |
| rv.getValue() + "\"", null); |
| return new int[1]; |
| } |
| } |
| |
| /** |
| * Try to find the ArrayResourceValue for the given id. |
| * <p/> |
| * If the ResourceValue found is not of type {@link ResourceType#ARRAY}, the method logs an |
| * error and return null. However, if the ResourceValue found has type {@code |
| * ResourceType.ARRAY}, but the value is not an instance of {@link ArrayResourceValue}, the |
| * method returns the ResourceValue. This happens on older versions of the IDE, which did not |
| * parse the array resources properly. |
| * <p/> |
| * |
| * @throws NotFoundException if no resource if found |
| */ |
| @Nullable |
| private static ResourceValue getArrayResourceValue(Resources resources, int id) |
| throws NotFoundException { |
| Pair<String, ResourceValue> v = getResourceValue(resources, id); |
| |
| if (v != null) { |
| ResourceValue resValue = v.getSecond(); |
| |
| assert resValue != null; |
| if (resValue != null) { |
| final ResourceType type = resValue.getResourceType(); |
| if (type != ResourceType.ARRAY) { |
| Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE, |
| String.format( |
| "Resource with id 0x%1$X is not an array resource, but %2$s", |
| id, type == null ? "null" : type.getDisplayName()), |
| null); |
| return null; |
| } |
| if (!(resValue instanceof ArrayResourceValue)) { |
| Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, |
| "Obtaining resource arrays via getTextArray, getStringArray or getIntArray is not fully supported in this version of the IDE.", |
| null); |
| } |
| return resValue; |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @Nullable |
| private static String resolveReference(@NonNull Resources resources, @Nullable String value, |
| @NonNull ResourceNamespace contextNamespace, |
| @NonNull ResourceNamespace.Resolver resolver) { |
| if (value != null) { |
| ResourceValue resValue = new UnresolvedResourceValue(value, contextNamespace, resolver); |
| return resolveReference(resources, resValue); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static String resolveReference(@NonNull Resources resources, |
| @NonNull ResourceValue value) { |
| RenderResources renderResources = getContext(resources).getRenderResources(); |
| ResourceValue resolvedValue = renderResources.resolveResValue(value); |
| return resolvedValue == null ? null : resolvedValue.getValue(); |
| } |
| |
| @LayoutlibDelegate |
| static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> v = getResourceValue(resources, id); |
| |
| if (v != null) { |
| ResourceValue value = v.getSecond(); |
| |
| try { |
| BridgeXmlBlockParser parser = |
| ResourceHelper.getXmlBlockParser(getContext(resources), value); |
| if (parser != null) { |
| return parser; |
| } |
| } catch (XmlPullParserException e) { |
| Bridge.getLog().error(LayoutLog.TAG_BROKEN, |
| "Failed to parse " + value.getValue(), e, null /*data*/); |
| // we'll return null below. |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id, "layout"); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> v = getResourceValue(resources, id); |
| |
| if (v != null) { |
| ResourceValue value = v.getSecond(); |
| |
| try { |
| return ResourceHelper.getXmlBlockParser(getContext(resources), value); |
| } catch (XmlPullParserException e) { |
| Bridge.getLog().error(LayoutLog.TAG_BROKEN, |
| "Failed to parse " + value.getValue(), e, null /*data*/); |
| // we'll return null below. |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) { |
| return getContext(resources).obtainStyledAttributes(set, attrs); |
| } |
| |
| @LayoutlibDelegate |
| static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet |
| set, int[] attrs) { |
| return Resources.obtainAttributes_Original(resources, theme, set, attrs); |
| } |
| |
| @LayoutlibDelegate |
| static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException { |
| BridgeContext context = getContext(resources); |
| ResourceReference reference = context.resolveId(id); |
| RenderResources renderResources = context.getRenderResources(); |
| ResourceValue value = renderResources.getResolvedResource(reference); |
| |
| if (!(value instanceof ArrayResourceValue)) { |
| throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id)); |
| } |
| |
| ArrayResourceValue arrayValue = (ArrayResourceValue) value; |
| int length = arrayValue.getElementCount(); |
| ResourceNamespace namespace = arrayValue.getNamespace(); |
| BridgeTypedArray typedArray = newTypeArray(resources, length); |
| |
| for (int i = 0; i < length; i++) { |
| ResourceValue elementValue; |
| ResourceUrl resourceUrl = ResourceUrl.parse(arrayValue.getElement(i)); |
| if (resourceUrl != null) { |
| ResourceReference elementRef = |
| resourceUrl.resolve(namespace, arrayValue.getNamespaceResolver()); |
| elementValue = renderResources.getResolvedResource(elementRef); |
| } else { |
| elementValue = new ResourceValueImpl(namespace, ResourceType.STRING, "element" + i, |
| arrayValue.getElement(i)); |
| } |
| typedArray.bridgeSetValue(i, elementValue.getName(), namespace, i, elementValue); |
| } |
| |
| typedArray.sealArray(); |
| return typedArray; |
| } |
| |
| @LayoutlibDelegate |
| static float getDimension(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resValue = value.getSecond(); |
| |
| assert resValue != null; |
| if (resValue != null) { |
| String v = resValue.getValue(); |
| if (v != null) { |
| if (v.equals(BridgeConstants.MATCH_PARENT) || |
| v.equals(BridgeConstants.FILL_PARENT)) { |
| return LayoutParams.MATCH_PARENT; |
| } else if (v.equals(BridgeConstants.WRAP_CONTENT)) { |
| return LayoutParams.WRAP_CONTENT; |
| } |
| TypedValue tmpValue = new TypedValue(); |
| if (ResourceHelper.parseFloatAttribute( |
| value.getFirst(), v, tmpValue, true /*requireUnit*/) && |
| tmpValue.type == TypedValue.TYPE_DIMENSION) { |
| return tmpValue.getDimension(resources.getDisplayMetrics()); |
| } |
| } |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return 0; |
| } |
| |
| @LayoutlibDelegate |
| static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resValue = value.getSecond(); |
| |
| assert resValue != null; |
| if (resValue != null) { |
| String v = resValue.getValue(); |
| if (v != null) { |
| TypedValue tmpValue = new TypedValue(); |
| if (ResourceHelper.parseFloatAttribute( |
| value.getFirst(), v, tmpValue, true /*requireUnit*/) && |
| tmpValue.type == TypedValue.TYPE_DIMENSION) { |
| return TypedValue.complexToDimensionPixelOffset(tmpValue.data, |
| resources.getDisplayMetrics()); |
| } |
| } |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return 0; |
| } |
| |
| @LayoutlibDelegate |
| static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resValue = value.getSecond(); |
| |
| assert resValue != null; |
| if (resValue != null) { |
| String v = resValue.getValue(); |
| if (v != null) { |
| TypedValue tmpValue = new TypedValue(); |
| if (ResourceHelper.parseFloatAttribute( |
| value.getFirst(), v, tmpValue, true /*requireUnit*/) && |
| tmpValue.type == TypedValue.TYPE_DIMENSION) { |
| return TypedValue.complexToDimensionPixelSize(tmpValue.data, |
| resources.getDisplayMetrics()); |
| } |
| } |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return 0; |
| } |
| |
| @LayoutlibDelegate |
| static int getInteger(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resValue = value.getSecond(); |
| |
| assert resValue != null; |
| if (resValue != null) { |
| String v = resValue.getValue(); |
| if (v != null) { |
| try { |
| return getInt(v); |
| } catch (NumberFormatException e) { |
| // return exception below |
| } |
| } |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return 0; |
| } |
| |
| @LayoutlibDelegate |
| static float getFloat(Resources resources, int id) { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resValue = value.getSecond(); |
| |
| if (resValue != null) { |
| String v = resValue.getValue(); |
| if (v != null) { |
| try { |
| return Float.parseFloat(v); |
| } catch (NumberFormatException ignore) { |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| @LayoutlibDelegate |
| static boolean getBoolean(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resValue = value.getSecond(); |
| |
| if (resValue != null) { |
| String v = resValue.getValue(); |
| if (v != null) { |
| return Boolean.parseBoolean(v); |
| } |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return false; |
| } |
| |
| @LayoutlibDelegate |
| static String getResourceEntryName(Resources resources, int resid) throws NotFoundException { |
| ResourceReference resourceInfo = getResourceInfo(resources, resid); |
| if (resourceInfo != null) { |
| return resourceInfo.getName(); |
| } |
| throwException(resid, null); |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static String getResourceName(Resources resources, int resid) throws NotFoundException { |
| ResourceReference resourceInfo = getResourceInfo(resources, resid); |
| if (resourceInfo != null) { |
| String packageName = getPackageName(resourceInfo, resources); |
| return packageName + ':' + resourceInfo.getResourceType().getName() + '/' + |
| resourceInfo.getName(); |
| } |
| throwException(resid, null); |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static String getResourcePackageName(Resources resources, int resid) throws NotFoundException { |
| ResourceReference resourceInfo = getResourceInfo(resources, resid); |
| if (resourceInfo != null) { |
| return getPackageName(resourceInfo, resources); |
| } |
| throwException(resid, null); |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static String getResourceTypeName(Resources resources, int resid) throws NotFoundException { |
| ResourceReference resourceInfo = getResourceInfo(resources, resid); |
| if (resourceInfo != null) { |
| return resourceInfo.getResourceType().getName(); |
| } |
| throwException(resid, null); |
| return null; |
| } |
| |
| private static String getPackageName(ResourceReference resourceInfo, Resources resources) { |
| String packageName = resourceInfo.getNamespace().getPackageName(); |
| if (packageName == null) { |
| packageName = getContext(resources).getPackageName(); |
| if (packageName == null) { |
| packageName = SdkConstants.APP_PREFIX; |
| } |
| } |
| return packageName; |
| } |
| |
| @LayoutlibDelegate |
| static String getString(Resources resources, int id, Object... formatArgs) |
| throws NotFoundException { |
| String s = getString(resources, id); |
| if (s != null) { |
| return String.format(s, formatArgs); |
| |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static String getString(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null && value.getSecond().getValue() != null) { |
| return value.getSecond().getValue(); |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static String getQuantityString(Resources resources, int id, int quantity) throws |
| NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| if (value.getSecond() instanceof PluralsResourceValue) { |
| PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond(); |
| PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales() |
| .get(0)); |
| String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity)); |
| if (strValue == null) { |
| strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER); |
| } |
| |
| return strValue; |
| } |
| else { |
| return value.getSecond().getValue(); |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs) |
| throws NotFoundException { |
| String raw = getQuantityString(resources, id, quantity); |
| return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs); |
| } |
| |
| @LayoutlibDelegate |
| static CharSequence getQuantityText(Resources resources, int id, int quantity) throws |
| NotFoundException { |
| return getQuantityString(resources, id, quantity); |
| } |
| |
| @LayoutlibDelegate |
| static Typeface getFont(Resources resources, int id) throws |
| NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| if (value != null) { |
| return ResourceHelper.getFont(value.getSecond(), getContext(resources), null); |
| } |
| |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static Typeface getFont(Resources resources, TypedValue outValue, int id) throws |
| NotFoundException { |
| ResourceValue resVal = getResourceValue(resources, id, outValue); |
| if (resVal != null) { |
| return ResourceHelper.getFont(resVal, getContext(resources), null); |
| } |
| |
| throwException(resources, id); |
| return null; // This is not used since the method above always throws. |
| } |
| |
| @LayoutlibDelegate |
| static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs) |
| throws NotFoundException { |
| getResourceValue(resources, id, outValue); |
| } |
| |
| private static ResourceValue getResourceValue(Resources resources, int id, TypedValue outValue) |
| throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| ResourceValue resVal = value.getSecond(); |
| String v = resVal != null ? resVal.getValue() : null; |
| |
| if (v != null) { |
| if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, |
| false /*requireUnit*/)) { |
| return resVal; |
| } |
| if (resVal instanceof DensityBasedResourceValue) { |
| outValue.density = |
| ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue(); |
| } |
| |
| // else it's a string |
| outValue.type = TypedValue.TYPE_STRING; |
| outValue.string = v; |
| return resVal; |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| return null; // This is not used since the method above always throws. |
| } |
| |
| @LayoutlibDelegate |
| static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs) |
| throws NotFoundException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @LayoutlibDelegate |
| static void getValueForDensity(Resources resources, int id, int density, TypedValue outValue, |
| boolean resolveRefs) throws NotFoundException { |
| getValue(resources, id, outValue, resolveRefs); |
| } |
| |
| @LayoutlibDelegate |
| static int getAttributeSetSourceResId(@Nullable AttributeSet set) { |
| // Not supported in layoutlib |
| return Resources.ID_NULL; |
| } |
| |
| @LayoutlibDelegate |
| static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> v = getResourceValue(resources, id); |
| |
| if (v != null) { |
| ResourceValue value = v.getSecond(); |
| |
| try { |
| return ResourceHelper.getXmlBlockParser(getContext(resources), value); |
| } catch (XmlPullParserException e) { |
| Bridge.getLog().error(LayoutLog.TAG_BROKEN, |
| "Failed to parse " + value.getValue(), e, null /*data*/); |
| // we'll return null below. |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static XmlResourceParser loadXmlResourceParser(Resources resources, int id, |
| String type) throws NotFoundException { |
| return resources.loadXmlResourceParser_Original(id, type); |
| } |
| |
| @LayoutlibDelegate |
| static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id, |
| int assetCookie, String type) throws NotFoundException { |
| // even though we know the XML file to load directly, we still need to resolve the |
| // id so that we can know if it's a platform or project resource. |
| // (mPlatformResouceFlag will get the result and will be used later). |
| Pair<String, ResourceValue> result = getResourceValue(resources, id); |
| |
| ResourceNamespace layoutNamespace; |
| if (result != null && result.getSecond() != null) { |
| layoutNamespace = result.getSecond().getNamespace(); |
| } else { |
| // We need to pick something, even though the resource system never heard about a layout |
| // with this numeric id. |
| layoutNamespace = ResourceNamespace.RES_AUTO; |
| } |
| |
| try { |
| XmlPullParser parser = ParserFactory.create(file); |
| return new BridgeXmlBlockParser(parser, getContext(resources), layoutNamespace); |
| } catch (XmlPullParserException e) { |
| NotFoundException newE = new NotFoundException(); |
| newE.initCause(e); |
| throw newE; |
| } |
| } |
| |
| @LayoutlibDelegate |
| static InputStream openRawResource(Resources resources, int id) throws NotFoundException { |
| Pair<String, ResourceValue> value = getResourceValue(resources, id); |
| |
| if (value != null) { |
| String path = value.getSecond().getValue(); |
| if (path != null) { |
| return openRawResource(resources, path); |
| } |
| } |
| |
| // id was not found or not resolved. Throw a NotFoundException. |
| throwException(resources, id); |
| |
| // this is not used since the method above always throws |
| return null; |
| } |
| |
| @LayoutlibDelegate |
| static InputStream openRawResource(Resources resources, int id, TypedValue value) |
| throws NotFoundException { |
| getValue(resources, id, value, true); |
| |
| String path = value.string.toString(); |
| return openRawResource(resources, path); |
| } |
| |
| private static InputStream openRawResource(Resources resources, String path) |
| throws NotFoundException { |
| AssetRepository repository = getAssetRepository(resources); |
| try { |
| InputStream stream = repository.openNonAsset(0, path, ACCESS_STREAMING); |
| if (stream == null) { |
| throw new NotFoundException(path); |
| } |
| // If it's a nine-patch return a custom input stream so that |
| // other methods (mainly bitmap factory) can detect it's a 9-patch |
| // and actually load it as a 9-patch instead of a normal bitmap. |
| if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { |
| return new NinePatchInputStream(stream); |
| } |
| return stream; |
| } catch (IOException e) { |
| NotFoundException exception = new NotFoundException(); |
| exception.initCause(e); |
| throw exception; |
| } |
| } |
| |
| @LayoutlibDelegate |
| static AssetFileDescriptor openRawResourceFd(Resources resources, int id) |
| throws NotFoundException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @VisibleForTesting |
| @Nullable |
| static ResourceUrl resourceUrlFromName( |
| @NonNull String name, @Nullable String defType, @Nullable String defPackage) { |
| int colonIdx = name.indexOf(':'); |
| int slashIdx = name.indexOf('/'); |
| |
| if (colonIdx != -1 && slashIdx != -1) { |
| // Easy case |
| return ResourceUrl.parse(PREFIX_RESOURCE_REF + name); |
| } |
| |
| if (colonIdx == -1 && slashIdx == -1) { |
| if (defType == null) { |
| throw new IllegalArgumentException("name does not define a type an no defType was" + |
| " passed"); |
| } |
| |
| // It does not define package or type |
| return ResourceUrl.parse( |
| PREFIX_RESOURCE_REF + (defPackage != null ? defPackage + ":" : "") + defType + |
| "/" + name); |
| } |
| |
| if (colonIdx != -1) { |
| if (defType == null) { |
| throw new IllegalArgumentException("name does not define a type an no defType was" + |
| " passed"); |
| } |
| // We have package but no type |
| String pkg = name.substring(0, colonIdx); |
| ResourceType type = ResourceType.getEnum(defType); |
| return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) : |
| null; |
| } |
| |
| ResourceType type = ResourceType.getEnum(name.substring(0, slashIdx)); |
| if (type == null) { |
| return null; |
| } |
| // We have type but no package |
| return ResourceUrl.create(defPackage, |
| type, |
| name.substring(slashIdx + 1)); |
| } |
| |
| @LayoutlibDelegate |
| static int getIdentifier(Resources resources, String name, String defType, String defPackage) { |
| if (name == null) { |
| return 0; |
| } |
| |
| ResourceUrl url = resourceUrlFromName(name, defType, defPackage); |
| if (url != null) { |
| if (ANDROID_PKG.equals(url.namespace)) { |
| return Bridge.getResourceId(url.type, url.name); |
| } |
| |
| if (getContext(resources).getPackageName().equals(url.namespace)) { |
| return getLayoutlibCallback(resources).getOrGenerateResourceId( |
| new ResourceReference(ResourceNamespace.RES_AUTO, url.type, url.name)); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource |
| * type. |
| * |
| * @param id the id of the resource |
| * @param expectedType the type of resource that was expected |
| * |
| * @throws NotFoundException |
| */ |
| private static void throwException(Resources resources, int id, @Nullable String expectedType) |
| throws NotFoundException { |
| throwException(id, getResourceInfo(resources, id), expectedType); |
| } |
| |
| private static void throwException(Resources resources, int id) throws NotFoundException { |
| throwException(resources, id, null); |
| } |
| |
| private static void throwException(int id, @Nullable ResourceReference resourceInfo) { |
| throwException(id, resourceInfo, null); |
| } |
| private static void throwException(int id, @Nullable ResourceReference resourceInfo, |
| @Nullable String expectedType) { |
| String message; |
| if (resourceInfo != null) { |
| message = String.format( |
| "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", |
| resourceInfo.getResourceType(), id, resourceInfo.getName()); |
| } else { |
| message = String.format("Could not resolve resource value: 0x%1$X.", id); |
| } |
| |
| if (expectedType != null) { |
| message += " Or the resolved value was not of type " + expectedType + " as expected."; |
| } |
| throw new NotFoundException(message); |
| } |
| |
| private static int getInt(String v) throws NumberFormatException { |
| int radix = 10; |
| if (v.startsWith("0x")) { |
| v = v.substring(2); |
| radix = 16; |
| } else if (v.startsWith("0")) { |
| radix = 8; |
| } |
| return Integer.parseInt(v, radix); |
| } |
| |
| private static AssetRepository getAssetRepository(Resources resources) { |
| BridgeContext context = getContext(resources); |
| BridgeAssetManager assetManager = context.getAssets(); |
| return assetManager.getAssetRepository(); |
| } |
| } |