diff options
Diffstat (limited to 'ide_common/src/com/android')
-rw-r--r-- | ide_common/src/com/android/ide/common/xml/AndroidManifestParser.java | 671 | ||||
-rw-r--r-- | ide_common/src/com/android/ide/common/xml/ManifestData.java | 747 |
2 files changed, 1418 insertions, 0 deletions
diff --git a/ide_common/src/com/android/ide/common/xml/AndroidManifestParser.java b/ide_common/src/com/android/ide/common/xml/AndroidManifestParser.java new file mode 100644 index 0000000..38dc1c4 --- /dev/null +++ b/ide_common/src/com/android/ide/common/xml/AndroidManifestParser.java @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2007 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.ide.common.xml; + +import com.android.SdkConstants; +import com.android.ide.common.xml.ManifestData.Activity; +import com.android.ide.common.xml.ManifestData.Instrumentation; +import com.android.ide.common.xml.ManifestData.SupportsScreens; +import com.android.ide.common.xml.ManifestData.UsesConfiguration; +import com.android.ide.common.xml.ManifestData.UsesFeature; +import com.android.ide.common.xml.ManifestData.UsesLibrary; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.io.StreamException; +import com.android.resources.Keyboard; +import com.android.resources.Navigation; +import com.android.resources.TouchScreen; +import com.android.xml.AndroidManifest; + +import org.xml.sax.Attributes; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +public class AndroidManifestParser { + + private final static int LEVEL_TOP = 0; + private final static int LEVEL_INSIDE_MANIFEST = 1; + private final static int LEVEL_INSIDE_APPLICATION = 2; + private final static int LEVEL_INSIDE_APP_COMPONENT = 3; + private final static int LEVEL_INSIDE_INTENT_FILTER = 4; + + private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ + private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ + + public interface ManifestErrorHandler extends ErrorHandler { + /** + * Handles a parsing error and an optional line number. + */ + void handleError(Exception exception, int lineNumber); + + /** + * Checks that a class is valid and can be used in the Android Manifest. + * <p/> + * Errors are put as {@code org.eclipse.core.resources.IMarker} on the manifest file. + * + * @param locator + * @param className the fully qualified name of the class to test. + * @param superClassName the fully qualified name of the class it is supposed to extend. + * @param testVisibility if <code>true</code>, the method will check the visibility of + * the class or of its constructors. + */ + void checkClass(Locator locator, String className, String superClassName, + boolean testVisibility); + } + + /** + * XML error & data handler used when parsing the AndroidManifest.xml file. + * <p/> + * During parsing this will fill up the {@link ManifestData} object given to the constructor + * and call out errors to the given {@link ManifestErrorHandler}. + */ + private static class ManifestHandler extends DefaultHandler { + + //--- temporary data/flags used during parsing + private final ManifestData mManifestData; + private final ManifestErrorHandler mErrorHandler; + private int mCurrentLevel = 0; + private int mValidLevel = 0; + private Activity mCurrentActivity = null; + private Locator mLocator; + + /** + * Creates a new {@link ManifestHandler}. + * + * @param manifestFile The manifest file being parsed. Can be null. + * @param manifestData Class containing the manifest info obtained during the parsing. + * @param errorHandler An optional error handler. + */ + ManifestHandler(IAbstractFile manifestFile, ManifestData manifestData, + ManifestErrorHandler errorHandler) { + super(); + mManifestData = manifestData; + mErrorHandler = errorHandler; + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator) + */ + @Override + public void setDocumentLocator(Locator locator) { + mLocator = locator; + super.setDocumentLocator(locator); + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, + * java.lang.String, org.xml.sax.Attributes) + */ + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) + throws SAXException { + try { + if (mManifestData == null) { + return; + } + + // if we're at a valid level + if (mValidLevel == mCurrentLevel) { + String value; + switch (mValidLevel) { + case LEVEL_TOP: + if (AndroidManifest.NODE_MANIFEST.equals(localName)) { + // lets get the package name. + mManifestData.mPackage = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_PACKAGE, + false /* hasNamespace */); + + // and the versionCode + String tmp = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_VERSIONCODE, true); + if (tmp != null) { + try { + mManifestData.mVersionCode = Integer.valueOf(tmp); + } catch (NumberFormatException e) { + // keep null in the field. + } + } + mValidLevel++; + } + break; + case LEVEL_INSIDE_MANIFEST: + if (AndroidManifest.NODE_APPLICATION.equals(localName)) { + value = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_PROCESS, + true /* hasNamespace */); + if (value != null) { + mManifestData.addProcessName(value); + } + + value = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_DEBUGGABLE, + true /* hasNamespace*/); + if (value != null) { + mManifestData.mDebuggable = Boolean.parseBoolean(value); + } + + mValidLevel++; + } else if (AndroidManifest.NODE_USES_SDK.equals(localName)) { + mManifestData.setMinSdkVersionString(getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, + true /* hasNamespace */)); + mManifestData.setTargetSdkVersionString(getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION, + true /* hasNamespace */)); + } else if (AndroidManifest.NODE_INSTRUMENTATION.equals(localName)) { + processInstrumentationNode(attributes); + + } else if (AndroidManifest.NODE_SUPPORTS_SCREENS.equals(localName)) { + processSupportsScreensNode(attributes); + + } else if (AndroidManifest.NODE_USES_CONFIGURATION.equals(localName)) { + processUsesConfiguration(attributes); + + } else if (AndroidManifest.NODE_USES_FEATURE.equals(localName)) { + UsesFeature feature = new UsesFeature(); + + // get the name + value = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_NAME, + true /* hasNamespace */); + if (value != null) { + feature.mName = value; + } + + // read the required attribute + value = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_REQUIRED, + true /*hasNamespace*/); + if (value != null) { + Boolean b = Boolean.valueOf(value); + if (b != null) { + feature.mRequired = b; + } + } + + // read the gl es attribute + value = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_GLESVERSION, + true /*hasNamespace*/); + if (value != null) { + try { + int version = Integer.decode(value); + feature.mGlEsVersion = version; + } catch (NumberFormatException e) { + // ignore + } + + } + + mManifestData.mFeatures.add(feature); + } + break; + case LEVEL_INSIDE_APPLICATION: + if (AndroidManifest.NODE_ACTIVITY.equals(localName)) { + processActivityNode(attributes); + mValidLevel++; + } else if (AndroidManifest.NODE_SERVICE.equals(localName)) { + processNode(attributes, SdkConstants.CLASS_SERVICE); + mValidLevel++; + } else if (AndroidManifest.NODE_RECEIVER.equals(localName)) { + processNode(attributes, SdkConstants.CLASS_BROADCASTRECEIVER); + mValidLevel++; + } else if (AndroidManifest.NODE_PROVIDER.equals(localName)) { + processNode(attributes, SdkConstants.CLASS_CONTENTPROVIDER); + mValidLevel++; + } else if (AndroidManifest.NODE_USES_LIBRARY.equals(localName)) { + value = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_NAME, + true /* hasNamespace */); + if (value != null) { + UsesLibrary library = new UsesLibrary(); + library.mName = value; + + // read the required attribute + value = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_REQUIRED, + true /*hasNamespace*/); + if (value != null) { + Boolean b = Boolean.valueOf(value); + if (b != null) { + library.mRequired = b; + } + } + + mManifestData.mLibraries.add(library); + } + } + break; + case LEVEL_INSIDE_APP_COMPONENT: + // only process this level if we are in an activity + if (mCurrentActivity != null && + AndroidManifest.NODE_INTENT.equals(localName)) { + mCurrentActivity.resetIntentFilter(); + mValidLevel++; + } + break; + case LEVEL_INSIDE_INTENT_FILTER: + if (mCurrentActivity != null) { + if (AndroidManifest.NODE_ACTION.equals(localName)) { + // get the name attribute + String action = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_NAME, + true /* hasNamespace */); + if (action != null) { + mCurrentActivity.setHasAction(true); + mCurrentActivity.setHasMainAction( + ACTION_MAIN.equals(action)); + } + } else if (AndroidManifest.NODE_CATEGORY.equals(localName)) { + String category = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_NAME, + true /* hasNamespace */); + if (CATEGORY_LAUNCHER.equals(category)) { + mCurrentActivity.setHasLauncherCategory(true); + } + } + + // no need to increase mValidLevel as we don't process anything + // below this level. + } + break; + } + } + + mCurrentLevel++; + } finally { + super.startElement(uri, localName, name, attributes); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, + * java.lang.String) + */ + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + try { + if (mManifestData == null) { + return; + } + + // decrement the levels. + if (mValidLevel == mCurrentLevel) { + mValidLevel--; + } + mCurrentLevel--; + + // if we're at a valid level + // process the end of the element + if (mValidLevel == mCurrentLevel) { + switch (mValidLevel) { + case LEVEL_INSIDE_APPLICATION: + mCurrentActivity = null; + break; + case LEVEL_INSIDE_APP_COMPONENT: + // if we found both a main action and a launcher category, this is our + // launcher activity! + if (mManifestData.mLauncherActivity == null && + mCurrentActivity != null && + mCurrentActivity.isHomeActivity() && + mCurrentActivity.isExported()) { + mManifestData.mLauncherActivity = mCurrentActivity; + } + break; + default: + break; + } + + } + } finally { + super.endElement(uri, localName, name); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) + */ + @Override + public void error(SAXParseException e) { + if (mErrorHandler != null) { + mErrorHandler.handleError(e, e.getLineNumber()); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException) + */ + @Override + public void fatalError(SAXParseException e) { + if (mErrorHandler != null) { + mErrorHandler.handleError(e, e.getLineNumber()); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException) + */ + @Override + public void warning(SAXParseException e) throws SAXException { + if (mErrorHandler != null) { + mErrorHandler.warning(e); + } + } + + /** + * Processes the activity node. + * @param attributes the attributes for the activity node. + */ + private void processActivityNode(Attributes attributes) { + // lets get the activity name, and add it to the list + String activityName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME, + true /* hasNamespace */); + if (activityName != null) { + activityName = AndroidManifest.combinePackageAndClassName(mManifestData.mPackage, + activityName); + + // get the exported flag. + String exportedStr = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_EXPORTED, true); + boolean exported = exportedStr == null || + exportedStr.toLowerCase(Locale.US).equals("true"); //$NON-NLS-1$ + mCurrentActivity = new Activity(activityName, exported); + mManifestData.mActivities.add(mCurrentActivity); + + if (mErrorHandler != null) { + mErrorHandler.checkClass(mLocator, activityName, SdkConstants.CLASS_ACTIVITY, + true /* testVisibility */); + } + } else { + // no activity found! Aapt will output an error, + // so we don't have to do anything + mCurrentActivity = null; + } + + String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS, + true /* hasNamespace */); + if (processName != null) { + mManifestData.addProcessName(processName); + } + } + + /** + * Processes the service/receiver/provider nodes. + * @param attributes the attributes for the activity node. + * @param superClassName the fully qualified name of the super class that this + * node is representing + */ + private void processNode(Attributes attributes, String superClassName) { + // lets get the class name, and check it if required. + String serviceName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME, + true /* hasNamespace */); + if (serviceName != null) { + serviceName = AndroidManifest.combinePackageAndClassName(mManifestData.mPackage, + serviceName); + + if (mErrorHandler != null) { + mErrorHandler.checkClass(mLocator, serviceName, superClassName, + false /* testVisibility */); + } + } + + String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS, + true /* hasNamespace */); + if (processName != null) { + mManifestData.addProcessName(processName); + } + } + + /** + * Processes the instrumentation node. + * @param attributes the attributes for the instrumentation node. + */ + private void processInstrumentationNode(Attributes attributes) { + // lets get the class name, and check it if required. + String instrumentationName = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_NAME, + true /* hasNamespace */); + if (instrumentationName != null) { + String instrClassName = AndroidManifest.combinePackageAndClassName( + mManifestData.mPackage, instrumentationName); + String targetPackage = getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_TARGET_PACKAGE, + true /* hasNamespace */); + mManifestData.mInstrumentations.add( + new Instrumentation(instrClassName, targetPackage)); + if (mErrorHandler != null) { + mErrorHandler.checkClass(mLocator, instrClassName, + SdkConstants.CLASS_INSTRUMENTATION, true /* testVisibility */); + } + } + } + + /** + * Processes the supports-screens node. + * @param attributes the attributes for the supports-screens node. + */ + private void processSupportsScreensNode(Attributes attributes) { + mManifestData.mSupportsScreensFromManifest = new SupportsScreens(); + + mManifestData.mSupportsScreensFromManifest.setResizeable(getAttributeBooleanValue( + attributes, AndroidManifest.ATTRIBUTE_RESIZEABLE, true /*hasNamespace*/)); + + mManifestData.mSupportsScreensFromManifest.setAnyDensity(getAttributeBooleanValue( + attributes, AndroidManifest.ATTRIBUTE_ANYDENSITY, true /*hasNamespace*/)); + + mManifestData.mSupportsScreensFromManifest.setSmallScreens(getAttributeBooleanValue( + attributes, AndroidManifest.ATTRIBUTE_SMALLSCREENS, true /*hasNamespace*/)); + + mManifestData.mSupportsScreensFromManifest.setNormalScreens(getAttributeBooleanValue( + attributes, AndroidManifest.ATTRIBUTE_NORMALSCREENS, true /*hasNamespace*/)); + + mManifestData.mSupportsScreensFromManifest.setLargeScreens(getAttributeBooleanValue( + attributes, AndroidManifest.ATTRIBUTE_LARGESCREENS, true /*hasNamespace*/)); + } + + /** + * Processes the supports-screens node. + * @param attributes the attributes for the supports-screens node. + */ + private void processUsesConfiguration(Attributes attributes) { + mManifestData.mUsesConfiguration = new UsesConfiguration(); + + mManifestData.mUsesConfiguration.mReqFiveWayNav = getAttributeBooleanValue( + attributes, + AndroidManifest.ATTRIBUTE_REQ_5WAYNAV, true /*hasNamespace*/); + mManifestData.mUsesConfiguration.mReqNavigation = Navigation.getEnum( + getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_REQ_NAVIGATION, true /*hasNamespace*/)); + mManifestData.mUsesConfiguration.mReqHardKeyboard = getAttributeBooleanValue( + attributes, + AndroidManifest.ATTRIBUTE_REQ_HARDKEYBOARD, true /*hasNamespace*/); + mManifestData.mUsesConfiguration.mReqKeyboardType = Keyboard.getEnum( + getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_REQ_KEYBOARDTYPE, true /*hasNamespace*/)); + mManifestData.mUsesConfiguration.mReqTouchScreen = TouchScreen.getEnum( + getAttributeValue(attributes, + AndroidManifest.ATTRIBUTE_REQ_TOUCHSCREEN, true /*hasNamespace*/)); + } + + /** + * Searches through the attributes list for a particular one and returns its value. + * @param attributes the attribute list to search through + * @param attributeName the name of the attribute to look for. + * @param hasNamespace Indicates whether the attribute has an android namespace. + * @return a String with the value or null if the attribute was not found. + * @see SdkConstants#NS_RESOURCES + */ + private String getAttributeValue(Attributes attributes, String attributeName, + boolean hasNamespace) { + int count = attributes.getLength(); + for (int i = 0 ; i < count ; i++) { + if (attributeName.equals(attributes.getLocalName(i)) && + ((hasNamespace && + SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) || + (hasNamespace == false && attributes.getURI(i).length() == 0))) { + return attributes.getValue(i); + } + } + + return null; + } + + /** + * Searches through the attributes list for a particular one and returns its value as a + * Boolean. If the attribute is not present, this will return null. + * @param attributes the attribute list to search through + * @param attributeName the name of the attribute to look for. + * @param hasNamespace Indicates whether the attribute has an android namespace. + * @return a String with the value or null if the attribute was not found. + * @see SdkConstants#NS_RESOURCES + */ + private Boolean getAttributeBooleanValue(Attributes attributes, String attributeName, + boolean hasNamespace) { + int count = attributes.getLength(); + for (int i = 0 ; i < count ; i++) { + if (attributeName.equals(attributes.getLocalName(i)) && + ((hasNamespace && + SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) || + (hasNamespace == false && attributes.getURI(i).length() == 0))) { + String attr = attributes.getValue(i); + if (attr != null) { + return Boolean.valueOf(attr); + } else { + return null; + } + } + } + + return null; + } + + } + + private final static SAXParserFactory sParserFactory; + + static { + sParserFactory = SAXParserFactory.newInstance(); + sParserFactory.setNamespaceAware(true); + } + + /** + * Parses the Android Manifest, and returns a {@link ManifestData} object containing the + * result of the parsing. + * + * @param manifestFile the {@link IAbstractFile} representing the manifest file. + * @param gatherData indicates whether the parsing will extract data from the manifest. If false + * the method will always return null. + * @param errorHandler an optional errorHandler. + * @return A class containing the manifest info obtained during the parsing, or null on error. + * + * @throws StreamException + * @throws IOException + * @throws SAXException + * @throws ParserConfigurationException + */ + public static ManifestData parse( + IAbstractFile manifestFile, + boolean gatherData, + ManifestErrorHandler errorHandler) + throws SAXException, IOException, StreamException, ParserConfigurationException { + if (manifestFile != null) { + SAXParser parser = sParserFactory.newSAXParser(); + + ManifestData data = null; + if (gatherData) { + data = new ManifestData(); + } + + ManifestHandler manifestHandler = new ManifestHandler(manifestFile, + data, errorHandler); + parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); + + return data; + } + + return null; + } + + /** + * Parses the Android Manifest, and returns an object containing the result of the parsing. + * + * <p/> + * This is the equivalent of calling <pre>parse(manifestFile, true, null)</pre> + * + * @param manifestFile the manifest file to parse. + * + * @throws ParserConfigurationException + * @throws StreamException + * @throws IOException + * @throws SAXException + */ + public static ManifestData parse(IAbstractFile manifestFile) + throws SAXException, IOException, StreamException, ParserConfigurationException { + return parse(manifestFile, true, null); + } + + public static ManifestData parse(IAbstractFolder projectFolder) + throws SAXException, IOException, StreamException, ParserConfigurationException { + IAbstractFile manifestFile = AndroidManifest.getManifest(projectFolder); + if (manifestFile == null) { + throw new FileNotFoundException(); + } + + return parse(manifestFile, true, null); + } + + /** + * Parses the Android Manifest from an {@link InputStream}, and returns a {@link ManifestData} + * object containing the result of the parsing. + * + * @param manifestFileStream the {@link InputStream} representing the manifest file. + * @return A class containing the manifest info obtained during the parsing or null on error. + * + * @throws StreamException + * @throws IOException + * @throws SAXException + * @throws ParserConfigurationException + */ + public static ManifestData parse(InputStream manifestFileStream) + throws SAXException, IOException, StreamException, ParserConfigurationException { + if (manifestFileStream != null) { + SAXParser parser = sParserFactory.newSAXParser(); + + ManifestData data = new ManifestData(); + + ManifestHandler manifestHandler = new ManifestHandler(null, data, null); + parser.parse(new InputSource(manifestFileStream), manifestHandler); + + return data; + } + + return null; + } +} diff --git a/ide_common/src/com/android/ide/common/xml/ManifestData.java b/ide_common/src/com/android/ide/common/xml/ManifestData.java new file mode 100644 index 0000000..9b68d60 --- /dev/null +++ b/ide_common/src/com/android/ide/common/xml/ManifestData.java @@ -0,0 +1,747 @@ +/* + * Copyright (C) 2010 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.ide.common.xml; + +import com.android.resources.Keyboard; +import com.android.resources.Navigation; +import com.android.resources.TouchScreen; + +import java.util.ArrayList; +import java.util.Set; +import java.util.TreeSet; + +/** + * Class containing the manifest info obtained during the parsing. + */ +public final class ManifestData { + + /** + * Value returned by {@link #getMinSdkVersion()} when the value of the minSdkVersion attribute + * in the manifest is a codename and not an integer value. + */ + public final static int MIN_SDK_CODENAME = 0; + + /** + * Value returned by {@link #getGlEsVersion()} when there are no <uses-feature> node with the + * attribute glEsVersion set. + */ + public final static int GL_ES_VERSION_NOT_SET = -1; + + /** Application package */ + String mPackage; + /** Application version Code, null if the attribute is not present. */ + Integer mVersionCode = null; + /** List of all activities */ + final ArrayList<Activity> mActivities = new ArrayList<Activity>(); + /** Launcher activity */ + Activity mLauncherActivity = null; + /** list of process names declared by the manifest */ + Set<String> mProcesses = null; + /** debuggable attribute value. If null, the attribute is not present. */ + Boolean mDebuggable = null; + /** API level requirement. if null the attribute was not present. */ + private String mMinSdkVersionString = null; + /** API level requirement. Default is 1 even if missing. If value is a codename, then it'll be + * 0 instead. */ + private int mMinSdkVersion = 1; + private int mTargetSdkVersion = 0; + /** List of all instrumentations declared by the manifest */ + final ArrayList<Instrumentation> mInstrumentations = + new ArrayList<Instrumentation>(); + /** List of all libraries in use declared by the manifest */ + final ArrayList<UsesLibrary> mLibraries = new ArrayList<UsesLibrary>(); + /** List of all feature in use declared by the manifest */ + final ArrayList<UsesFeature> mFeatures = new ArrayList<UsesFeature>(); + + SupportsScreens mSupportsScreensFromManifest; + SupportsScreens mSupportsScreensValues; + UsesConfiguration mUsesConfiguration; + + /** + * Instrumentation info obtained from manifest + */ + public final static class Instrumentation { + private final String mName; + private final String mTargetPackage; + + Instrumentation(String name, String targetPackage) { + mName = name; + mTargetPackage = targetPackage; + } + + /** + * Returns the fully qualified instrumentation class name + */ + public String getName() { + return mName; + } + + /** + * Returns the Android app package that is the target of this instrumentation + */ + public String getTargetPackage() { + return mTargetPackage; + } + } + + /** + * Activity info obtained from the manifest. + */ + public final static class Activity { + private final String mName; + private final boolean mIsExported; + private boolean mHasAction = false; + private boolean mHasMainAction = false; + private boolean mHasLauncherCategory = false; + + public Activity(String name, boolean exported) { + mName = name; + mIsExported = exported; + } + + public String getName() { + return mName; + } + + public boolean isExported() { + return mIsExported; + } + + public boolean hasAction() { + return mHasAction; + } + + public boolean isHomeActivity() { + return mHasMainAction && mHasLauncherCategory; + } + + void setHasAction(boolean hasAction) { + mHasAction = hasAction; + } + + /** If the activity doesn't yet have a filter set for the launcher, this resets both + * flags. This is to handle multiple intent-filters where one could have the valid + * action, and another one of the valid category. + */ + void resetIntentFilter() { + if (isHomeActivity() == false) { + mHasMainAction = mHasLauncherCategory = false; + } + } + + void setHasMainAction(boolean hasMainAction) { + mHasMainAction = hasMainAction; + } + + void setHasLauncherCategory(boolean hasLauncherCategory) { + mHasLauncherCategory = hasLauncherCategory; + } + } + + /** + * Class representing the <code>supports-screens</code> node in the manifest. + * By default, all the getters will return null if there was no value defined in the manifest. + * + * To get an instance with all the actual values, use {@link #resolveSupportsScreensValues(int)} + */ + public final static class SupportsScreens { + private Boolean mResizeable; + private Boolean mAnyDensity; + private Boolean mSmallScreens; + private Boolean mNormalScreens; + private Boolean mLargeScreens; + + public SupportsScreens() { + } + + /** + * Instantiate an instance from a string. The string must have been created with + * {@link #getEncodedValues()}. + * @param value the string. + */ + public SupportsScreens(String value) { + String[] values = value.split("\\|"); + + mAnyDensity = Boolean.valueOf(values[0]); + mResizeable = Boolean.valueOf(values[1]); + mSmallScreens = Boolean.valueOf(values[2]); + mNormalScreens = Boolean.valueOf(values[3]); + mLargeScreens = Boolean.valueOf(values[4]); + } + + /** + * Returns an instance of {@link SupportsScreens} initialized with the default values + * based on the given targetSdkVersion. + * @param targetSdkVersion + */ + public static SupportsScreens getDefaultValues(int targetSdkVersion) { + SupportsScreens result = new SupportsScreens(); + + result.mNormalScreens = Boolean.TRUE; + // Screen size and density became available in Android 1.5/API3, so before that + // non normal screens were not supported by default. After they are considered + // supported. + result.mResizeable = result.mAnyDensity = result.mSmallScreens = result.mLargeScreens = + targetSdkVersion <= 3 ? Boolean.FALSE : Boolean.TRUE; + + return result; + } + + /** + * Returns a version of the receiver for which all values have been set, even if they + * were not present in the manifest. + * @param targetSdkVersion the target api level of the app, since this has an effect + * on default values. + */ + public SupportsScreens resolveSupportsScreensValues(int targetSdkVersion) { + SupportsScreens result = getDefaultValues(targetSdkVersion); + + // Override the default with the existing values: + if (mResizeable != null) result.mResizeable = mResizeable; + if (mAnyDensity != null) result.mAnyDensity = mAnyDensity; + if (mSmallScreens != null) result.mSmallScreens = mSmallScreens; + if (mNormalScreens != null) result.mNormalScreens = mNormalScreens; + if (mLargeScreens != null) result.mLargeScreens = mLargeScreens; + + return result; + } + + /** + * returns the value of the <code>resizeable</code> attribute or null if not present. + */ + public Boolean getResizeable() { + return mResizeable; + } + + void setResizeable(Boolean resizeable) { + mResizeable = getConstantBoolean(resizeable); + } + + /** + * returns the value of the <code>anyDensity</code> attribute or null if not present. + */ + public Boolean getAnyDensity() { + return mAnyDensity; + } + + void setAnyDensity(Boolean anyDensity) { + mAnyDensity = getConstantBoolean(anyDensity); + } + + /** + * returns the value of the <code>smallScreens</code> attribute or null if not present. + */ + public Boolean getSmallScreens() { + return mSmallScreens; + } + + void setSmallScreens(Boolean smallScreens) { + mSmallScreens = getConstantBoolean(smallScreens); + } + + /** + * returns the value of the <code>normalScreens</code> attribute or null if not present. + */ + public Boolean getNormalScreens() { + return mNormalScreens; + } + + void setNormalScreens(Boolean normalScreens) { + mNormalScreens = getConstantBoolean(normalScreens); + } + + /** + * returns the value of the <code>largeScreens</code> attribute or null if not present. + */ + public Boolean getLargeScreens() { + return mLargeScreens; + } + + void setLargeScreens(Boolean largeScreens) { + mLargeScreens = getConstantBoolean(largeScreens); + } + + /** + * Returns either {@link Boolean#TRUE} or {@link Boolean#FALSE} based on the value of + * the given Boolean object. + */ + private Boolean getConstantBoolean(Boolean v) { + if (v != null) { + if (v.equals(Boolean.TRUE)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SupportsScreens) { + SupportsScreens support = (SupportsScreens) obj; + // since all the fields are guaranteed to be either Boolean.TRUE or Boolean.FALSE + // (or null), we can simply check they are identical and not bother with + // calling equals (which would require to check != null. + // see #getConstanntBoolean(Boolean) + return mResizeable == support.mResizeable && + mAnyDensity == support.mAnyDensity && + mSmallScreens == support.mSmallScreens && + mNormalScreens == support.mNormalScreens && + mLargeScreens == support.mLargeScreens; + } + + return false; + } + + /* Override hashCode, mostly to make Eclipse happy and not warn about it. + * And if you ever put this in a Map or Set, it will avoid surprises. */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mAnyDensity == null) ? 0 : mAnyDensity.hashCode()); + result = prime * result + ((mLargeScreens == null) ? 0 : mLargeScreens.hashCode()); + result = prime * result + ((mNormalScreens == null) ? 0 : mNormalScreens.hashCode()); + result = prime * result + ((mResizeable == null) ? 0 : mResizeable.hashCode()); + result = prime * result + ((mSmallScreens == null) ? 0 : mSmallScreens.hashCode()); + return result; + } + + /** + * Returns true if the two instances support the same screen sizes. + * This is similar to {@link #equals(Object)} except that it ignores the values of + * {@link #getAnyDensity()} and {@link #getResizeable()}. + * @param support the other instance to compare to. + * @return true if the two instances support the same screen sizes. + */ + public boolean hasSameScreenSupportAs(SupportsScreens support) { + // since all the fields are guaranteed to be either Boolean.TRUE or Boolean.FALSE + // (or null), we can simply check they are identical and not bother with + // calling equals (which would require to check != null. + // see #getConstanntBoolean(Boolean) + + // This only checks that matter here are the screen sizes. resizeable and anyDensity + // are not checked. + return mSmallScreens == support.mSmallScreens && + mNormalScreens == support.mNormalScreens && + mLargeScreens == support.mLargeScreens; + } + + /** + * Returns true if the two instances have strictly different screen size support. + * This means that there is no screen size that they both support. + * @param support the other instance to compare to. + * @return true if they are stricly different. + */ + public boolean hasStrictlyDifferentScreenSupportAs(SupportsScreens support) { + // since all the fields are guaranteed to be either Boolean.TRUE or Boolean.FALSE + // (or null), we can simply check they are identical and not bother with + // calling equals (which would require to check != null. + // see #getConstanntBoolean(Boolean) + + // This only checks that matter here are the screen sizes. resizeable and anyDensity + // are not checked. + return (mSmallScreens != Boolean.TRUE || support.mSmallScreens != Boolean.TRUE) && + (mNormalScreens != Boolean.TRUE || support.mNormalScreens != Boolean.TRUE) && + (mLargeScreens != Boolean.TRUE || support.mLargeScreens != Boolean.TRUE); + } + + /** + * Comparison of 2 Supports-screens. This only uses screen sizes (ignores resizeable and + * anyDensity), and considers that + * {@link #hasStrictlyDifferentScreenSupportAs(SupportsScreens)} returns true and + * {@link #overlapWith(SupportsScreens)} returns false. + * @throws IllegalArgumentException if the two instanced are not strictly different or + * overlap each other + * @see #hasStrictlyDifferentScreenSupportAs(SupportsScreens) + * @see #overlapWith(SupportsScreens) + */ + public int compareScreenSizesWith(SupportsScreens o) { + if (hasStrictlyDifferentScreenSupportAs(o) == false) { + throw new IllegalArgumentException("The two instances are not strictly different."); + } + if (overlapWith(o)) { + throw new IllegalArgumentException("The two instances overlap each other."); + } + + int comp = mLargeScreens.compareTo(o.mLargeScreens); + if (comp != 0) return comp; + + comp = mNormalScreens.compareTo(o.mNormalScreens); + if (comp != 0) return comp; + + comp = mSmallScreens.compareTo(o.mSmallScreens); + if (comp != 0) return comp; + + return 0; + } + + /** + * Returns a string encoding of the content of the instance. This string can be used to + * instantiate a {@link SupportsScreens} object through + * {@link #SupportsScreens(String)}. + */ + public String getEncodedValues() { + return String.format("%1$s|%2$s|%3$s|%4$s|%5$s", + mAnyDensity, mResizeable, mSmallScreens, mNormalScreens, mLargeScreens); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + boolean alreadyOutputSomething = false; + + if (Boolean.TRUE.equals(mSmallScreens)) { + alreadyOutputSomething = true; + sb.append("small"); + } + + if (Boolean.TRUE.equals(mNormalScreens)) { + if (alreadyOutputSomething) { + sb.append(", "); + } + alreadyOutputSomething = true; + sb.append("normal"); + } + + if (Boolean.TRUE.equals(mLargeScreens)) { + if (alreadyOutputSomething) { + sb.append(", "); + } + alreadyOutputSomething = true; + sb.append("large"); + } + + if (alreadyOutputSomething == false) { + sb.append("<none>"); + } + + return sb.toString(); + } + + /** + * Returns true if the two instance overlap with each other. + * This can happen if one instances supports a size, when the other instance doesn't while + * supporting a size above and a size below. + * @param otherSS the other supports-screens to compare to. + */ + public boolean overlapWith(SupportsScreens otherSS) { + if (mSmallScreens == null || mNormalScreens == null || mLargeScreens == null || + otherSS.mSmallScreens == null || otherSS.mNormalScreens == null || + otherSS.mLargeScreens == null) { + throw new IllegalArgumentException("Some screen sizes Boolean are not initialized"); + } + + if (mSmallScreens == Boolean.TRUE && mNormalScreens == Boolean.FALSE && + mLargeScreens == Boolean.TRUE) { + return otherSS.mNormalScreens == Boolean.TRUE; + } + + if (otherSS.mSmallScreens == Boolean.TRUE && otherSS.mNormalScreens == Boolean.FALSE && + otherSS.mLargeScreens == Boolean.TRUE) { + return mNormalScreens == Boolean.TRUE; + } + + return false; + } + } + + /** + * Class representing a <code>uses-library</code> node in the manifest. + */ + public final static class UsesLibrary { + String mName; + Boolean mRequired = Boolean.TRUE; // default is true even if missing + + public String getName() { + return mName; + } + + public Boolean getRequired() { + return mRequired; + } + } + + /** + * Class representing a <code>uses-feature</code> node in the manifest. + */ + public final static class UsesFeature { + String mName; + int mGlEsVersion = 0; + Boolean mRequired = Boolean.TRUE; // default is true even if missing + + public String getName() { + return mName; + } + + /** + * Returns the value of the glEsVersion attribute, or 0 if the attribute was not present. + */ + public int getGlEsVersion() { + return mGlEsVersion; + } + + public Boolean getRequired() { + return mRequired; + } + } + + /** + * Class representing the <code>uses-configuration</code> node in the manifest. + */ + public final static class UsesConfiguration { + Boolean mReqFiveWayNav; + Boolean mReqHardKeyboard; + Keyboard mReqKeyboardType; + TouchScreen mReqTouchScreen; + Navigation mReqNavigation; + + /** + * returns the value of the <code>reqFiveWayNav</code> attribute or null if not present. + */ + public Boolean getReqFiveWayNav() { + return mReqFiveWayNav; + } + + /** + * returns the value of the <code>reqNavigation</code> attribute or null if not present. + */ + public Navigation getReqNavigation() { + return mReqNavigation; + } + + /** + * returns the value of the <code>reqHardKeyboard</code> attribute or null if not present. + */ + public Boolean getReqHardKeyboard() { + return mReqHardKeyboard; + } + + /** + * returns the value of the <code>reqKeyboardType</code> attribute or null if not present. + */ + public Keyboard getReqKeyboardType() { + return mReqKeyboardType; + } + + /** + * returns the value of the <code>reqTouchScreen</code> attribute or null if not present. + */ + public TouchScreen getReqTouchScreen() { + return mReqTouchScreen; + } + } + + /** + * Returns the package defined in the manifest, if found. + * @return The package name or null if not found. + */ + public String getPackage() { + return mPackage; + } + + /** + * Returns the versionCode value defined in the manifest, if found, null otherwise. + * @return the versionCode or null if not found. + */ + public Integer getVersionCode() { + return mVersionCode; + } + + /** + * Returns the list of activities found in the manifest. + * @return An array of fully qualified class names, or empty if no activity were found. + */ + public Activity[] getActivities() { + return mActivities.toArray(new Activity[mActivities.size()]); + } + + /** + * Returns the name of one activity found in the manifest, that is configured to show + * up in the HOME screen. + * @return the fully qualified name of a HOME activity or null if none were found. + */ + public Activity getLauncherActivity() { + return mLauncherActivity; + } + + /** + * Returns the list of process names declared by the manifest. + */ + public String[] getProcesses() { + if (mProcesses != null) { + return mProcesses.toArray(new String[mProcesses.size()]); + } + + return new String[0]; + } + + /** + * Returns the <code>debuggable</code> attribute value or null if it is not set. + */ + public Boolean getDebuggable() { + return mDebuggable; + } + + /** + * Returns the <code>minSdkVersion</code> attribute, or null if it's not set. + */ + public String getMinSdkVersionString() { + return mMinSdkVersionString; + } + + /** + * Sets the value of the <code>minSdkVersion</code> attribute. + * @param minSdkVersion the string value of the attribute in the manifest. + */ + public void setMinSdkVersionString(String minSdkVersion) { + mMinSdkVersionString = minSdkVersion; + if (mMinSdkVersionString != null) { + try { + mMinSdkVersion = Integer.parseInt(mMinSdkVersionString); + } catch (NumberFormatException e) { + mMinSdkVersion = MIN_SDK_CODENAME; + } + } + } + + /** + * Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set or is a codename. + * @see #getMinSdkVersionString() + */ + public int getMinSdkVersion() { + return mMinSdkVersion; + } + + + /** + * Sets the value of the <code>minSdkVersion</code> attribute. + * @param targetSdkVersion the string value of the attribute in the manifest. + */ + public void setTargetSdkVersionString(String targetSdkVersion) { + if (targetSdkVersion != null) { + try { + mTargetSdkVersion = Integer.parseInt(targetSdkVersion); + } catch (NumberFormatException e) { + // keep the value at 0. + } + } + } + + /** + * Returns the <code>targetSdkVersion</code> attribute, or the same value as + * {@link #getMinSdkVersion()} if it was not set in the manifest. + */ + public int getTargetSdkVersion() { + if (mTargetSdkVersion == 0) { + return getMinSdkVersion(); + } + + return mTargetSdkVersion; + } + + /** + * Returns the list of instrumentations found in the manifest. + * @return An array of {@link Instrumentation}, or empty if no instrumentations were + * found. + */ + public Instrumentation[] getInstrumentations() { + return mInstrumentations.toArray(new Instrumentation[mInstrumentations.size()]); + } + + /** + * Returns the list of libraries in use found in the manifest. + * @return An array of {@link UsesLibrary} objects, or empty if no libraries were found. + */ + public UsesLibrary[] getUsesLibraries() { + return mLibraries.toArray(new UsesLibrary[mLibraries.size()]); + } + + /** + * Returns the list of features in use found in the manifest. + * @return An array of {@link UsesFeature} objects, or empty if no libraries were found. + */ + public UsesFeature[] getUsesFeatures() { + return mFeatures.toArray(new UsesFeature[mFeatures.size()]); + } + + /** + * Returns the glEsVersion from a <uses-feature> or {@link #GL_ES_VERSION_NOT_SET} if not set. + */ + public int getGlEsVersion() { + for (UsesFeature feature : mFeatures) { + if (feature.mGlEsVersion > 0) { + return feature.mGlEsVersion; + } + } + return GL_ES_VERSION_NOT_SET; + } + + /** + * Returns the {@link SupportsScreens} object representing the <code>supports-screens</code> + * node, or null if the node doesn't exist at all. + * Some values in the {@link SupportsScreens} instance maybe null, indicating that they + * were not present in the manifest. To get an instance that contains the values, as seen + * by the Android platform when the app is running, use {@link #getSupportsScreensValues()}. + */ + public SupportsScreens getSupportsScreensFromManifest() { + return mSupportsScreensFromManifest; + } + + /** + * Returns an always non-null instance of {@link SupportsScreens} that's been initialized with + * the default values, and the values from the manifest. + * The default values depends on the manifest values for minSdkVersion and targetSdkVersion. + */ + public synchronized SupportsScreens getSupportsScreensValues() { + if (mSupportsScreensValues == null) { + if (mSupportsScreensFromManifest == null) { + mSupportsScreensValues = SupportsScreens.getDefaultValues(getTargetSdkVersion()); + } else { + // get a SupportsScreen that replace the missing values with default values. + mSupportsScreensValues = mSupportsScreensFromManifest.resolveSupportsScreensValues( + getTargetSdkVersion()); + } + } + + return mSupportsScreensValues; + } + + /** + * Returns the {@link UsesConfiguration} object representing the <code>uses-configuration</code> + * node, or null if the node doesn't exist at all. + */ + public UsesConfiguration getUsesConfiguration() { + return mUsesConfiguration; + } + + void addProcessName(String processName) { + if (mProcesses == null) { + mProcesses = new TreeSet<String>(); + } + + if (processName.startsWith(":")) { + mProcesses.add(mPackage + processName); + } else { + mProcesses.add(processName); + } + } + +} |