/* * Copyright 2016 Flipkart Internet Pvt. Ltd. * * 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 com.flipkart.android.proteus.toolbox; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; import android.text.TextUtils; import android.util.Log; import android.util.StateSet; import android.webkit.ValueCallback; import com.flipkart.android.proteus.parser.ParseHelper; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.util.HashMap; import java.util.Map; import java.util.Set; public class ColorUtils { private static final String TAG = "ColorUtils"; private static HashMap<String, Integer> sAttributesMap = null; /** * @param context Application context used to access resources * @param value JSON representation of the Color * @param colorCallback Callback for return Value if it is a Color Resource * @param colorStateListCallback Callback for return Value if it is a ColorStateList * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded */ public static void loadColor(Context context, JsonElement value, ValueCallback<Integer> colorCallback, ValueCallback<ColorStateList> colorStateListCallback) throws Resources.NotFoundException { if (value.isJsonPrimitive()) { handleString(context, value.getAsString(), colorCallback, colorStateListCallback); } else if (value.isJsonObject()) { handleElement(context, value.getAsJsonObject(), colorCallback, colorStateListCallback); } else { if (ProteusConstants.isLoggingEnabled()) { Log.e(TAG, "Could not color for : " + value.toString()); } } } private static void handleString(Context context, String attributeValue, ValueCallback<Integer> colorCallback, ValueCallback<ColorStateList> colorStateListCallback) { if (ParseHelper.isColor(attributeValue)) { colorCallback.onReceiveValue(Color.parseColor(attributeValue)); } else if (ParseHelper.isLocalColorResource(attributeValue)) { try { Resources r = context.getResources(); int colorId = r.getIdentifier(attributeValue, "color", context.getPackageName()); ColorStateList colorStateList = null; try { colorStateList = r.getColorStateList(colorId); } catch (Resources.NotFoundException nfe) { //assuming this is a color Now } if (null != colorStateList) { colorStateListCallback.onReceiveValue(colorStateList); } else { int color = r.getColor(colorId); colorCallback.onReceiveValue(color); } } catch (Exception ex) { if (ProteusConstants.isLoggingEnabled()) { Log.e(TAG, "Could not load local resource " + attributeValue); } } } } private static void handleElement(Context context, JsonObject value, ValueCallback<Integer> colorCallback, ValueCallback<ColorStateList> colorStateListCallback) { JsonObject jsonObject = value.getAsJsonObject(); ColorStateList colorStateList = inflateFromJson(context, jsonObject); if (null != colorStateList) { colorStateListCallback.onReceiveValue(colorStateList); } } private static Integer getColorFromAttributeValue(Context context, String attributeValue) { Integer result = null; if (ParseHelper.isColor(attributeValue)) { result = Color.parseColor(attributeValue); } else if (ParseHelper.isLocalColorResource(attributeValue)) { Resources r = context.getResources(); int colorId = r.getIdentifier(attributeValue, "color", context.getPackageName()); result = r.getColor(colorId); } return result; } private static int idealByteArraySize(int need) { for (int i = 4; i < 32; i++) { if (need <= (1 << i) - 12) { return (1 << i) - 12; } } return need; } private static int idealIntArraySize(int need) { return idealByteArraySize(need * 4) / 4; } public static int constrain(int amount, int low, int high) { return amount < low ? low : (amount > high ? high : amount); } private static int modulateColorAlpha(int baseColor, float alphaMod) { if (alphaMod == 1.0f) { return baseColor; } final int baseAlpha = Color.alpha(baseColor); final int alpha = constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255); return (baseColor & 0xFFFFFF) | (alpha << 24); } private static HashMap<String, Integer> getAttributesMap() { if (null == sAttributesMap) { synchronized (ColorUtils.class) { if (null == sAttributesMap) { sAttributesMap = new HashMap<>(15); sAttributesMap.put("type", android.R.attr.type); sAttributesMap.put("color", android.R.attr.color); sAttributesMap.put("alpha", android.R.attr.alpha); sAttributesMap.put("state_pressed", android.R.attr.state_pressed); sAttributesMap.put("state_focused", android.R.attr.state_focused); sAttributesMap.put("state_selected", android.R.attr.state_selected); sAttributesMap.put("state_checkable", android.R.attr.state_checkable); sAttributesMap.put("state_checked", android.R.attr.state_checked); sAttributesMap.put("state_enabled", android.R.attr.state_enabled); sAttributesMap.put("state_window_focused", android.R.attr.state_window_focused); } } } return sAttributesMap; } private static Integer getAttribute(String attribute) { return getAttributesMap().get(attribute); } private static ColorStateList inflateFromJson(Context context, JsonObject jsonObject) { ColorStateList result = null; JsonElement type = jsonObject.get("type"); if (null != type && type.isJsonPrimitive()) { String colorType = type.getAsString(); if (TextUtils.equals(colorType, "selector")) { JsonElement childrenElement = jsonObject.get("children"); if (null != childrenElement && childrenElement.isJsonArray()) { JsonArray children = childrenElement.getAsJsonArray(); int listAllocated = 20; int listSize = 0; int[] colorList = new int[listAllocated]; int[][] stateSpecList = new int[listAllocated][]; for (int idx = 0; idx < children.size(); idx++) { JsonElement itemObject = children.get(idx); if (!itemObject.isJsonObject()) { continue; } Set<Map.Entry<String, JsonElement>> entrySet = ((JsonObject) itemObject).entrySet(); if (entrySet.size() == 0) { continue; } int j = 0; Integer baseColor = null; float alphaMod = 1.0f; int[] stateSpec = new int[entrySet.size() - 1]; boolean ignoreItem = false; for (Map.Entry<String, JsonElement> entry : entrySet) { if (ignoreItem) { break; } if (!entry.getValue().isJsonPrimitive()) { continue; } Integer attributeId = getAttribute(entry.getKey()); if (null != attributeId) { switch (attributeId) { case android.R.attr.type: if (!TextUtils.equals("item", entry.getValue().getAsString())) { ignoreItem = true; } break; case android.R.attr.color: String colorRes = entry.getValue().getAsString(); if (!TextUtils.isEmpty(colorRes)) { baseColor = getColorFromAttributeValue(context, colorRes); } break; case android.R.attr.alpha: String alphaStr = entry.getValue().getAsString(); if (!TextUtils.isEmpty(alphaStr)) { alphaMod = Float.parseFloat(alphaStr); } break; default: stateSpec[j++] = entry.getValue().getAsBoolean() ? attributeId : -attributeId; break; } } } if (!ignoreItem) { stateSpec = StateSet.trimStateSet(stateSpec, j); if (null == baseColor) { throw new IllegalStateException("No Color Specified"); } if (listSize + 1 >= listAllocated) { listAllocated = idealIntArraySize(listSize + 1); int[] ncolor = new int[listAllocated]; System.arraycopy(colorList, 0, ncolor, 0, listSize); int[][] nstate = new int[listAllocated][]; System.arraycopy(stateSpecList, 0, nstate, 0, listSize); colorList = ncolor; stateSpecList = nstate; } final int color = modulateColorAlpha(baseColor, alphaMod); colorList[listSize] = color; stateSpecList[listSize] = stateSpec; listSize++; } } if (listSize > 0) { int[] colors = new int[listSize]; int[][] stateSpecs = new int[listSize][]; System.arraycopy(colorList, 0, colors, 0, listSize); System.arraycopy(stateSpecList, 0, stateSpecs, 0, listSize); result = new ColorStateList(stateSpecs, colors); } } } } return result; } }