/* * 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 static final int MIN_SDK_CODENAME = 0; /** * Value returned by {@link #getGlEsVersion()} when there are no <uses-feature> node with the * attribute glEsVersion set. */ public static final 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 static final 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 static final 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 static final 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 strictly 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 static final 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 static final 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 static final 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); } } }