/* * Copyright 2016 Google Inc. All Rights Reserved. * * 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 org.cf.apkfile.res; import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.primitives.UnsignedBytes; import javax.annotation.Nullable; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Map; /** * Represents a single typed resource value. */ @AutoValue public abstract class ResourceValue implements SerializableResource { private static final int COMPLEX_RADIX_SHIFT = 4; private static final int COMPLEX_RADIX_MASK = 0x3; private static final int COMPLEX_UNIT_MASK = 0xf; private static final int COMPLEX_MANTISSA_SHIFT = 8; private static final int COMPLEX_MANTISSA_MASK = 0xffffff; private static final float MANTISSA_MULT = 1.0f / (1 << COMPLEX_MANTISSA_SHIFT); private static final float[] RADIX_MULTS = new float[]{ 1.0f * MANTISSA_MULT, 1.0f / (1 << 7) * MANTISSA_MULT, 1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT }; private static final String[] DIMENSION_UNIT_STRS = new String[]{"px", "dip", "sp", "pt", "in", "mm"}; private static final String[] FRACTION_UNIT_STRS = new String[]{"%", "%p"}; /** * The serialized size in bytes of a {@link ResourceValue}. */ public static final int SIZE = 8; public static ResourceValue create(ByteBuffer buffer) { int size = (buffer.getShort() & 0xFFFF); buffer.get(); // Unused Type type = Type.fromCode(buffer.get()); int data = buffer.getInt(); return new AutoValue_ResourceValue(size, type, data); } /** * The length in bytes of this value. */ public abstract int size(); /** * The raw data type of this value. */ public abstract Type type(); /** * The actual 4-byte value; interpretation of the value depends on {@code dataType}. */ public abstract int data(); @Override public byte[] toByteArray() { return toByteArray(false); } @Override public byte[] toByteArray(boolean shrink) { ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN); buffer.putShort((short) size()); buffer.put((byte) 0); // Unused buffer.put(type().code()); buffer.putInt(data()); return buffer.array(); } private static float complexToFloat(int complex) { return (complex & (COMPLEX_MANTISSA_MASK << COMPLEX_MANTISSA_SHIFT)) * RADIX_MULTS[(complex >> COMPLEX_RADIX_SHIFT) & COMPLEX_RADIX_MASK]; } protected String getString(StringPoolChunk stringPool, @Nullable ResourceTableChunk resourceTable) { switch (type()) { case INT_BOOLEAN: return data() != 0 ? Boolean.TRUE.toString() : Boolean.FALSE.toString(); case REFERENCE: if (resourceTable != null) { String value = resourceTable.resolve(data()); if (value != null) { return value; } } return "U[" + data() + "]"; case STRING: return stringPool.getString(data()); case INT_HEX: // Prefer numeric values when possible. No reason to care about hex formatting. // return String.format("0x%08X", data()); case INT_DEC: return Integer.toString(data()); case NULL: return ""; case ATTRIBUTE: return "A[" + data() + "]"; case FLOAT: return Float.toString(Float.intBitsToFloat(data())); case DIMENSION: return Float.toString(complexToFloat(data())) + DIMENSION_UNIT_STRS[(data()) & COMPLEX_UNIT_MASK]; case FRACTION: return Float.toString(complexToFloat(data())) + DIMENSION_UNIT_STRS[(data()) & COMPLEX_UNIT_MASK]; } if (type().code >= Type.INT_COLOR_ARGB8.code && type().code <= Type.INT_COLOR_RGB4.code) { String res = String.format("%08x", data()); char[] vals = res.toCharArray(); switch (type()) { default: case INT_COLOR_ARGB8: // #AaRrGgBb break; case INT_COLOR_RGB8: // #FFRrGgBb->#RrGgBb res = res.substring(2); break; case INT_COLOR_ARGB4: // #AARRGGBB->#ARGB res = String.valueOf(vals[0]) + vals[2] + vals[4] + vals[6]; break; case INT_COLOR_RGB4: // #FFRRGGBB->#RGB res = String.valueOf(vals[2]) + vals[4] + vals[6]; break; } return "#" + res; } else if (type().code >= Type.INT_DEC.code && type().code <= Type.INT_COLOR_RGB4.code) { return Integer.toString(data()); } return ""; } /** * Resource type codes. */ public enum Type { /** * {@code data} is either 0 (undefined) or 1 (empty). */ NULL(0x00), /** * {@code data} holds a {@link ResourceTableChunk} entry reference. */ REFERENCE(0x01), /** * {@code data} holds an attribute resource identifier. */ ATTRIBUTE(0x02), /** * {@code data} holds an index into the containing resource table's string pool. */ STRING(0x03), /** * {@code data} holds a single-precision floating point number. */ FLOAT(0x04), /** * {@code data} holds a complex number encoding a dimension value, such as "100in". */ DIMENSION(0x05), /** * {@code data} holds a complex number encoding a fraction of a container. */ FRACTION(0x06), /** * TODO: Unknown value form */ DYNAMIC_ATTRIBUTE(0x08), /** * {@code data} holds a dynamic {@link ResourceTableChunk} entry reference. */ DYNAMIC_REFERENCE(0x07), /** * {@code data} is a raw integer value of the form n..n. */ INT_DEC(0x10), /** * {@code data} is a raw integer value of the form 0xn..n. */ INT_HEX(0x11), /** * {@code data} is either 0 (false) or 1 (true). */ INT_BOOLEAN(0x12), /** * {@code data} is a raw integer value of the form #aarrggbb. */ INT_COLOR_ARGB8(0x1c), /** * {@code data} is a raw integer value of the form #rrggbb. */ INT_COLOR_RGB8(0x1d), /** * {@code data} is a raw integer value of the form #argb. */ INT_COLOR_ARGB4(0x1e), /** * {@code data} is a raw integer value of the form #rgb. */ INT_COLOR_RGB4(0x1f); private static final Map<Byte, Type> FROM_BYTE; static { Builder<Byte, Type> builder = ImmutableMap.builder(); for (Type type : values()) { builder.put(type.code(), type); } FROM_BYTE = builder.build(); } private final byte code; Type(int code) { this.code = UnsignedBytes.checkedCast(code); } public static Type fromCode(byte code) { return Preconditions.checkNotNull(FROM_BYTE.get(code), "Unknown resource type: %s", code); } public byte code() { return code; } } }