blob: 059f9fbe8de3d4d298a4e1595e7a4b622813c825 [file] [log] [blame]
/*
* Copyright (C) 2012 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 com.android.sdklib.devices;
import static com.android.SdkConstants.VALUE_FALSE;
import static com.android.SdkConstants.VALUE_TRUE;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.dvlib.DeviceSchema;
import com.android.resources.Density;
import com.android.resources.Keyboard;
import com.android.resources.KeyboardState;
import com.android.resources.Navigation;
import com.android.resources.NavigationState;
import com.android.resources.ScreenOrientation;
import com.android.resources.ScreenRatio;
import com.android.resources.ScreenRound;
import com.android.resources.ScreenSize;
import com.android.resources.TouchScreen;
import com.android.resources.UiMode;
import com.google.common.base.Splitter;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import java.awt.Point;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.validation.Schema;
public class DeviceParser {
private static class DeviceHandler extends DefaultHandler {
private static final Splitter sSpaceSplitter = Splitter.on(' ').omitEmptyStrings();
private static final String ROUND_BOOT_PROP = "ro.emulator.circular";
private static final String CHIN_BOOT_PROP = "ro.emu.win_outset_bottom_px";
private final Table<String, String, Device> mDevices = HashBasedTable.create();
private final StringBuilder mStringAccumulator = new StringBuilder();
private final File mParentFolder;
private Meta mMeta;
private Hardware mHardware;
private Software mSoftware;
private State mState;
private Device.Builder mBuilder;
private Camera mCamera;
private Storage.Unit mUnit;
private String[] mBootProp;
public DeviceHandler(@Nullable File parentFolder) {
mParentFolder = parentFolder;
}
@NonNull
public Table<String, String, Device> getDevices() {
return mDevices;
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes)
throws SAXException {
if (DeviceSchema.NODE_DEVICE.equals(localName)) {
// Reset everything
mMeta = null;
mHardware = null;
mSoftware = null;
mState = null;
mCamera = null;
mBuilder = new Device.Builder();
} else if (DeviceSchema.NODE_META.equals(localName)) {
mMeta = new Meta();
} else if (DeviceSchema.NODE_HARDWARE.equals(localName)) {
mHardware = new Hardware();
} else if (DeviceSchema.NODE_SOFTWARE.equals(localName)) {
mSoftware = new Software();
} else if (DeviceSchema.NODE_STATE.equals(localName)) {
mState = new State();
// mState can embed a Hardware instance
mHardware = mHardware.deepCopy();
String defaultState = attributes.getValue(DeviceSchema.ATTR_DEFAULT);
if ("true".equals(defaultState) || "1".equals(defaultState)) {
mState.setDefaultState(true);
}
mState.setName(attributes.getValue(DeviceSchema.ATTR_NAME).trim());
} else if (DeviceSchema.NODE_CAMERA.equals(localName)) {
mCamera = new Camera();
} else if (DeviceSchema.NODE_RAM.equals(localName)
|| DeviceSchema.NODE_INTERNAL_STORAGE.equals(localName)
|| DeviceSchema.NODE_REMOVABLE_STORAGE.equals(localName)) {
mUnit = Storage.Unit.getEnum(attributes.getValue(DeviceSchema.ATTR_UNIT));
} else if (DeviceSchema.NODE_FRAME.equals(localName)) {
mMeta.setFrameOffsetLandscape(new Point());
mMeta.setFrameOffsetPortrait(new Point());
} else if (DeviceSchema.NODE_SCREEN.equals(localName)) {
mHardware.setScreen(new Screen());
} else if (DeviceSchema.NODE_BOOT_PROP.equals(localName)) {
mBootProp = new String[2];
}
mStringAccumulator.setLength(0);
}
@Override
public void characters(char[] ch, int start, int length) {
mStringAccumulator.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
if (DeviceSchema.NODE_DEVICE.equals(localName)) {
Device device = mBuilder.build();
mDevices.put(device.getId(), device.getManufacturer(), device);
} else if (DeviceSchema.NODE_NAME.equals(localName)) {
mBuilder.setName(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_ID.equals(localName)) {
mBuilder.setId(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_MANUFACTURER.equals(localName)) {
mBuilder.setManufacturer(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_META.equals(localName)) {
mBuilder.setMeta(mMeta);
} else if (DeviceSchema.NODE_SOFTWARE.equals(localName)) {
mBuilder.addSoftware(mSoftware);
} else if (DeviceSchema.NODE_STATE.equals(localName)) {
mState.setHardware(mHardware);
mBuilder.addState(mState);
} else if (DeviceSchema.NODE_SIXTY_FOUR.equals(localName)) {
mMeta.setIconSixtyFour(new File(mParentFolder, getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_SIXTEEN.equals(localName)) {
mMeta.setIconSixteen(new File(mParentFolder, getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_PATH.equals(localName)) {
mMeta.setFrame(new File(mParentFolder, mStringAccumulator.toString().trim()));
} else if (DeviceSchema.NODE_PORTRAIT_X_OFFSET.equals(localName)) {
mMeta.getFrameOffsetPortrait().x = getInteger(mStringAccumulator);
} else if (DeviceSchema.NODE_PORTRAIT_Y_OFFSET.equals(localName)) {
mMeta.getFrameOffsetPortrait().y = getInteger(mStringAccumulator);
} else if (DeviceSchema.NODE_LANDSCAPE_X_OFFSET.equals(localName)) {
mMeta.getFrameOffsetLandscape().x = getInteger(mStringAccumulator);
} else if (DeviceSchema.NODE_LANDSCAPE_Y_OFFSET.equals(localName)) {
mMeta.getFrameOffsetLandscape().y = getInteger(mStringAccumulator);
} else if (DeviceSchema.NODE_SCREEN_SIZE.equals(localName)) {
mHardware.getScreen().setSize(ScreenSize.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_DIAGONAL_LENGTH.equals(localName)) {
mHardware.getScreen().setDiagonalLength(getDouble(mStringAccumulator));
} else if (DeviceSchema.NODE_PIXEL_DENSITY.equals(localName)) {
mHardware.getScreen().setPixelDensity(
Density.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_SCREEN_RATIO.equals(localName)) {
mHardware.getScreen().setRatio(
ScreenRatio.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_X_DIMENSION.equals(localName)) {
mHardware.getScreen().setXDimension(getInteger(mStringAccumulator));
} else if (DeviceSchema.NODE_Y_DIMENSION.equals(localName)) {
mHardware.getScreen().setYDimension(getInteger(mStringAccumulator));
} else if (DeviceSchema.NODE_XDPI.equals(localName)) {
mHardware.getScreen().setXdpi(getDouble(mStringAccumulator));
} else if (DeviceSchema.NODE_YDPI.equals(localName)) {
mHardware.getScreen().setYdpi(getDouble(mStringAccumulator));
} else if (DeviceSchema.NODE_MULTITOUCH.equals(localName)) {
mHardware.getScreen().setMultitouch(
Multitouch.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_MECHANISM.equals(localName)) {
mHardware.getScreen().setMechanism(
TouchScreen.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_SCREEN_TYPE.equals(localName)) {
mHardware.getScreen().setScreenType(
ScreenType.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_NETWORKING.equals(localName)) {
for (String n : getStringList(mStringAccumulator)) {
Network net = Network.getEnum(n);
if (net != null) {
mHardware.addNetwork(net);
}
}
} else if (DeviceSchema.NODE_SENSORS.equals(localName)) {
for (String s : getStringList(mStringAccumulator)) {
Sensor sens = Sensor.getEnum(s);
if (sens != null) {
mHardware.addSensor(sens);
}
}
} else if (DeviceSchema.NODE_MIC.equals(localName)) {
mHardware.setHasMic(getBool(mStringAccumulator));
} else if (DeviceSchema.NODE_CAMERA.equals(localName)) {
mHardware.addCamera(mCamera);
mCamera = null;
} else if (DeviceSchema.NODE_LOCATION.equals(localName)) {
CameraLocation location = CameraLocation.getEnum(getString(mStringAccumulator));
if (location != null) {
mCamera.setLocation(location);
}
} else if (DeviceSchema.NODE_AUTOFOCUS.equals(localName)) {
mCamera.setFlash(getBool(mStringAccumulator));
} else if (DeviceSchema.NODE_FLASH.equals(localName)) {
mCamera.setFlash(getBool(mStringAccumulator));
} else if (DeviceSchema.NODE_KEYBOARD.equals(localName)) {
mHardware.setKeyboard(Keyboard.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_NAV.equals(localName)) {
mHardware.setNav(Navigation.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_RAM.equals(localName)) {
int val = getInteger(mStringAccumulator);
mHardware.setRam(new Storage(val, mUnit));
} else if (DeviceSchema.NODE_BUTTONS.equals(localName)) {
ButtonType buttonType = ButtonType.getEnum(getString(mStringAccumulator));
if (buttonType != null) {
mHardware.setButtonType(buttonType);
}
} else if (DeviceSchema.NODE_INTERNAL_STORAGE.equals(localName)) {
for (String s : getStringList(mStringAccumulator)) {
int val = Integer.parseInt(s);
mHardware.addInternalStorage(new Storage(val, mUnit));
}
} else if (DeviceSchema.NODE_REMOVABLE_STORAGE.equals(localName)) {
for (String s : getStringList(mStringAccumulator)) {
if (s != null && !s.isEmpty()) {
int val = Integer.parseInt(s);
mHardware.addRemovableStorage(new Storage(val, mUnit));
}
}
} else if (DeviceSchema.NODE_CPU.equals(localName)) {
mHardware.setCpu(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_GPU.equals(localName)) {
mHardware.setGpu(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_ABI.equals(localName)) {
for (String s : getStringList(mStringAccumulator)) {
Abi abi = Abi.getEnum(s);
if (abi != null) {
mHardware.addSupportedAbi(abi);
}
}
} else if (DeviceSchema.NODE_DOCK.equals(localName)) {
for (String s : getStringList(mStringAccumulator)) {
UiMode d = UiMode.getEnum(s);
if (d != null) {
mHardware.addSupportedUiMode(d);
}
}
} else if (DeviceSchema.NODE_POWER_TYPE.equals(localName)) {
PowerType type = PowerType.getEnum(getString(mStringAccumulator));
if (type != null) {
mHardware.setChargeType(type);
}
} else if (DeviceSchema.NODE_API_LEVEL.equals(localName)) {
String val = getString(mStringAccumulator);
// Can be one of 5 forms:
// 1
// 1-2
// 1-
// -2
// -
int index;
if (val.charAt(0) == '-') {
if (val.length() == 1) { // -
mSoftware.setMinSdkLevel(0);
mSoftware.setMaxSdkLevel(Integer.MAX_VALUE);
} else { // -2
// Remove the front dash and any whitespace between it
// and the upper bound.
val = val.substring(1).trim();
mSoftware.setMinSdkLevel(0);
mSoftware.setMaxSdkLevel(Integer.parseInt(val));
}
} else if ((index = val.indexOf('-')) > 0) {
if (index == val.length() - 1) { // 1-
// Strip the last dash and any whitespace between it and
// the lower bound.
val = val.substring(0, val.length() - 1).trim();
mSoftware.setMinSdkLevel(Integer.parseInt(val));
mSoftware.setMaxSdkLevel(Integer.MAX_VALUE);
} else { // 1-2
String min = val.substring(0, index).trim();
String max = val.substring(index + 1);
mSoftware.setMinSdkLevel(Integer.parseInt(min));
mSoftware.setMaxSdkLevel(Integer.parseInt(max));
}
} else { // 1
int apiLevel = Integer.parseInt(val);
mSoftware.setMinSdkLevel(apiLevel);
mSoftware.setMaxSdkLevel(apiLevel);
}
} else if (DeviceSchema.NODE_LIVE_WALLPAPER_SUPPORT.equals(localName)) {
mSoftware.setLiveWallpaperSupport(getBool(mStringAccumulator));
} else if (DeviceSchema.NODE_BLUETOOTH_PROFILES.equals(localName)) {
for (String s : getStringList(mStringAccumulator)) {
BluetoothProfile profile = BluetoothProfile.getEnum(s);
if (profile != null) {
mSoftware.addBluetoothProfile(profile);
}
}
} else if (DeviceSchema.NODE_GL_VERSION.equals(localName)) {
// Guaranteed to be in the form [\d]\.[\d]
mSoftware.setGlVersion(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_GL_EXTENSIONS.equals(localName)) {
mSoftware.addAllGlExtensions(getStringList(mStringAccumulator));
} else if (DeviceSchema.NODE_DESCRIPTION.equals(localName)) {
mState.setDescription(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_SCREEN_ORIENTATION.equals(localName)) {
mState.setOrientation(ScreenOrientation.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_KEYBOARD_STATE.equals(localName)) {
mState.setKeyState(KeyboardState.getEnum(getString(mStringAccumulator)));
} else if (DeviceSchema.NODE_NAV_STATE.equals(localName)) {
// We have an extra state in our XML for nonav that
// NavigationState doesn't contain
String navState = getString(mStringAccumulator);
if (navState.equals("nonav")) {
mState.setNavState(NavigationState.HIDDEN);
} else {
mState.setNavState(NavigationState.getEnum(getString(mStringAccumulator)));
}
} else if (DeviceSchema.NODE_STATUS_BAR.equals(localName)) {
mSoftware.setStatusBar(getBool(mStringAccumulator));
} else if (DeviceSchema.NODE_TAG_ID.equals(localName)) {
mBuilder.setTagId(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_PROP_NAME.equals(localName)) {
assert mBootProp != null && mBootProp.length == 2;
mBootProp[0] = getString(mStringAccumulator);
} else if (DeviceSchema.NODE_PROP_VALUE.equals(localName)) {
assert mBootProp != null && mBootProp.length == 2;
mBootProp[1] = mStringAccumulator.toString();
} else if (DeviceSchema.NODE_BOOT_PROP.equals(localName)) {
assert mBootProp != null && mBootProp.length == 2 &&
mBootProp[0] != null && mBootProp[1] != null;
mBuilder.addBootProp(mBootProp[0], mBootProp[1]);
checkAndSetIfRound(mBootProp[0], mBootProp[1]);
mBootProp = null;
} else if (DeviceSchema.NODE_SKIN.equals(localName)) {
String path = getString(mStringAccumulator).replace('/', File.separatorChar);
mHardware.setSkinFile(new File(path));
}
}
@Override
public void error(SAXParseException e) throws SAXParseException {
throw e;
}
private void checkAndSetIfRound(String bootPropKey, String bootPropValue) {
// This is a ugly hack. To keep the existing devices.xmls working, the roundness of the
// screen is stored in a boot property.
ScreenRound roundness = null;
if (ROUND_BOOT_PROP.equals(bootPropKey)) {
if (VALUE_TRUE.equals(bootPropValue)) {
roundness = ScreenRound.ROUND;
} else if (VALUE_FALSE.equals(bootPropValue)) {
roundness = ScreenRound.NOTROUND;
}
for (State state : mBuilder.getAllStates()) {
state.getHardware().getScreen().setScreenRound(roundness);
}
}
if (CHIN_BOOT_PROP.equals(bootPropKey)) {
int chin = Integer.parseInt(bootPropValue);
for (State state : mBuilder.getAllStates()) {
state.getHardware().getScreen().setChin(chin);
}
}
}
private static List<String> getStringList(StringBuilder stringAccumulator) {
List<String> filteredStrings = new ArrayList<String>();
for (String s : sSpaceSplitter.split(stringAccumulator)) {
if (s != null && !s.isEmpty()) {
filteredStrings.add(s.trim());
}
}
return filteredStrings;
}
private static Boolean getBool(StringBuilder s) {
return equals(s, "true") || equals(s, "1");
}
private static double getDouble(StringBuilder stringAccumulator) {
return Double.parseDouble(getString(stringAccumulator));
}
private static String getString(StringBuilder s) {
return s.toString().trim();
}
private static boolean equals(StringBuilder s, String t) {
int start = 0;
int length = s.length();
while (start < length && Character.isWhitespace(s.charAt(start))) {
start++;
}
if (start == length) {
return t.isEmpty();
}
int end = length;
while (end > start && Character.isWhitespace(s.charAt(end - 1))) {
end--;
}
if (t.length() != (end - start)) {
return false;
}
for (int i = 0, n = t.length(), j = start; i < n; i++, j++) {
if (Character.toLowerCase(s.charAt(j)) != Character.toLowerCase(t.charAt(i))) {
return false;
}
}
return true;
}
private static int getInteger(StringBuilder stringAccumulator) {
return Integer.parseInt(getString(stringAccumulator));
}
}
private static final SAXParserFactory sParserFactory;
static {
sParserFactory = SAXParserFactory.newInstance();
sParserFactory.setNamespaceAware(true);
}
@NonNull
public static Table<String, String, Device> parse(@NonNull File devicesFile)
throws SAXException, ParserConfigurationException, IOException {
// stream closed by parseImpl.
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
InputStream stream = new FileInputStream(devicesFile);
return parseImpl(stream, devicesFile.getAbsoluteFile().getParentFile());
}
/**
* This method closes the stream.
*/
@NonNull
public static Table<String, String, Device> parse(@NonNull InputStream devices)
throws SAXException, IOException, ParserConfigurationException {
return parseImpl(devices, null);
}
/**
* After parsing, this method closes the stream.
*/
@NonNull
private static Table<String, String, Device> parseImpl(@NonNull InputStream devices, @Nullable File parentDir)
throws SAXException, IOException, ParserConfigurationException {
try {
if (!devices.markSupported()) {
//noinspection IOResourceOpenedButNotSafelyClosed
devices = new BufferedInputStream(devices); // closed in the finally block.
}
devices.mark(500000);
int version = DeviceSchema.getXmlSchemaVersion(devices);
SAXParser parser = getParser(version);
DeviceHandler dHandler = new DeviceHandler(parentDir);
devices.reset();
parser.parse(devices, dHandler);
return dHandler.getDevices();
}
finally {
// It's better to close the stream here since we may have created it above.
devices.close();
}
}
@NonNull
private static SAXParser getParser(int version) throws ParserConfigurationException, SAXException {
Schema schema = DeviceSchema.getSchema(version);
if (schema != null) {
sParserFactory.setSchema(schema);
}
return sParserFactory.newSAXParser();
}
}