aboutsummaryrefslogtreecommitdiffstats
path: root/sdk_common/src
diff options
context:
space:
mode:
authorSiva Velusamy <vsiva@google.com>2012-09-18 14:51:46 -0700
committerSiva Velusamy <vsiva@google.com>2012-09-18 15:09:33 -0700
commit6837aad30d6c51783ca1dc784ca6bdcc8a3d9f2d (patch)
tree90af15ddcc9f0bbc3151a0978b3e637f77fdd54a /sdk_common/src
parent6184f12fa097e1c5bddfe50700b3b0740c736a5a (diff)
downloadsdk-6837aad30d6c51783ca1dc784ca6bdcc8a3d9f2d.zip
sdk-6837aad30d6c51783ca1dc784ca6bdcc8a3d9f2d.tar.gz
sdk-6837aad30d6c51783ca1dc784ca6bdcc8a3d9f2d.tar.bz2
Rename ide_common to sdk_common
Change-Id: I1b39ee439a532f3f6758be35b569948e2e906665
Diffstat (limited to 'sdk_common/src')
-rw-r--r--sdk_common/src/com/android/ide/common/rendering/LayoutLibrary.java755
-rw-r--r--sdk_common/src/com/android/ide/common/rendering/StaticRenderSession.java63
-rw-r--r--sdk_common/src/com/android/ide/common/rendering/legacy/ILegacyPullParser.java31
-rw-r--r--sdk_common/src/com/android/ide/common/rendering/legacy/LegacyCallback.java59
-rw-r--r--sdk_common/src/com/android/ide/common/resources/FrameworkResourceItem.java40
-rwxr-xr-xsdk_common/src/com/android/ide/common/resources/FrameworkResources.java237
-rw-r--r--sdk_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java240
-rw-r--r--sdk_common/src/com/android/ide/common/resources/IdResourceParser.java156
-rw-r--r--sdk_common/src/com/android/ide/common/resources/InlineResourceItem.java71
-rw-r--r--sdk_common/src/com/android/ide/common/resources/IntArrayWrapper.java53
-rw-r--r--sdk_common/src/com/android/ide/common/resources/MultiResourceFile.java223
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ResourceDeltaKind.java26
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ResourceFile.java98
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ResourceFolder.java353
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ResourceItem.java239
-rwxr-xr-xsdk_common/src/com/android/ide/common/resources/ResourceRepository.java719
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ResourceResolver.java560
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ScanningContext.java92
-rw-r--r--sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java142
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java308
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/Configurable.java28
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java158
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/DensityQualifier.java130
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/DeviceConfigHelper.java112
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java92
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java886
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java112
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java165
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java74
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java75
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java169
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java74
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java166
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java134
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java172
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/ScreenHeightQualifier.java181
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java73
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java70
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java74
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/ScreenWidthQualifier.java180
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/SmallestScreenWidthQualifier.java180
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java76
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java76
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/UiModeQualifier.java108
-rw-r--r--sdk_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java194
-rw-r--r--sdk_common/src/com/android/ide/common/sdk/LoadStatus.java24
-rw-r--r--sdk_common/src/com/android/ide/common/xml/AndroidManifestParser.java671
-rw-r--r--sdk_common/src/com/android/ide/common/xml/ManifestData.java747
48 files changed, 9666 insertions, 0 deletions
diff --git a/sdk_common/src/com/android/ide/common/rendering/LayoutLibrary.java b/sdk_common/src/com/android/ide/common/rendering/LayoutLibrary.java
new file mode 100644
index 0000000..0a353f9
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/rendering/LayoutLibrary.java
@@ -0,0 +1,755 @@
+/*
+ * 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.rendering;
+
+import static com.android.ide.common.rendering.api.Result.Status.ERROR_REFLECTION;
+
+import com.android.ide.common.rendering.api.Bridge;
+import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.ide.common.rendering.legacy.ILegacyPullParser;
+import com.android.ide.common.rendering.legacy.LegacyCallback;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.sdk.LoadStatus;
+import com.android.layoutlib.api.ILayoutBridge;
+import com.android.layoutlib.api.ILayoutLog;
+import com.android.layoutlib.api.ILayoutResult;
+import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
+import com.android.layoutlib.api.IProjectCallback;
+import com.android.layoutlib.api.IResourceValue;
+import com.android.layoutlib.api.IXmlPullParser;
+import com.android.resources.ResourceType;
+import com.android.utils.ILogger;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Class to use the Layout library.
+ * <p/>
+ * Use {@link #load(String, ILogger)} to load the jar file.
+ * <p/>
+ * Use the layout library with:
+ * {@link #init(String, Map)}, {@link #supports(Capability)}, {@link #createSession(SessionParams)},
+ * {@link #dispose()}, {@link #clearCaches(Object)}.
+ *
+ * <p/>
+ * For client wanting to access both new and old (pre API level 5) layout libraries, it is
+ * important that the following interfaces be used:<br>
+ * {@link ILegacyPullParser} instead of {@link ILayoutPullParser}<br>
+ * {@link LegacyCallback} instead of {@link com.android.ide.common.rendering.api.IProjectCallback}.
+ * <p/>
+ * These interfaces will ensure that both new and older Layout libraries can be accessed.
+ */
+@SuppressWarnings("deprecation")
+public class LayoutLibrary {
+
+ public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$
+
+ /** Link to the layout bridge */
+ private final Bridge mBridge;
+ /** Link to a ILayoutBridge in case loaded an older library */
+ private final ILayoutBridge mLegacyBridge;
+ /** Status of the layoutlib.jar loading */
+ private final LoadStatus mStatus;
+ /** Message associated with the {@link LoadStatus}. This is mostly used when
+ * {@link #getStatus()} returns {@link LoadStatus#FAILED}.
+ */
+ private final String mLoadMessage;
+ /** classloader used to load the jar file */
+ private final ClassLoader mClassLoader;
+
+ // Reflection data for older Layout Libraries.
+ private Method mViewGetParentMethod;
+ private Method mViewGetBaselineMethod;
+ private Method mViewParentIndexOfChildMethod;
+ private Class<?> mMarginLayoutParamClass;
+ private Field mLeftMarginField;
+ private Field mTopMarginField;
+ private Field mRightMarginField;
+ private Field mBottomMarginField;
+
+ /**
+ * Returns the {@link LoadStatus} of the loading of the layoutlib jar file.
+ */
+ public LoadStatus getStatus() {
+ return mStatus;
+ }
+
+ /** Returns the message associated with the {@link LoadStatus}. This is mostly used when
+ * {@link #getStatus()} returns {@link LoadStatus#FAILED}.
+ */
+ public String getLoadMessage() {
+ return mLoadMessage;
+ }
+
+ /**
+ * Returns the classloader used to load the classes in the layoutlib jar file.
+ */
+ public ClassLoader getClassLoader() {
+ return mClassLoader;
+ }
+
+ /**
+ * Loads the layoutlib.jar file located at the given path and returns a {@link LayoutLibrary}
+ * object representing the result.
+ * <p/>
+ * If loading failed {@link #getStatus()} will reflect this, and {@link #getBridge()} will
+ * return null.
+ *
+ * @param layoutLibJarOsPath the path of the jar file
+ * @param log an optional log file.
+ * @return a {@link LayoutLibrary} object always.
+ */
+ public static LayoutLibrary load(String layoutLibJarOsPath, ILogger log, String toolName) {
+
+ LoadStatus status = LoadStatus.LOADING;
+ String message = null;
+ Bridge bridge = null;
+ ILayoutBridge legacyBridge = null;
+ ClassLoader classLoader = null;
+
+ try {
+ // get the URL for the file.
+ File f = new File(layoutLibJarOsPath);
+ if (f.isFile() == false) {
+ if (log != null) {
+ log.error(null, "layoutlib.jar is missing!"); //$NON-NLS-1$
+ }
+ } else {
+ URI uri = f.toURI();
+ URL url = uri.toURL();
+
+ // create a class loader. Because this jar reference interfaces
+ // that are in the editors plugin, it's important to provide
+ // a parent class loader.
+ classLoader = new URLClassLoader(
+ new URL[] { url },
+ LayoutLibrary.class.getClassLoader());
+
+ // load the class
+ Class<?> clazz = classLoader.loadClass(CLASS_BRIDGE);
+ if (clazz != null) {
+ // instantiate an object of the class.
+ Constructor<?> constructor = clazz.getConstructor();
+ if (constructor != null) {
+ Object bridgeObject = constructor.newInstance();
+ if (bridgeObject instanceof Bridge) {
+ bridge = (Bridge)bridgeObject;
+ } else if (bridgeObject instanceof ILayoutBridge) {
+ legacyBridge = (ILayoutBridge) bridgeObject;
+ }
+ }
+ }
+
+ if (bridge == null && legacyBridge == null) {
+ status = LoadStatus.FAILED;
+ message = "Failed to load " + CLASS_BRIDGE; //$NON-NLS-1$
+ if (log != null) {
+ log.error(null,
+ "Failed to load " + //$NON-NLS-1$
+ CLASS_BRIDGE +
+ " from " + //$NON-NLS-1$
+ layoutLibJarOsPath);
+ }
+ } else {
+ // mark the lib as loaded, unless it's overridden below.
+ status = LoadStatus.LOADED;
+
+ // check the API, only if it's not a legacy bridge
+ if (bridge != null) {
+ int api = bridge.getApiLevel();
+ if (api > Bridge.API_CURRENT) {
+ status = LoadStatus.FAILED;
+ message = String.format(
+ "This version of the rendering library is more recent than your version of %1$s. Please update %1$s", toolName);
+ }
+ }
+ }
+ }
+ } catch (Throwable t) {
+ status = LoadStatus.FAILED;
+ Throwable cause = t;
+ while (cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+ message = "Failed to load the LayoutLib: " + cause.getMessage();
+ // log the error.
+ if (log != null) {
+ log.error(t, message);
+ }
+ }
+
+ return new LayoutLibrary(bridge, legacyBridge, classLoader, status, message);
+ }
+
+ // ------ Layout Lib API proxy
+
+ /**
+ * Returns the API level of the layout library.
+ */
+ public int getApiLevel() {
+ if (mBridge != null) {
+ return mBridge.getApiLevel();
+ }
+
+ if (mLegacyBridge != null) {
+ return getLegacyApiLevel();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the revision of the library inside a given (layoutlib) API level.
+ * The true version number of the library is {@link #getApiLevel()}.{@link #getRevision()}
+ */
+ public int getRevision() {
+ if (mBridge != null) {
+ return mBridge.getRevision();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns whether the LayoutLibrary supports a given {@link Capability}.
+ * @return true if it supports it.
+ *
+ * @see Bridge#getCapabilities()
+ *
+ */
+ public boolean supports(Capability capability) {
+ if (mBridge != null) {
+ return mBridge.getCapabilities().contains(capability);
+ }
+
+ if (mLegacyBridge != null) {
+ switch (capability) {
+ case UNBOUND_RENDERING:
+ // legacy stops at 4. 5 is new API.
+ return getLegacyApiLevel() == 4;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Initializes the Layout Library object. This must be called before any other action is taken
+ * on the instance.
+ *
+ * @param platformProperties The build properties for the platform.
+ * @param fontLocation the location of the fonts in the SDK target.
+ * @param enumValueMap map attrName => { map enumFlagName => Integer value }. This is typically
+ * read from attrs.xml in the SDK target.
+ * @param log a {@link LayoutLog} object. Can be null.
+ * @return true if success.
+ *
+ * @see Bridge#init(String, Map)
+ */
+ public boolean init(Map<String, String> platformProperties,
+ File fontLocation,
+ Map<String, Map<String, Integer>> enumValueMap,
+ LayoutLog log) {
+ if (mBridge != null) {
+ return mBridge.init(platformProperties, fontLocation, enumValueMap, log);
+ } else if (mLegacyBridge != null) {
+ return mLegacyBridge.init(fontLocation.getAbsolutePath(), enumValueMap);
+ }
+
+ return false;
+ }
+
+ /**
+ * Prepares the layoutlib to unloaded.
+ *
+ * @see Bridge#dispose()
+ */
+ public boolean dispose() {
+ if (mBridge != null) {
+ return mBridge.dispose();
+ }
+
+ return true;
+ }
+
+ /**
+ * Starts a layout session by inflating and rendering it. The method returns a
+ * {@link RenderSession} on which further actions can be taken.
+ * <p/>
+ * Before taking further actions on the scene, it is recommended to use
+ * {@link #supports(Capability)} to check what the scene can do.
+ *
+ * @return a new {@link ILayoutScene} object that contains the result of the scene creation and
+ * first rendering or null if {@link #getStatus()} doesn't return {@link LoadStatus#LOADED}.
+ *
+ * @see Bridge#createSession(SessionParams)
+ */
+ public RenderSession createSession(SessionParams params) {
+ if (mBridge != null) {
+ RenderSession session = mBridge.createSession(params);
+ if (params.getExtendedViewInfoMode() &&
+ mBridge.getCapabilities().contains(Capability.EXTENDED_VIEWINFO) == false) {
+ // Extended view info was requested but the layoutlib does not support it.
+ // Add it manually.
+ List<ViewInfo> infoList = session.getRootViews();
+ if (infoList != null) {
+ for (ViewInfo info : infoList) {
+ addExtendedViewInfo(info);
+ }
+ }
+ }
+
+ return session;
+ } else if (mLegacyBridge != null) {
+ return createLegacySession(params);
+ }
+
+ return null;
+ }
+
+ /**
+ * Renders a Drawable. If the rendering is successful, the result image is accessible through
+ * {@link Result#getData()}. It is of type {@link BufferedImage}
+ * @param params the rendering parameters.
+ * @return the result of the action.
+ */
+ public Result renderDrawable(DrawableParams params) {
+ if (mBridge != null) {
+ return mBridge.renderDrawable(params);
+ }
+
+ return Status.NOT_IMPLEMENTED.createResult();
+ }
+
+ /**
+ * Clears the resource cache for a specific project.
+ * <p/>This cache contains bitmaps and nine patches that are loaded from the disk and reused
+ * until this method is called.
+ * <p/>The cache is not configuration dependent and should only be cleared when a
+ * resource changes (at this time only bitmaps and 9 patches go into the cache).
+ *
+ * @param projectKey the key for the project.
+ *
+ * @see Bridge#clearCaches(Object)
+ */
+ public void clearCaches(Object projectKey) {
+ if (mBridge != null) {
+ mBridge.clearCaches(projectKey);
+ } else if (mLegacyBridge != null) {
+ mLegacyBridge.clearCaches(projectKey);
+ }
+ }
+
+ /**
+ * Utility method returning the parent of a given view object.
+ *
+ * @param viewObject the object for which to return the parent.
+ *
+ * @return a {@link Result} indicating the status of the action, and if success, the parent
+ * object in {@link Result#getData()}
+ */
+ public Result getViewParent(Object viewObject) {
+ if (mBridge != null) {
+ Result r = mBridge.getViewParent(viewObject);
+ if (r.isSuccess()) {
+ return r;
+ }
+ }
+
+ return getViewParentWithReflection(viewObject);
+ }
+
+ /**
+ * Utility method returning the index of a given view in its parent.
+ * @param viewObject the object for which to return the index.
+ *
+ * @return a {@link Result} indicating the status of the action, and if success, the index in
+ * the parent in {@link Result#getData()}
+ */
+ public Result getViewIndex(Object viewObject) {
+ if (mBridge != null) {
+ Result r = mBridge.getViewIndex(viewObject);
+ if (r.isSuccess()) {
+ return r;
+ }
+ }
+
+ return getViewIndexReflection(viewObject);
+ }
+
+ // ------ Implementation
+
+ private LayoutLibrary(Bridge bridge, ILayoutBridge legacyBridge, ClassLoader classLoader,
+ LoadStatus status, String message) {
+ mBridge = bridge;
+ mLegacyBridge = legacyBridge;
+ mClassLoader = classLoader;
+ mStatus = status;
+ mLoadMessage = message;
+ }
+
+ /**
+ * Returns the API level of the legacy bridge.
+ * <p/>
+ * This handles the case where ILayoutBridge does not have a {@link ILayoutBridge#getApiLevel()}
+ * (at API level 1).
+ * <p/>
+ * {@link ILayoutBridge#getApiLevel()} should never called directly.
+ *
+ * @return the api level of {@link #mLegacyBridge}.
+ */
+ private int getLegacyApiLevel() {
+ int apiLevel = 1;
+ try {
+ apiLevel = mLegacyBridge.getApiLevel();
+ } catch (AbstractMethodError e) {
+ // the first version of the api did not have this method
+ // so this is 1
+ }
+
+ return apiLevel;
+ }
+
+ private RenderSession createLegacySession(SessionParams params) {
+ if (params.getLayoutDescription() instanceof IXmlPullParser == false) {
+ throw new IllegalArgumentException("Parser must be of type ILegacyPullParser");
+ }
+ if (params.getProjectCallback() instanceof
+ com.android.layoutlib.api.IProjectCallback == false) {
+ throw new IllegalArgumentException("Project callback must be of type ILegacyCallback");
+ }
+
+ if (params.getResources() instanceof ResourceResolver == false) {
+ throw new IllegalArgumentException("RenderResources object must be of type ResourceResolver");
+ }
+
+ ResourceResolver resources = (ResourceResolver) params.getResources();
+
+ int apiLevel = getLegacyApiLevel();
+
+ // create a log wrapper since the older api requires a ILayoutLog
+ final LayoutLog log = params.getLog();
+ ILayoutLog logWrapper = new ILayoutLog() {
+
+ @Override
+ public void warning(String message) {
+ log.warning(null, message, null /*data*/);
+ }
+
+ @Override
+ public void error(Throwable t) {
+ log.error(null, "error!", t, null /*data*/);
+ }
+
+ @Override
+ public void error(String message) {
+ log.error(null, message, null /*data*/);
+ }
+ };
+
+
+ // convert the map of ResourceValue into IResourceValue. Super ugly but works.
+
+ Map<String, Map<String, IResourceValue>> projectMap = convertMap(
+ resources.getProjectResources());
+ Map<String, Map<String, IResourceValue>> frameworkMap = convertMap(
+ resources.getFrameworkResources());
+
+ ILayoutResult result = null;
+
+ if (apiLevel == 4) {
+ // Final ILayoutBridge API added support for "render full height"
+ result = mLegacyBridge.computeLayout(
+ (IXmlPullParser) params.getLayoutDescription(),
+ params.getProjectKey(),
+ params.getScreenWidth(), params.getScreenHeight(),
+ params.getRenderingMode() == RenderingMode.FULL_EXPAND ? true : false,
+ params.getDensity().getDpiValue(), params.getXdpi(), params.getYdpi(),
+ resources.getThemeName(), resources.isProjectTheme(),
+ projectMap, frameworkMap,
+ (IProjectCallback) params.getProjectCallback(),
+ logWrapper);
+ } else if (apiLevel == 3) {
+ // api 3 add density support.
+ result = mLegacyBridge.computeLayout(
+ (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
+ params.getScreenWidth(), params.getScreenHeight(),
+ params.getDensity().getDpiValue(), params.getXdpi(), params.getYdpi(),
+ resources.getThemeName(), resources.isProjectTheme(),
+ projectMap, frameworkMap,
+ (IProjectCallback) params.getProjectCallback(), logWrapper);
+ } else if (apiLevel == 2) {
+ // api 2 added boolean for separation of project/framework theme
+ result = mLegacyBridge.computeLayout(
+ (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
+ params.getScreenWidth(), params.getScreenHeight(),
+ resources.getThemeName(), resources.isProjectTheme(),
+ projectMap, frameworkMap,
+ (IProjectCallback) params.getProjectCallback(), logWrapper);
+ } else {
+ // First api with no density/dpi, and project theme boolean mixed
+ // into the theme name.
+
+ // change the string if it's a custom theme to make sure we can
+ // differentiate them
+ String themeName = resources.getThemeName();
+ if (resources.isProjectTheme()) {
+ themeName = "*" + themeName; //$NON-NLS-1$
+ }
+
+ result = mLegacyBridge.computeLayout(
+ (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
+ params.getScreenWidth(), params.getScreenHeight(),
+ themeName,
+ projectMap, frameworkMap,
+ (IProjectCallback) params.getProjectCallback(), logWrapper);
+ }
+
+ // clean up that is not done by the ILayoutBridge itself
+ legacyCleanUp();
+
+ return convertToScene(result);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, Map<String, IResourceValue>> convertMap(
+ Map<ResourceType, Map<String, ResourceValue>> map) {
+ Map<String, Map<String, IResourceValue>> result =
+ new HashMap<String, Map<String, IResourceValue>>();
+
+ for (Entry<ResourceType, Map<String, ResourceValue>> entry : map.entrySet()) {
+ // ugly case but works.
+ result.put(entry.getKey().getName(),
+ (Map) entry.getValue());
+ }
+
+ return result;
+ }
+
+ /**
+ * Converts a {@link ILayoutResult} to a {@link RenderSession}.
+ */
+ private RenderSession convertToScene(ILayoutResult result) {
+
+ Result sceneResult;
+ ViewInfo rootViewInfo = null;
+
+ if (result.getSuccess() == ILayoutResult.SUCCESS) {
+ sceneResult = Status.SUCCESS.createResult();
+ ILayoutViewInfo oldRootView = result.getRootView();
+ if (oldRootView != null) {
+ rootViewInfo = convertToViewInfo(oldRootView);
+ }
+ } else {
+ sceneResult = Status.ERROR_UNKNOWN.createResult(result.getErrorMessage());
+ }
+
+ // create a BasicLayoutScene. This will return the given values but return the default
+ // implementation for all method.
+ // ADT should gracefully handle the default implementations of LayoutScene
+ return new StaticRenderSession(sceneResult, rootViewInfo, result.getImage());
+ }
+
+ /**
+ * Converts a {@link ILayoutViewInfo} (and its children) to a {@link ViewInfo}.
+ */
+ private ViewInfo convertToViewInfo(ILayoutViewInfo view) {
+ // create the view info.
+ ViewInfo viewInfo = new ViewInfo(view.getName(), view.getViewKey(),
+ view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+
+ // then convert the children
+ ILayoutViewInfo[] children = view.getChildren();
+ if (children != null) {
+ ArrayList<ViewInfo> convertedChildren = new ArrayList<ViewInfo>(children.length);
+ for (ILayoutViewInfo child : children) {
+ convertedChildren.add(convertToViewInfo(child));
+ }
+ viewInfo.setChildren(convertedChildren);
+ }
+
+ return viewInfo;
+ }
+
+ /**
+ * Post rendering clean-up that must be done here because it's not done in any layoutlib using
+ * {@link ILayoutBridge}.
+ */
+ private void legacyCleanUp() {
+ try {
+ Class<?> looperClass = mClassLoader.loadClass("android.os.Looper"); //$NON-NLS-1$
+ Field threadLocalField = looperClass.getField("sThreadLocal"); //$NON-NLS-1$
+ if (threadLocalField != null) {
+ threadLocalField.setAccessible(true);
+ // get object. Field is static so no need to pass an object
+ ThreadLocal<?> threadLocal = (ThreadLocal<?>) threadLocalField.get(null);
+ if (threadLocal != null) {
+ threadLocal.remove();
+ }
+ }
+ } catch (Exception e) {
+ // do nothing.
+ }
+ }
+
+ private Result getViewParentWithReflection(Object viewObject) {
+ // default implementation using reflection.
+ try {
+ if (mViewGetParentMethod == null) {
+ Class<?> viewClass = Class.forName("android.view.View");
+ mViewGetParentMethod = viewClass.getMethod("getParent");
+ }
+
+ return Status.SUCCESS.createResult(mViewGetParentMethod.invoke(viewObject));
+ } catch (Exception e) {
+ // Catch all for the reflection calls.
+ return ERROR_REFLECTION.createResult(null, e);
+ }
+ }
+
+ /**
+ * Utility method returning the index of a given view in its parent.
+ * @param viewObject the object for which to return the index.
+ *
+ * @return a {@link Result} indicating the status of the action, and if success, the index in
+ * the parent in {@link Result#getData()}
+ */
+ private Result getViewIndexReflection(Object viewObject) {
+ // default implementation using reflection.
+ try {
+ Class<?> viewClass = Class.forName("android.view.View");
+
+ if (mViewGetParentMethod == null) {
+ mViewGetParentMethod = viewClass.getMethod("getParent");
+ }
+
+ Object parentObject = mViewGetParentMethod.invoke(viewObject);
+
+ if (mViewParentIndexOfChildMethod == null) {
+ Class<?> viewParentClass = Class.forName("android.view.ViewParent");
+ mViewParentIndexOfChildMethod = viewParentClass.getMethod("indexOfChild",
+ viewClass);
+ }
+
+ return Status.SUCCESS.createResult(
+ mViewParentIndexOfChildMethod.invoke(parentObject, viewObject));
+ } catch (Exception e) {
+ // Catch all for the reflection calls.
+ return ERROR_REFLECTION.createResult(null, e);
+ }
+ }
+
+ private void addExtendedViewInfo(ViewInfo info) {
+ computeExtendedViewInfo(info);
+
+ List<ViewInfo> children = info.getChildren();
+ for (ViewInfo child : children) {
+ addExtendedViewInfo(child);
+ }
+ }
+
+ private void computeExtendedViewInfo(ViewInfo info) {
+ Object viewObject = info.getViewObject();
+ Object params = info.getLayoutParamsObject();
+
+ int baseLine = getViewBaselineReflection(viewObject);
+ int leftMargin = 0;
+ int topMargin = 0;
+ int rightMargin = 0;
+ int bottomMargin = 0;
+
+ try {
+ if (mMarginLayoutParamClass == null) {
+ mMarginLayoutParamClass = Class.forName(
+ "android.view.ViewGroup$MarginLayoutParams");
+
+ mLeftMarginField = mMarginLayoutParamClass.getField("leftMargin");
+ mTopMarginField = mMarginLayoutParamClass.getField("topMargin");
+ mRightMarginField = mMarginLayoutParamClass.getField("rightMargin");
+ mBottomMarginField = mMarginLayoutParamClass.getField("bottomMargin");
+ }
+
+ if (mMarginLayoutParamClass.isAssignableFrom(params.getClass())) {
+
+ leftMargin = (Integer)mLeftMarginField.get(params);
+ topMargin = (Integer)mTopMarginField.get(params);
+ rightMargin = (Integer)mRightMarginField.get(params);
+ bottomMargin = (Integer)mBottomMarginField.get(params);
+ }
+
+ } catch (Exception e) {
+ // just use 'unknown' value.
+ leftMargin = Integer.MIN_VALUE;
+ topMargin = Integer.MIN_VALUE;
+ rightMargin = Integer.MIN_VALUE;
+ bottomMargin = Integer.MIN_VALUE;
+ }
+
+ info.setExtendedInfo(baseLine, leftMargin, topMargin, rightMargin, bottomMargin);
+ }
+
+ /**
+ * Utility method returning the baseline value for a given view object. This basically returns
+ * View.getBaseline().
+ *
+ * @param viewObject the object for which to return the index.
+ *
+ * @return the baseline value or -1 if not applicable to the view object or if this layout
+ * library does not implement this method.
+ */
+ private int getViewBaselineReflection(Object viewObject) {
+ // default implementation using reflection.
+ try {
+ if (mViewGetBaselineMethod == null) {
+ Class<?> viewClass = Class.forName("android.view.View");
+ mViewGetBaselineMethod = viewClass.getMethod("getBaseline");
+ }
+
+ Object result = mViewGetBaselineMethod.invoke(viewObject);
+ if (result instanceof Integer) {
+ return ((Integer)result).intValue();
+ }
+
+ } catch (Exception e) {
+ // Catch all for the reflection calls.
+ }
+
+ return Integer.MIN_VALUE;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/rendering/StaticRenderSession.java b/sdk_common/src/com/android/ide/common/rendering/StaticRenderSession.java
new file mode 100644
index 0000000..c122c1c
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/rendering/StaticRenderSession.java
@@ -0,0 +1,63 @@
+/*
+ * 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.rendering;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.ViewInfo;
+
+import java.awt.image.BufferedImage;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Static {@link RenderSession} returning a given {@link Result}, {@link ViewInfo} and
+ * {@link BufferedImage}.
+ * <p/>
+ * All other methods are untouched from the base implementation provided by the API.
+ * <p/>
+ * This is meant to be used as a wrapper around the static results. No further operations are
+ * possible.
+ *
+ */
+public class StaticRenderSession extends RenderSession {
+
+ private final Result mResult;
+ private final List<ViewInfo> mRootViewInfo;
+ private final BufferedImage mImage;
+
+ public StaticRenderSession(Result result, ViewInfo rootViewInfo, BufferedImage image) {
+ mResult = result;
+ mRootViewInfo = Collections.singletonList(rootViewInfo);
+ mImage = image;
+ }
+
+ @Override
+ public Result getResult() {
+ return mResult;
+ }
+
+ @Override
+ public List<ViewInfo> getRootViews() {
+ return mRootViewInfo;
+ }
+
+ @Override
+ public BufferedImage getImage() {
+ return mImage;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/rendering/legacy/ILegacyPullParser.java b/sdk_common/src/com/android/ide/common/rendering/legacy/ILegacyPullParser.java
new file mode 100644
index 0000000..a71e190
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/rendering/legacy/ILegacyPullParser.java
@@ -0,0 +1,31 @@
+/*
+ * 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.rendering.legacy;
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.layoutlib.api.IXmlPullParser;
+
+/**
+ * Intermediary interface extending both old and new project pull parsers from the layout lib API.
+ *
+ * Clients should use this instead of {@link ILayoutPullParser} or {@link IXmlPullParser}.
+ *
+ */
+@SuppressWarnings("deprecation")
+public interface ILegacyPullParser extends ILayoutPullParser, IXmlPullParser {
+
+}
diff --git a/sdk_common/src/com/android/ide/common/rendering/legacy/LegacyCallback.java b/sdk_common/src/com/android/ide/common/rendering/legacy/LegacyCallback.java
new file mode 100644
index 0000000..67e6a7b
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/rendering/legacy/LegacyCallback.java
@@ -0,0 +1,59 @@
+/*
+ * 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.rendering.legacy;
+
+import com.android.ide.common.rendering.api.IProjectCallback;
+import com.android.resources.ResourceType;
+import com.android.util.Pair;
+
+/**
+ * Intermediary class implementing parts of both the old and new project call back from the
+ * layout lib API.
+ *
+ * Clients should use this instead of {@link IProjectCallback} to target both old and new
+ * Layout Libraries.
+ *
+ */
+@SuppressWarnings("deprecation")
+public abstract class LegacyCallback implements
+ com.android.ide.common.rendering.api.IProjectCallback,
+ com.android.layoutlib.api.IProjectCallback {
+
+ // ------ implementation of the old interface using the new interface.
+
+ @Override
+ public final Integer getResourceValue(String type, String name) {
+ return getResourceId(ResourceType.getEnum(type), name);
+ }
+
+ @Override
+ public final String[] resolveResourceValue(int id) {
+ Pair<ResourceType, String> info = resolveResourceId(id);
+ if (info != null) {
+ return new String[] { info.getSecond(), info.getFirst().getName() };
+ }
+
+ return null;
+ }
+
+ @Override
+ public final String resolveResourceValue(int[] id) {
+ return resolveResourceId(id);
+ }
+
+ // ------
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/FrameworkResourceItem.java b/sdk_common/src/com/android/ide/common/resources/FrameworkResourceItem.java
new file mode 100644
index 0000000..70bbcef
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/FrameworkResourceItem.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 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.resources;
+
+/**
+ * A custom {@link ResourceItem} for resources provided by the framework.
+ *
+ * The main change is that {@link #isEditableDirectly()} returns false.
+ */
+class FrameworkResourceItem extends ResourceItem {
+
+ FrameworkResourceItem(String name) {
+ super(name);
+ }
+
+ @Override
+ public boolean isEditableDirectly() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "FrameworkResourceItem [mName=" + getName() + ", mFiles=" //$NON-NLS-1$ //$NON-NLS-2$
+ + getSourceFileList() + "]"; //$NON-NLS-1$
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/FrameworkResources.java b/sdk_common/src/com/android/ide/common/resources/FrameworkResources.java
new file mode 100755
index 0000000..fbf5926
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/FrameworkResources.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2011 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.resources;
+
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.io.IAbstractFile;
+import com.android.io.IAbstractFolder;
+import com.android.resources.ResourceType;
+import com.android.utils.ILogger;
+
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Framework resources repository.
+ *
+ * This behaves the same as {@link ResourceRepository} except that it differentiates between
+ * resources that are public and non public.
+ * {@link #getResources(ResourceType)} and {@link #hasResourcesOfType(ResourceType)} only return
+ * public resources. This is typically used to display resource lists in the UI.
+ *
+ * {@link #getConfiguredResources(com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration)}
+ * returns all resources, even the non public ones so that this can be used for rendering.
+ */
+public class FrameworkResources extends ResourceRepository {
+
+ /**
+ * Map of {@link ResourceType} to list of items. It is guaranteed to contain a list for all
+ * possible values of ResourceType.
+ */
+ protected final Map<ResourceType, List<ResourceItem>> mPublicResourceMap =
+ new EnumMap<ResourceType, List<ResourceItem>>(ResourceType.class);
+
+ public FrameworkResources() {
+ super(true /*isFrameworkRepository*/);
+ }
+
+ /**
+ * Returns a {@link Collection} (always non null, but can be empty) of <b>public</b>
+ * {@link ResourceItem} matching a given {@link ResourceType}.
+ *
+ * @param type the type of the resources to return
+ * @return a collection of items, possible empty.
+ */
+ @Override
+ @NonNull
+ public List<ResourceItem> getResourceItemsOfType(@NonNull ResourceType type) {
+ return mPublicResourceMap.get(type);
+ }
+
+ /**
+ * Returns whether the repository has <b>public</b> resources of a given {@link ResourceType}.
+ * @param type the type of resource to check.
+ * @return true if the repository contains resources of the given type, false otherwise.
+ */
+ @Override
+ public boolean hasResourcesOfType(@NonNull ResourceType type) {
+ return mPublicResourceMap.get(type).size() > 0;
+ }
+
+ @Override
+ @NonNull
+ protected ResourceItem createResourceItem(@NonNull String name) {
+ return new FrameworkResourceItem(name);
+ }
+
+ /**
+ * Reads the public.xml file in data/res/values/ for a given resource folder and builds up
+ * a map of public resources.
+ *
+ * This map is a subset of the full resource map that only contains framework resources
+ * that are public.
+ *
+ * @param resFolder The root folder of the resources
+ * @param logger a logger to report issues to
+ */
+ public void loadPublicResources(@NonNull IAbstractFolder resFolder, @Nullable ILogger logger) {
+ IAbstractFolder valueFolder = resFolder.getFolder(SdkConstants.FD_RES_VALUES);
+ if (valueFolder.exists() == false) {
+ return;
+ }
+
+ IAbstractFile publicXmlFile = valueFolder.getFile("public.xml"); //$NON-NLS-1$
+ if (publicXmlFile.exists()) {
+ Reader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(publicXmlFile.getContents(),
+ "UTF-8")); //$NON-NLS-1$
+ KXmlParser parser = new KXmlParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
+ parser.setInput(reader);
+
+ ResourceType lastType = null;
+ String lastTypeName = "";
+ while (true) {
+ int event = parser.next();
+ if (event == XmlPullParser.START_TAG) {
+ // As of API 15 there are a number of "java-symbol" entries here
+ if (!parser.getName().equals("public")) { //$NON-NLS-1$
+ continue;
+ }
+
+ String name = null;
+ String typeName = null;
+ for (int i = 0, n = parser.getAttributeCount(); i < n; i++) {
+ String attribute = parser.getAttributeName(i);
+
+ if (attribute.equals("name")) { //$NON-NLS-1$
+ name = parser.getAttributeValue(i);
+ if (typeName != null) {
+ // Skip id attribute processing
+ break;
+ }
+ } else if (attribute.equals("type")) { //$NON-NLS-1$
+ typeName = parser.getAttributeValue(i);
+ }
+ }
+
+ if (name != null && typeName != null) {
+ ResourceType type = null;
+ if (typeName.equals(lastTypeName)) {
+ type = lastType;
+ } else {
+ type = ResourceType.getEnum(typeName);
+ lastType = type;
+ lastTypeName = typeName;
+ }
+ if (type != null) {
+ ResourceItem match = null;
+ Map<String, ResourceItem> map = mResourceMap.get(type);
+ if (map != null) {
+ match = map.get(name);
+ }
+
+ if (match != null) {
+ List<ResourceItem> publicList = mPublicResourceMap.get(type);
+ if (publicList == null) {
+ // Pick initial size for the list to hold the public
+ // resources. We could just use map.size() here,
+ // but they're usually much bigger; for example,
+ // in one platform version, there are 1500 drawables
+ // and 1200 strings but only 175 and 25 public ones
+ // respectively.
+ int size;
+ switch (type) {
+ case STYLE: size = 500; break;
+ case ATTR: size = 1000; break;
+ case DRAWABLE: size = 200; break;
+ case ID: size = 50; break;
+ case LAYOUT:
+ case COLOR:
+ case STRING:
+ case ANIM:
+ case INTERPOLATOR:
+ size = 30;
+ break;
+ default:
+ size = 10;
+ break;
+ }
+ publicList = new ArrayList<ResourceItem>(size);
+ mPublicResourceMap.put(type, publicList);
+ }
+
+ publicList.add(match);
+ } else {
+ // log that there's a public resource that doesn't actually
+ // exist?
+ }
+ } else {
+ // log that there was a reference to a typo that doesn't actually
+ // exist?
+ }
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+ }
+ } catch (Exception e) {
+ if (logger != null) {
+ logger.error(e, "Can't read and parse public attribute list");
+ }
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // Nothing to be done here - we don't care if it closed or not.
+ }
+ }
+ }
+ }
+
+ // put unmodifiable list for all res type in the public resource map
+ // this will simplify access
+ for (ResourceType type : ResourceType.values()) {
+ List<ResourceItem> list = mPublicResourceMap.get(type);
+ if (list == null) {
+ list = Collections.emptyList();
+ } else {
+ list = Collections.unmodifiableList(list);
+ }
+
+ // put the new list in the map
+ mPublicResourceMap.put(type, list);
+ }
+ }
+}
+
diff --git a/sdk_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java b/sdk_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java
new file mode 100644
index 0000000..9ff1748
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2011 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.resources;
+
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.io.IAbstractFile;
+import com.android.io.StreamException;
+import com.android.resources.ResourceType;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a resource file that also generates ID resources.
+ * <p/>
+ * This is typically an XML file in res/layout or res/menu
+ */
+public final class IdGeneratingResourceFile extends ResourceFile
+ implements IValueResourceRepository {
+
+ private final Map<String, ResourceValue> mIdResources =
+ new HashMap<String, ResourceValue>();
+
+ private final Collection<ResourceType> mResourceTypeList;
+
+ private final String mFileName;
+
+ private final ResourceType mFileType;
+
+ private final ResourceValue mFileValue;
+
+ public IdGeneratingResourceFile(IAbstractFile file, ResourceFolder folder, ResourceType type) {
+ super(file, folder);
+
+ mFileType = type;
+
+ // Set up our resource types
+ mResourceTypeList = new HashSet<ResourceType>();
+ mResourceTypeList.add(mFileType);
+ mResourceTypeList.add(ResourceType.ID);
+
+ // compute the resource name
+ mFileName = getFileName(type);
+
+ // Get the resource value of this file as a whole layout
+ mFileValue = getFileValue(file, folder);
+ }
+
+ @Override
+ protected void load(ScanningContext context) {
+ // Parse the file and look for @+id/ entries
+ parseFileForIds(context);
+
+ // create the resource items in the repository
+ updateResourceItems(context);
+ }
+
+ @Override
+ protected void update(ScanningContext context) {
+ // Copy the previous list of ID names
+ Set<String> oldIdNames = new HashSet<String>(mIdResources.keySet());
+
+ // reset current content.
+ mIdResources.clear();
+
+ // need to parse the file and find the IDs.
+ if (!parseFileForIds(context)) {
+ context.requestFullAapt();
+ // Continue through to updating the resource item here since it
+ // will make for example layout rendering more accurate until
+ // aapt is re-run
+ }
+
+ // We only need to update the repository if our IDs have changed
+ Set<String> keySet = mIdResources.keySet();
+ assert keySet != oldIdNames;
+ if (oldIdNames.equals(keySet) == false) {
+ updateResourceItems(context);
+ }
+ }
+
+ @Override
+ protected void dispose(ScanningContext context) {
+ ResourceRepository repository = getRepository();
+
+ // Remove declarations from this file from the repository
+ repository.removeFile(mResourceTypeList, this);
+
+ // Ask for an ID refresh since we'll be taking away ID generating items
+ context.requestFullAapt();
+ }
+
+ @Override
+ public Collection<ResourceType> getResourceTypes() {
+ return mResourceTypeList;
+ }
+
+ @Override
+ public boolean hasResources(ResourceType type) {
+ return (type == mFileType) || (type == ResourceType.ID && !mIdResources.isEmpty());
+ }
+
+ @Override
+ public ResourceValue getValue(ResourceType type, String name) {
+ // Check to see if they're asking for one of the right types:
+ if (type != mFileType && type != ResourceType.ID) {
+ return null;
+ }
+
+ // If they're looking for a resource of this type with this name give them the whole file
+ if (type == mFileType && name.equals(mFileName)) {
+ return mFileValue;
+ } else {
+ // Otherwise try to return them an ID
+ // the map will return null if it's not found
+ return mIdResources.get(name);
+ }
+ }
+
+ /**
+ * Looks through the file represented for Ids and adds them to
+ * our id repository
+ *
+ * @return true if parsing succeeds and false if it fails
+ */
+ private boolean parseFileForIds(ScanningContext context) {
+ IdResourceParser parser = new IdResourceParser(this, context, isFramework());
+ try {
+ IAbstractFile file = getFile();
+ return parser.parse(mFileType, file.getOsLocation(), file.getContents());
+ } catch (IOException e) {
+ // Pass
+ } catch (StreamException e) {
+ // Pass
+ }
+
+ return false;
+ }
+
+ /**
+ * Add the resources represented by this file to the repository
+ */
+ private void updateResourceItems(ScanningContext context) {
+ ResourceRepository repository = getRepository();
+
+ // remove this file from all existing ResourceItem.
+ repository.removeFile(mResourceTypeList, this);
+
+ // First add this as a layout file
+ ResourceItem item = repository.getResourceItem(mFileType, mFileName);
+ item.add(this);
+
+ // Now iterate through our IDs and add
+ for (String idName : mIdResources.keySet()) {
+ item = repository.getResourceItem(ResourceType.ID, idName);
+ // add this file to the list of files generating ID resources.
+ item.add(this);
+ }
+
+ // Ask the repository for an ID refresh
+ context.requestFullAapt();
+ }
+
+ /**
+ * Returns the resource value associated with this whole file as a layout resource
+ * @param file the file handler that represents this file
+ * @param folder the folder this file is under
+ * @return a resource value associated with this layout
+ */
+ private ResourceValue getFileValue(IAbstractFile file, ResourceFolder folder) {
+ // test if there's a density qualifier associated with the resource
+ DensityQualifier qualifier = folder.getConfiguration().getDensityQualifier();
+
+ ResourceValue value;
+ if (qualifier == null) {
+ value = new ResourceValue(mFileType, mFileName,
+ file.getOsLocation(), isFramework());
+ } else {
+ value = new DensityBasedResourceValue(
+ mFileType, mFileName,
+ file.getOsLocation(),
+ qualifier.getValue(),
+ isFramework());
+ }
+ return value;
+ }
+
+
+ /**
+ * Returns the name of this resource.
+ */
+ private String getFileName(ResourceType type) {
+ // get the name from the filename.
+ String name = getFile().getName();
+
+ int pos = name.indexOf('.');
+ if (pos != -1) {
+ name = name.substring(0, pos);
+ }
+
+ return name;
+ }
+
+ @Override
+ public void addResourceValue(ResourceValue value) {
+ // Just overwrite collisions. We're only interested in the unique
+ // IDs declared
+ mIdResources.put(value.getName(), value);
+ }
+
+ @Override
+ public boolean hasResourceValue(ResourceType type, String name) {
+ if (type == ResourceType.ID) {
+ return mIdResources.containsKey(name);
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java b/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java
new file mode 100644
index 0000000..1de664e
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2011 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.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
+import com.android.resources.ResourceType;
+
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Parser for scanning an id-generating resource file such as a layout or a menu
+ * file, which registers any ids it encounters with an
+ * {@link IValueResourceRepository}, and which registers errors with a
+ * {@link ScanningContext}.
+ */
+public class IdResourceParser {
+ private final IValueResourceRepository mRepository;
+ private final boolean mIsFramework;
+ private ScanningContext mContext;
+
+ /**
+ * Creates a new {@link IdResourceParser}
+ *
+ * @param repository value repository for registering resource declaration
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
+ * @param isFramework true if scanning a framework resource
+ */
+ public IdResourceParser(IValueResourceRepository repository, ScanningContext context,
+ boolean isFramework) {
+ mRepository = repository;
+ mContext = context;
+ mIsFramework = isFramework;
+ }
+
+ /**
+ * Parse the given input and register ids with the given
+ * {@link IValueResourceRepository}.
+ *
+ * @param type the type of resource being scanned
+ * @param path the full OS path to the file being parsed
+ * @param input the input stream of the XML to be parsed
+ * @return true if parsing succeeds and false if it fails
+ * @throws IOException if reading the contents fails
+ */
+ public boolean parse(ResourceType type, final String path, InputStream input)
+ throws IOException {
+ KXmlParser parser = new KXmlParser();
+ try {
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+
+ if (input instanceof FileInputStream) {
+ input = new BufferedInputStream(input);
+ }
+ parser.setInput(input, "UTF-8"); //$NON-NLS-1$
+
+ return parse(type, path, parser);
+ } catch (XmlPullParserException e) {
+ String message = e.getMessage();
+
+ // Strip off position description
+ int index = message.indexOf("(position:"); //$NON-NLS-1$ (Hardcoded in KXml)
+ if (index != -1) {
+ message = message.substring(0, index);
+ }
+
+ String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
+ path, parser.getLineNumber(), message);
+ mContext.addError(error);
+ return false;
+ } catch (RuntimeException e) {
+ // Some exceptions are thrown by the KXmlParser that are not XmlPullParserExceptions,
+ // such as this one:
+ // java.lang.RuntimeException: Undefined Prefix: w in org.kxml2.io.KXmlParser@...
+ // at org.kxml2.io.KXmlParser.adjustNsp(Unknown Source)
+ // at org.kxml2.io.KXmlParser.parseStartTag(Unknown Source)
+ String message = e.getMessage();
+ String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
+ path, parser.getLineNumber(), message);
+ mContext.addError(error);
+ return false;
+ }
+ }
+
+ private boolean parse(ResourceType type, String path, KXmlParser parser)
+ throws XmlPullParserException, IOException {
+ boolean valid = true;
+ ResourceRepository resources = mContext.getRepository();
+ boolean checkForErrors = !mIsFramework && !mContext.needsFullAapt();
+
+ while (true) {
+ int event = parser.next();
+ if (event == XmlPullParser.START_TAG) {
+ for (int i = 0, n = parser.getAttributeCount(); i < n; i++) {
+ String attribute = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ assert value != null : attribute;
+
+ if (value.startsWith("@")) { //$NON-NLS-1$
+ // Gather IDs
+ if (value.startsWith("@+")) { //$NON-NLS-1$
+ // Strip out the @+id/ or @+android:id/ section
+ String id = value.substring(value.indexOf('/') + 1);
+ ResourceValue newId = new ResourceValue(ResourceType.ID, id,
+ mIsFramework);
+ mRepository.addResourceValue(newId);
+ } else if (checkForErrors){
+ // Validate resource references (unless we're scanning a framework
+ // resource or if we've already scheduled a full aapt run)
+ boolean exists = resources.hasResourceItem(value);
+ if (!exists && !mRepository.hasResourceValue(ResourceType.ID,
+ value.substring(value.indexOf('/') + 1))) {
+ String error = String.format(
+ // Don't localize because the exact pattern matches AAPT's
+ // output which has hardcoded regexp matching in
+ // AaptParser.
+ "%1$s:%2$d: Error: No resource found that matches " + //$NON-NLS-1$
+ "the given name (at '%3$s' with value '%4$s')", //$NON-NLS-1$
+ path, parser.getLineNumber(),
+ attribute, value);
+ mContext.addError(error);
+ valid = false;
+ }
+ }
+ }
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+ }
+
+ return valid;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/InlineResourceItem.java b/sdk_common/src/com/android/ide/common/resources/InlineResourceItem.java
new file mode 100644
index 0000000..37fdc81
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/InlineResourceItem.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2008 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.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+
+
+/**
+ * Represents a resource item that has been declared inline in another resource file.
+ *
+ * This covers the typical ID declaration of "@+id/foo", but does not cover normal value
+ * resources declared in strings.xml or other similar value files.
+ *
+ * This resource will return {@code true} for {@link #isDeclaredInline()} and {@code false} for
+ * {@link #isEditableDirectly()}.
+ */
+public class InlineResourceItem extends ResourceItem {
+
+ private ResourceValue mValue = null;
+
+ /**
+ * Constructs a new inline ResourceItem.
+ * @param name the name of the resource as it appears in the XML and R.java files.
+ */
+ public InlineResourceItem(String name) {
+ super(name);
+ }
+
+ @Override
+ public boolean isDeclaredInline() {
+ return true;
+ }
+
+ @Override
+ public boolean isEditableDirectly() {
+ return false;
+ }
+
+ @Override
+ public ResourceValue getResourceValue(ResourceType type, FolderConfiguration referenceConfig,
+ boolean isFramework) {
+ assert type == ResourceType.ID;
+ if (mValue == null) {
+ mValue = new ResourceValue(type, getName(), isFramework);
+ }
+
+ return mValue;
+ }
+
+ @Override
+ public String toString() {
+ return "InlineResourceItem [mName=" + getName() + ", mFiles=" //$NON-NLS-1$ //$NON-NLS-2$
+ + getSourceFileList() + "]"; //$NON-NLS-1$
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/IntArrayWrapper.java b/sdk_common/src/com/android/ide/common/resources/IntArrayWrapper.java
new file mode 100644
index 0000000..668c677
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/IntArrayWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 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.resources;
+
+import java.util.Arrays;
+
+
+/**
+ * Wrapper around a int[] to provide hashCode/equals support.
+ */
+public final class IntArrayWrapper {
+
+ private int[] mData;
+
+ public IntArrayWrapper(int[] data) {
+ mData = data;
+ }
+
+ public void set(int[] data) {
+ mData = data;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mData);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass().equals(obj.getClass())) {
+ return Arrays.equals(mData, ((IntArrayWrapper)obj).mData);
+ }
+
+ return super.equals(obj);
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/MultiResourceFile.java b/sdk_common/src/com/android/ide/common/resources/MultiResourceFile.java
new file mode 100644
index 0000000..c9a8bc7
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/MultiResourceFile.java
@@ -0,0 +1,223 @@
+/*
+ * 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.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
+import com.android.io.IAbstractFile;
+import com.android.io.StreamException;
+import com.android.resources.ResourceType;
+
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Represents a resource file able to declare multiple resources, which could be of
+ * different {@link ResourceType}.
+ * <p/>
+ * This is typically an XML file inside res/values.
+ */
+public final class MultiResourceFile extends ResourceFile implements IValueResourceRepository {
+
+ private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance();
+
+ private final Map<ResourceType, Map<String, ResourceValue>> mResourceItems =
+ new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
+
+ private Collection<ResourceType> mResourceTypeList = null;
+
+ public MultiResourceFile(IAbstractFile file, ResourceFolder folder) {
+ super(file, folder);
+ }
+
+ // Boolean flag to track whether a named element has been added or removed, thus requiring
+ // a new ID table to be generated
+ private boolean mNeedIdRefresh;
+
+ @Override
+ protected void load(ScanningContext context) {
+ // need to parse the file and find the content.
+ parseFile();
+
+ // create new ResourceItems for the new content.
+ mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet());
+
+ // We need an ID generation step
+ mNeedIdRefresh = true;
+
+ // create/update the resource items.
+ updateResourceItems(context);
+ }
+
+ @Override
+ protected void update(ScanningContext context) {
+ // Reset the ID generation flag
+ mNeedIdRefresh = false;
+
+ // Copy the previous version of our list of ResourceItems and types
+ Map<ResourceType, Map<String, ResourceValue>> oldResourceItems
+ = new EnumMap<ResourceType, Map<String, ResourceValue>>(mResourceItems);
+
+ // reset current content.
+ mResourceItems.clear();
+
+ // need to parse the file and find the content.
+ parseFile();
+
+ // create new ResourceItems for the new content.
+ mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet());
+
+ // Check to see if any names have changed. If so, mark the flag so updateResourceItems
+ // can notify the ResourceRepository that an ID refresh is needed
+ if (oldResourceItems.keySet().equals(mResourceItems.keySet())) {
+ for (ResourceType type : mResourceTypeList) {
+ // We just need to check the names of the items.
+ // If there are new or removed names then we'll have to regenerate IDs
+ if (mResourceItems.get(type).keySet()
+ .equals(oldResourceItems.get(type).keySet()) == false) {
+ mNeedIdRefresh = true;
+ }
+ }
+ } else {
+ // If our type list is different, obviously the names will be different
+ mNeedIdRefresh = true;
+ }
+ // create/update the resource items.
+ updateResourceItems(context);
+ }
+
+ @Override
+ protected void dispose(ScanningContext context) {
+ ResourceRepository repository = getRepository();
+
+ // only remove this file from all existing ResourceItem.
+ repository.removeFile(mResourceTypeList, this);
+
+ // We'll need an ID refresh because we deleted items
+ context.requestFullAapt();
+
+ // don't need to touch the content, it'll get reclaimed as this objects disappear.
+ // In the mean time other objects may need to access it.
+ }
+
+ @Override
+ public Collection<ResourceType> getResourceTypes() {
+ return mResourceTypeList;
+ }
+
+ @Override
+ public boolean hasResources(ResourceType type) {
+ Map<String, ResourceValue> list = mResourceItems.get(type);
+ return (list != null && list.size() > 0);
+ }
+
+ private void updateResourceItems(ScanningContext context) {
+ ResourceRepository repository = getRepository();
+
+ // remove this file from all existing ResourceItem.
+ repository.removeFile(mResourceTypeList, this);
+
+ for (ResourceType type : mResourceTypeList) {
+ Map<String, ResourceValue> list = mResourceItems.get(type);
+
+ if (list != null) {
+ Collection<ResourceValue> values = list.values();
+ for (ResourceValue res : values) {
+ ResourceItem item = repository.getResourceItem(type, res.getName());
+
+ // add this file to the list of files generating this resource item.
+ item.add(this);
+ }
+ }
+ }
+
+ // If we need an ID refresh, ask the repository for that now
+ if (mNeedIdRefresh) {
+ context.requestFullAapt();
+ }
+ }
+
+ /**
+ * Parses the file and creates a list of {@link ResourceType}.
+ */
+ private void parseFile() {
+ try {
+ SAXParser parser = sParserFactory.newSAXParser();
+ parser.parse(getFile().getContents(), new ValueResourceParser(this, isFramework()));
+ } catch (ParserConfigurationException e) {
+ } catch (SAXException e) {
+ } catch (IOException e) {
+ } catch (StreamException e) {
+ }
+ }
+
+ /**
+ * Adds a resource item to the list
+ * @param value The value of the resource.
+ */
+ @Override
+ public void addResourceValue(ResourceValue value) {
+ ResourceType resType = value.getResourceType();
+
+ Map<String, ResourceValue> list = mResourceItems.get(resType);
+
+ // if the list does not exist, create it.
+ if (list == null) {
+ list = new HashMap<String, ResourceValue>();
+ mResourceItems.put(resType, list);
+ } else {
+ // look for a possible value already existing.
+ ResourceValue oldValue = list.get(value.getName());
+
+ if (oldValue != null) {
+ oldValue.replaceWith(value);
+ return;
+ }
+ }
+
+ // empty list or no match found? add the given resource
+ list.put(value.getName(), value);
+ }
+
+ @Override
+ public boolean hasResourceValue(ResourceType type, String name) {
+ Map<String, ResourceValue> map = mResourceItems.get(type);
+ return map != null && map.containsKey(name);
+ }
+
+ @Override
+ public ResourceValue getValue(ResourceType type, String name) {
+ // get the list for the given type
+ Map<String, ResourceValue> list = mResourceItems.get(type);
+
+ if (list != null) {
+ return list.get(name);
+ }
+
+ return null;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceDeltaKind.java b/sdk_common/src/com/android/ide/common/resources/ResourceDeltaKind.java
new file mode 100644
index 0000000..769b6ea
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceDeltaKind.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011 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.resources;
+
+/**
+ * Enum indicating a type of resource change.
+ *
+ * This is similar, and can be easily mapped to Eclipse's integer constants in IResourceDelta.
+ */
+public enum ResourceDeltaKind {
+ CHANGED, ADDED, REMOVED;
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceFile.java b/sdk_common/src/com/android/ide/common/resources/ResourceFile.java
new file mode 100644
index 0000000..378602a
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceFile.java
@@ -0,0 +1,98 @@
+/*
+ * 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.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.Configurable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.io.IAbstractFile;
+import com.android.resources.ResourceType;
+
+import java.util.Collection;
+
+/**
+ * Represents a Resource file (a file under $Project/res/)
+ */
+public abstract class ResourceFile implements Configurable {
+
+ private final IAbstractFile mFile;
+ private final ResourceFolder mFolder;
+
+ protected ResourceFile(IAbstractFile file, ResourceFolder folder) {
+ mFile = file;
+ mFolder = folder;
+ }
+
+ protected abstract void load(ScanningContext context);
+ protected abstract void update(ScanningContext context);
+ protected abstract void dispose(ScanningContext context);
+
+ @Override
+ public FolderConfiguration getConfiguration() {
+ return mFolder.getConfiguration();
+ }
+
+ /**
+ * Returns the IFile associated with the ResourceFile.
+ */
+ public final IAbstractFile getFile() {
+ return mFile;
+ }
+
+ /**
+ * Returns the parent folder as a {@link ResourceFolder}.
+ */
+ public final ResourceFolder getFolder() {
+ return mFolder;
+ }
+
+ public final ResourceRepository getRepository() {
+ return mFolder.getRepository();
+ }
+
+ /**
+ * Returns whether the resource is a framework resource.
+ */
+ public final boolean isFramework() {
+ return mFolder.getRepository().isFrameworkRepository();
+ }
+
+ /**
+ * Returns the list of {@link ResourceType} generated by the file. This is never null.
+ */
+ public abstract Collection<ResourceType> getResourceTypes();
+
+ /**
+ * Returns whether the file generated a resource of a specific type.
+ * @param type The {@link ResourceType}
+ */
+ public abstract boolean hasResources(ResourceType type);
+
+ /**
+ * Returns the value of a resource generated by this file by {@link ResourceType} and name.
+ * <p/>If no resource match, <code>null</code> is returned.
+ * @param type the type of the resource.
+ * @param name the name of the resource.
+ */
+ public abstract ResourceValue getValue(ResourceType type, String name);
+
+ @Override
+ public String toString() {
+ return mFile.toString();
+ }
+}
+
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceFolder.java b/sdk_common/src/com/android/ide/common/resources/ResourceFolder.java
new file mode 100644
index 0000000..d6464c8
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceFolder.java
@@ -0,0 +1,353 @@
+/*
+ * 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.resources;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.ide.common.resources.configuration.Configurable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.io.IAbstractFile;
+import com.android.io.IAbstractFolder;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Resource Folder class. Contains list of {@link ResourceFile}s,
+ * the {@link FolderConfiguration}, and a link to the {@link IAbstractFolder} object.
+ */
+public final class ResourceFolder implements Configurable {
+ final ResourceFolderType mType;
+ final FolderConfiguration mConfiguration;
+ IAbstractFolder mFolder;
+ List<ResourceFile> mFiles = null;
+ Map<String, ResourceFile> mNames = null;
+ private final ResourceRepository mRepository;
+
+ /**
+ * Creates a new {@link ResourceFolder}
+ * @param type The type of the folder
+ * @param config The configuration of the folder
+ * @param folder The associated {@link IAbstractFolder} object.
+ * @param repository The associated {@link ResourceRepository}
+ */
+ protected ResourceFolder(ResourceFolderType type, FolderConfiguration config,
+ IAbstractFolder folder, ResourceRepository repository) {
+ mType = type;
+ mConfiguration = config;
+ mFolder = folder;
+ mRepository = repository;
+ }
+
+ /**
+ * Processes a file and adds it to its parent folder resource.
+ *
+ * @param file the underlying resource file.
+ * @param kind the file change kind.
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
+ * @return the {@link ResourceFile} that was created.
+ */
+ public ResourceFile processFile(IAbstractFile file, ResourceDeltaKind kind,
+ ScanningContext context) {
+ // look for this file if it's already been created
+ ResourceFile resFile = getFile(file, context);
+
+ if (resFile == null) {
+ if (kind != ResourceDeltaKind.REMOVED) {
+ // create a ResourceFile for it.
+
+ resFile = createResourceFile(file);
+ resFile.load(context);
+
+ // add it to the folder
+ addFile(resFile);
+ }
+ } else {
+ if (kind == ResourceDeltaKind.REMOVED) {
+ removeFile(resFile, context);
+ } else {
+ resFile.update(context);
+ }
+ }
+
+ return resFile;
+ }
+
+ private ResourceFile createResourceFile(IAbstractFile file) {
+ // check if that's a single or multi resource type folder. For now we define this by
+ // the number of possible resource type output by files in the folder.
+ // We have a special case for layout/menu folders which can also generate IDs.
+ // This does
+ // not make the difference between several resource types from a single file or
+ // the ability to have 2 files in the same folder generating 2 different types of
+ // resource. The former is handled by MultiResourceFile properly while we don't
+ // handle the latter. If we were to add this behavior we'd have to change this call.
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(mType);
+
+ ResourceFile resFile = null;
+ if (types.size() == 1) {
+ resFile = new SingleResourceFile(file, this);
+ } else if (types.contains(ResourceType.LAYOUT)) {
+ resFile = new IdGeneratingResourceFile(file, this, ResourceType.LAYOUT);
+ } else if (types.contains(ResourceType.MENU)) {
+ resFile = new IdGeneratingResourceFile(file, this, ResourceType.MENU);
+ } else {
+ resFile = new MultiResourceFile(file, this);
+ }
+ return resFile;
+ }
+
+ /**
+ * Adds a {@link ResourceFile} to the folder.
+ * @param file The {@link ResourceFile}.
+ */
+ @VisibleForTesting(visibility=Visibility.PROTECTED)
+ public void addFile(ResourceFile file) {
+ if (mFiles == null) {
+ int initialSize = 16;
+ if (mRepository.isFrameworkRepository()) {
+ String name = mFolder.getName();
+ // Pick some reasonable initial sizes for framework data structures
+ // since they are typically (a) large and (b) their sizes are roughly known
+ // in advance
+ switch (mType) {
+ case DRAWABLE: {
+ // See if it's one of the -mdpi, -hdpi etc folders which
+ // are large (~1250 items)
+ int index = name.indexOf('-');
+ if (index == -1) {
+ initialSize = 230; // "drawable" folder
+ } else {
+ index = name.indexOf('-', index + 1);
+ if (index == -1) {
+ // One of the "drawable-<density>" folders
+ initialSize = 1260;
+ } else {
+ // "drawable-sw600dp-hdpi" etc
+ initialSize = 30;
+ }
+ }
+ break;
+ }
+ case LAYOUT: {
+ // The main layout folder has about ~185 layouts in it;
+ // the others are small
+ if (name.indexOf('-') == -1) {
+ initialSize = 200;
+ }
+ break;
+ }
+ case VALUES: {
+ if (name.indexOf('-') == -1) {
+ initialSize = 32;
+ } else {
+ initialSize = 4;
+ }
+ break;
+ }
+ case ANIM: initialSize = 85; break;
+ case COLOR: initialSize = 32; break;
+ case RAW: initialSize = 4; break;
+ default:
+ // Stick with the 16 default
+ break;
+ }
+ }
+
+ mFiles = new ArrayList<ResourceFile>(initialSize);
+ mNames = new HashMap<String, ResourceFile>(initialSize, 2.0f);
+ }
+
+ mFiles.add(file);
+ mNames.put(file.getFile().getName(), file);
+ }
+
+ protected void removeFile(ResourceFile file, ScanningContext context) {
+ file.dispose(context);
+ mFiles.remove(file);
+ mNames.remove(file.getFile().getName());
+ }
+
+ protected void dispose(ScanningContext context) {
+ if (mFiles != null) {
+ for (ResourceFile file : mFiles) {
+ file.dispose(context);
+ }
+
+ mFiles.clear();
+ mNames.clear();
+ }
+ }
+
+ /**
+ * Returns the {@link IAbstractFolder} associated with this object.
+ */
+ public IAbstractFolder getFolder() {
+ return mFolder;
+ }
+
+ /**
+ * Returns the {@link ResourceFolderType} of this object.
+ */
+ public ResourceFolderType getType() {
+ return mType;
+ }
+
+ public ResourceRepository getRepository() {
+ return mRepository;
+ }
+
+ /**
+ * Returns the list of {@link ResourceType}s generated by the files inside this folder.
+ */
+ public Collection<ResourceType> getResourceTypes() {
+ ArrayList<ResourceType> list = new ArrayList<ResourceType>();
+
+ if (mFiles != null) {
+ for (ResourceFile file : mFiles) {
+ Collection<ResourceType> types = file.getResourceTypes();
+
+ // loop through those and add them to the main list,
+ // if they are not already present
+ for (ResourceType resType : types) {
+ if (list.indexOf(resType) == -1) {
+ list.add(resType);
+ }
+ }
+ }
+ }
+
+ return list;
+ }
+
+ @Override
+ public FolderConfiguration getConfiguration() {
+ return mConfiguration;
+ }
+
+ /**
+ * Returns whether the folder contains a file with the given name.
+ * @param name the name of the file.
+ */
+ public boolean hasFile(String name) {
+ if (mNames != null && mNames.containsKey(name)) {
+ return true;
+ }
+
+ // Note: mNames.containsKey(name) is faster, but doesn't give the same result; this
+ // method seems to be called on this ResourceFolder before it has been processed,
+ // so we need to use the file system check instead:
+ return mFolder.hasFile(name);
+ }
+
+ /**
+ * Returns the {@link ResourceFile} matching a {@link IAbstractFile} object.
+ *
+ * @param file The {@link IAbstractFile} object.
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
+ * @return the {@link ResourceFile} or null if no match was found.
+ */
+ private ResourceFile getFile(IAbstractFile file, ScanningContext context) {
+ assert mFolder.equals(file.getParentFolder());
+
+ if (mNames != null) {
+ ResourceFile resFile = mNames.get(file.getName());
+ if (resFile != null) {
+ return resFile;
+ }
+ }
+
+ // If the file actually exists, the resource folder may not have been
+ // scanned yet; add it lazily
+ if (file.exists()) {
+ ResourceFile resFile = createResourceFile(file);
+ resFile.load(context);
+ addFile(resFile);
+ return resFile;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link ResourceFile} matching a given name.
+ * @param filename The name of the file to return.
+ * @return the {@link ResourceFile} or <code>null</code> if no match was found.
+ */
+ public ResourceFile getFile(String filename) {
+ if (mNames != null) {
+ ResourceFile resFile = mNames.get(filename);
+ if (resFile != null) {
+ return resFile;
+ }
+ }
+
+ // If the file actually exists, the resource folder may not have been
+ // scanned yet; add it lazily
+ IAbstractFile file = mFolder.getFile(filename);
+ if (file != null && file.exists()) {
+ ResourceFile resFile = createResourceFile(file);
+ resFile.load(new ScanningContext(mRepository));
+ addFile(resFile);
+ return resFile;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether a file in the folder is generating a resource of a specified type.
+ * @param type The {@link ResourceType} being looked up.
+ */
+ public boolean hasResources(ResourceType type) {
+ // Check if the folder type is able to generate resource of the type that was asked.
+ // this is a first check to avoid going through the files.
+ List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type);
+
+ boolean valid = false;
+ for (ResourceFolderType rft : folderTypes) {
+ if (rft == mType) {
+ valid = true;
+ break;
+ }
+ }
+
+ if (valid) {
+ if (mFiles != null) {
+ for (ResourceFile f : mFiles) {
+ if (f.hasResources(type)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return mFolder.toString();
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceItem.java b/sdk_common/src/com/android/ide/common/resources/ResourceItem.java
new file mode 100644
index 0000000..49396eb
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceItem.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2011 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.resources;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * An android resource.
+ *
+ * This is a representation of the resource, not of its value(s). It gives access to all
+ * the source files that generate this particular resource which then can be used to access
+ * the actual value(s).
+ *
+ * @see ResourceFile#getResources(ResourceType, ResourceRepository)
+ */
+public class ResourceItem implements Comparable<ResourceItem> {
+
+ private final static Comparator<ResourceFile> sComparator = new Comparator<ResourceFile>() {
+ @Override
+ public int compare(ResourceFile file1, ResourceFile file2) {
+ // get both FolderConfiguration and compare them
+ FolderConfiguration fc1 = file1.getFolder().getConfiguration();
+ FolderConfiguration fc2 = file2.getFolder().getConfiguration();
+
+ return fc1.compareTo(fc2);
+ }
+ };
+
+ private final String mName;
+
+ /**
+ * List of files generating this ResourceItem.
+ */
+ private final List<ResourceFile> mFiles = new ArrayList<ResourceFile>();
+
+ /**
+ * Constructs a new ResourceItem.
+ * @param name the name of the resource as it appears in the XML and R.java files.
+ */
+ public ResourceItem(String name) {
+ mName = name;
+ }
+
+ /**
+ * Returns the name of the resource.
+ */
+ public final String getName() {
+ return mName;
+ }
+
+ /**
+ * Compares the {@link ResourceItem} to another.
+ * @param other the ResourceItem to be compared to.
+ */
+ @Override
+ public int compareTo(ResourceItem other) {
+ return mName.compareTo(other.mName);
+ }
+
+ /**
+ * Returns whether the resource is editable directly.
+ * <p/>
+ * This is typically the case for resources that don't have alternate versions, or resources
+ * of type {@link ResourceType#ID} that aren't declared inline.
+ */
+ public boolean isEditableDirectly() {
+ return hasAlternates() == false;
+ }
+
+ /**
+ * Returns whether the ID resource has been declared inline inside another resource XML file.
+ * If the resource type is not {@link ResourceType#ID}, this will always return {@code false}.
+ */
+ public boolean isDeclaredInline() {
+ return false;
+ }
+
+ /**
+ * Returns a {@link ResourceValue} for this item based on the given configuration.
+ * If the ResourceItem has several source files, one will be selected based on the config.
+ * @param type the type of the resource. This is necessary because ResourceItem doesn't embed
+ * its type, but ResourceValue does.
+ * @param referenceConfig the config of the resource item.
+ * @param isFramework whether the resource is a framework value. Same as the type.
+ * @return a ResourceValue or null if none match the config.
+ */
+ public ResourceValue getResourceValue(ResourceType type, FolderConfiguration referenceConfig,
+ boolean isFramework) {
+ // look for the best match for the given configuration
+ // the match has to be of type ResourceFile since that's what the input list contains
+ ResourceFile match = (ResourceFile) referenceConfig.findMatchingConfigurable(mFiles);
+
+ if (match != null) {
+ // get the value of this configured resource.
+ return match.getValue(type, mName);
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds a new source file.
+ * @param file the source file.
+ */
+ protected void add(ResourceFile file) {
+ mFiles.add(file);
+ }
+
+ /**
+ * Removes a file from the list of source files.
+ * @param file the file to remove
+ */
+ protected void removeFile(ResourceFile file) {
+ mFiles.remove(file);
+ }
+
+ /**
+ * Returns {@code true} if the item has no source file.
+ * @return
+ */
+ protected boolean hasNoSourceFile() {
+ return mFiles.size() == 0;
+ }
+
+ /**
+ * Reset the item by emptying its source file list.
+ */
+ protected void reset() {
+ mFiles.clear();
+ }
+
+ /**
+ * Returns the sorted list of {@link ResourceItem} objects for this resource item.
+ */
+ public ResourceFile[] getSourceFileArray() {
+ ArrayList<ResourceFile> list = new ArrayList<ResourceFile>();
+ list.addAll(mFiles);
+
+ Collections.sort(list, sComparator);
+
+ return list.toArray(new ResourceFile[list.size()]);
+ }
+
+ /**
+ * Returns the list of source file for this resource.
+ */
+ public List<ResourceFile> getSourceFileList() {
+ return Collections.unmodifiableList(mFiles);
+ }
+
+ /**
+ * Returns if the resource has at least one non-default version.
+ *
+ * @see ResourceFile#getConfiguration()
+ * @see FolderConfiguration#isDefault()
+ */
+ public boolean hasAlternates() {
+ for (ResourceFile file : mFiles) {
+ if (file.getFolder().getConfiguration().isDefault() == false) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether the resource has a default version, with no qualifier.
+ *
+ * @see ResourceFile#getConfiguration()
+ * @see FolderConfiguration#isDefault()
+ */
+ public boolean hasDefault() {
+ for (ResourceFile file : mFiles) {
+ if (file.getFolder().getConfiguration().isDefault()) {
+ return true;
+ }
+ }
+
+ // We only want to return false if there's no default and more than 0 items.
+ return (mFiles.size() == 0);
+ }
+
+ /**
+ * Returns the number of alternate versions for this resource.
+ *
+ * @see ResourceFile#getConfiguration()
+ * @see FolderConfiguration#isDefault()
+ */
+ public int getAlternateCount() {
+ int count = 0;
+ for (ResourceFile file : mFiles) {
+ if (file.getFolder().getConfiguration().isDefault() == false) {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * Returns a formatted string usable in an XML to use for the {@link ResourceItem}.
+ * @param system Whether this is a system resource or a project resource.
+ * @return a string in the format @[type]/[name]
+ */
+ public String getXmlString(ResourceType type, boolean system) {
+ if (type == ResourceType.ID && isDeclaredInline()) {
+ return (system ? "@android:" : "@+") + type.getName() + "/" + mName; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ return (system ? "@android:" : "@") + type.getName() + "/" + mName; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ @Override
+ public String toString() {
+ return "ResourceItem [mName=" + mName + ", mFiles=" + mFiles + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java b/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java
new file mode 100755
index 0000000..ac0614d
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java
@@ -0,0 +1,719 @@
+/*
+ * 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.resources;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.Configurable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+import com.android.io.IAbstractFile;
+import com.android.io.IAbstractFolder;
+import com.android.io.IAbstractResource;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Base class for resource repository.
+ *
+ * A repository is both a file representation of a resource folder and a representation
+ * of the generated resources, organized by type.
+ *
+ * {@link #getResourceFolder(IAbstractFolder)} and {@link #getSourceFiles(ResourceType, String, FolderConfiguration)}
+ * give access to the folders and files of the resource folder.
+ *
+ * {@link #getResources(ResourceType)} gives access to the resources directly.
+ *
+ */
+public abstract class ResourceRepository {
+
+ protected final Map<ResourceFolderType, List<ResourceFolder>> mFolderMap =
+ new EnumMap<ResourceFolderType, List<ResourceFolder>>(ResourceFolderType.class);
+
+ protected final Map<ResourceType, Map<String, ResourceItem>> mResourceMap =
+ new EnumMap<ResourceType, Map<String, ResourceItem>>(
+ ResourceType.class);
+
+ private final Map<Map<String, ResourceItem>, Collection<ResourceItem>> mReadOnlyListMap =
+ new IdentityHashMap<Map<String, ResourceItem>, Collection<ResourceItem>>();
+
+ private final boolean mFrameworkRepository;
+
+ protected final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
+
+ /**
+ * Makes a resource repository
+ * @param isFrameworkRepository whether the repository is for framework resources.
+ */
+ protected ResourceRepository(boolean isFrameworkRepository) {
+ mFrameworkRepository = isFrameworkRepository;
+ }
+
+ public boolean isFrameworkRepository() {
+ return mFrameworkRepository;
+ }
+
+ /**
+ * Adds a Folder Configuration to the project.
+ * @param type The resource type.
+ * @param config The resource configuration.
+ * @param folder The workspace folder object.
+ * @return the {@link ResourceFolder} object associated to this folder.
+ */
+ private ResourceFolder add(
+ @NonNull ResourceFolderType type,
+ @NonNull FolderConfiguration config,
+ @NonNull IAbstractFolder folder) {
+ // get the list for the resource type
+ List<ResourceFolder> list = mFolderMap.get(type);
+
+ if (list == null) {
+ list = new ArrayList<ResourceFolder>();
+
+ ResourceFolder cf = new ResourceFolder(type, config, folder, this);
+ list.add(cf);
+
+ mFolderMap.put(type, list);
+
+ return cf;
+ }
+
+ // look for an already existing folder configuration.
+ for (ResourceFolder cFolder : list) {
+ if (cFolder.mConfiguration.equals(config)) {
+ // config already exist. Nothing to be done really, besides making sure
+ // the IAbstractFolder object is up to date.
+ cFolder.mFolder = folder;
+ return cFolder;
+ }
+ }
+
+ // If we arrive here, this means we didn't find a matching configuration.
+ // So we add one.
+ ResourceFolder cf = new ResourceFolder(type, config, folder, this);
+ list.add(cf);
+
+ return cf;
+ }
+
+ /**
+ * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}.
+ * @param type The type of the folder
+ * @param removedFolder the IAbstractFolder object.
+ * @param context the scanning context
+ * @return the {@link ResourceFolder} that was removed, or null if no matches were found.
+ */
+ @Nullable
+ public ResourceFolder removeFolder(
+ @NonNull ResourceFolderType type,
+ @NonNull IAbstractFolder removedFolder,
+ @Nullable ScanningContext context) {
+ // get the list of folders for the resource type.
+ List<ResourceFolder> list = mFolderMap.get(type);
+
+ if (list != null) {
+ int count = list.size();
+ for (int i = 0 ; i < count ; i++) {
+ ResourceFolder resFolder = list.get(i);
+ IAbstractFolder folder = resFolder.getFolder();
+ if (removedFolder.equals(folder)) {
+ // we found the matching ResourceFolder. we need to remove it.
+ list.remove(i);
+
+ // remove its content
+ resFolder.dispose(context);
+
+ return resFolder;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if this resource repository contains a resource of the given
+ * name.
+ *
+ * @param url the resource URL
+ * @return true if the resource is known
+ */
+ public boolean hasResourceItem(@NonNull String url) {
+ assert url.startsWith("@") : url;
+
+ int typeEnd = url.indexOf('/', 1);
+ if (typeEnd != -1) {
+ int nameBegin = typeEnd + 1;
+
+ // Skip @ and @+
+ int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
+
+ int colon = url.lastIndexOf(':', typeEnd);
+ if (colon != -1) {
+ typeBegin = colon + 1;
+ }
+ String typeName = url.substring(typeBegin, typeEnd);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type != null) {
+ String name = url.substring(nameBegin);
+ return hasResourceItem(type, name);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if this resource repository contains a resource of the given
+ * name.
+ *
+ * @param type the type of resource to look up
+ * @param name the name of the resource
+ * @return true if the resource is known
+ */
+ public boolean hasResourceItem(@NonNull ResourceType type, @NonNull String name) {
+ Map<String, ResourceItem> map = mResourceMap.get(type);
+
+ if (map != null) {
+
+ ResourceItem resourceItem = map.get(name);
+ if (resourceItem != null) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a {@link ResourceItem} matching the given {@link ResourceType} and name. If none
+ * exist, it creates one.
+ *
+ * @param type the resource type
+ * @param name the name of the resource.
+ * @return A resource item matching the type and name.
+ */
+ @NonNull
+ protected ResourceItem getResourceItem(@NonNull ResourceType type, @NonNull String name) {
+ // looking for an existing ResourceItem with this type and name
+ ResourceItem item = findDeclaredResourceItem(type, name);
+
+ // create one if there isn't one already, or if the existing one is inlined, since
+ // clearly we need a non inlined one (the inline one is removed too)
+ if (item == null || item.isDeclaredInline()) {
+ ResourceItem oldItem = item != null && item.isDeclaredInline() ? item : null;
+
+ item = createResourceItem(name);
+
+ Map<String, ResourceItem> map = mResourceMap.get(type);
+
+ if (map == null) {
+ if (isFrameworkRepository()) {
+ // Pick initial size for the maps. Also change the load factor to 1.0
+ // to avoid rehashing the whole table when we (as expected) get near
+ // the known rough size of each resource type map.
+ int size;
+ switch (type) {
+ // Based on counts in API 16. Going back to API 10, the counts
+ // are roughly 25-50% smaller (e.g. compared to the top 5 types below
+ // the fractions are 1107 vs 1734, 831 vs 1508, 895 vs 1255,
+ // 733 vs 1064 and 171 vs 783.
+ case PUBLIC: size = 1734; break;
+ case DRAWABLE: size = 1508; break;
+ case STRING: size = 1255; break;
+ case ATTR: size = 1064; break;
+ case STYLE: size = 783; break;
+ case ID: size = 347; break;
+ case DECLARE_STYLEABLE: size = 210; break;
+ case LAYOUT: size = 187; break;
+ case COLOR: size = 120; break;
+ case ANIM: size = 95; break;
+ case DIMEN: size = 81; break;
+ case BOOL: size = 54; break;
+ case INTEGER: size = 52; break;
+ case ARRAY: size = 51; break;
+ case PLURALS: size = 20; break;
+ case XML: size = 14; break;
+ case INTERPOLATOR : size = 13; break;
+ case ANIMATOR: size = 8; break;
+ case RAW: size = 4; break;
+ case MENU: size = 2; break;
+ case MIPMAP: size = 2; break;
+ case FRACTION: size = 1; break;
+ default:
+ size = 2;
+ }
+ map = new HashMap<String, ResourceItem>(size, 1.0f);
+ } else {
+ map = new HashMap<String, ResourceItem>();
+ }
+ mResourceMap.put(type, map);
+ }
+
+ map.put(item.getName(), item);
+
+ if (oldItem != null) {
+ map.remove(oldItem.getName());
+
+ }
+ }
+
+ return item;
+ }
+
+ /**
+ * Creates a resource item with the given name.
+ * @param name the name of the resource
+ * @return a new ResourceItem (or child class) instance.
+ */
+ @NonNull
+ protected abstract ResourceItem createResourceItem(@NonNull String name);
+
+ /**
+ * Processes a folder and adds it to the list of existing folders.
+ * @param folder the folder to process
+ * @return the ResourceFolder created from this folder, or null if the process failed.
+ */
+ @Nullable
+ public ResourceFolder processFolder(@NonNull IAbstractFolder folder) {
+ // split the name of the folder in segments.
+ String[] folderSegments = folder.getName().split(SdkConstants.RES_QUALIFIER_SEP);
+
+ // get the enum for the resource type.
+ ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]);
+
+ if (type != null) {
+ // get the folder configuration.
+ FolderConfiguration config = FolderConfiguration.getConfig(folderSegments);
+
+ if (config != null) {
+ return add(type, config, folder);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}.
+ * @param type The {@link ResourceFolderType}
+ */
+ @Nullable
+ public List<ResourceFolder> getFolders(@NonNull ResourceFolderType type) {
+ return mFolderMap.get(type);
+ }
+
+ @NonNull
+ public List<ResourceType> getAvailableResourceTypes() {
+ List<ResourceType> list = new ArrayList<ResourceType>();
+
+ // For each key, we check if there's a single ResourceType match.
+ // If not, we look for the actual content to give us the resource type.
+
+ for (ResourceFolderType folderType : mFolderMap.keySet()) {
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folderType);
+ if (types.size() == 1) {
+ // before we add it we check if it's not already present, since a ResourceType
+ // could be created from multiple folders, even for the folders that only create
+ // one type of resource (drawable for instance, can be created from drawable/ and
+ // values/)
+ if (list.contains(types.get(0)) == false) {
+ list.add(types.get(0));
+ }
+ } else {
+ // there isn't a single resource type out of this folder, so we look for all
+ // content.
+ List<ResourceFolder> folders = mFolderMap.get(folderType);
+ if (folders != null) {
+ for (ResourceFolder folder : folders) {
+ Collection<ResourceType> folderContent = folder.getResourceTypes();
+
+ // then we add them, but only if they aren't already in the list.
+ for (ResourceType folderResType : folderContent) {
+ if (list.contains(folderResType) == false) {
+ list.add(folderResType);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns a list of {@link ResourceItem} matching a given {@link ResourceType}.
+ * @param type the type of the resource items to return
+ * @return a non null collection of resource items
+ */
+ @NonNull
+ public Collection<ResourceItem> getResourceItemsOfType(@NonNull ResourceType type) {
+ Map<String, ResourceItem> map = mResourceMap.get(type);
+
+ if (map == null) {
+ return Collections.emptyList();
+ }
+
+ Collection<ResourceItem> roList = mReadOnlyListMap.get(map);
+ if (roList == null) {
+ roList = Collections.unmodifiableCollection(map.values());
+ mReadOnlyListMap.put(map, roList);
+ }
+
+ return roList;
+ }
+
+ /**
+ * Returns whether the repository has resources of a given {@link ResourceType}.
+ * @param type the type of resource to check.
+ * @return true if the repository contains resources of the given type, false otherwise.
+ */
+ public boolean hasResourcesOfType(@NonNull ResourceType type) {
+ Map<String, ResourceItem> items = mResourceMap.get(type);
+ return (items != null && items.size() > 0);
+ }
+
+ /**
+ * Returns the {@link ResourceFolder} associated with a {@link IAbstractFolder}.
+ * @param folder The {@link IAbstractFolder} object.
+ * @return the {@link ResourceFolder} or null if it was not found.
+ */
+ @Nullable
+ public ResourceFolder getResourceFolder(@NonNull IAbstractFolder folder) {
+ Collection<List<ResourceFolder>> values = mFolderMap.values();
+
+ if (values.isEmpty()) { // This shouldn't be necessary, but has been observed
+ try {
+ loadResources(folder.getParentFolder());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ for (List<ResourceFolder> list : values) {
+ for (ResourceFolder resFolder : list) {
+ IAbstractFolder wrapper = resFolder.getFolder();
+ if (wrapper.equals(folder)) {
+ return resFolder;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and
+ * configuration.
+ * <p/>This only works with files generating one resource named after the file (for instance,
+ * layouts, bitmap based drawable, xml, anims).
+ * @return the matching file or <code>null</code> if no match was found.
+ */
+ @Nullable
+ public ResourceFile getMatchingFile(@NonNull String name, @NonNull ResourceFolderType type,
+ @NonNull FolderConfiguration config) {
+ // get the folders for the given type
+ List<ResourceFolder> folders = mFolderMap.get(type);
+
+ // look for folders containing a file with the given name.
+ ArrayList<ResourceFolder> matchingFolders = new ArrayList<ResourceFolder>(folders.size());
+
+ // remove the folders that do not have a file with the given name.
+ for (int i = 0 ; i < folders.size(); i++) {
+ ResourceFolder folder = folders.get(i);
+
+ if (folder.hasFile(name) == true) {
+ matchingFolders.add(folder);
+ }
+ }
+
+ // from those, get the folder with a config matching the given reference configuration.
+ Configurable match = config.findMatchingConfigurable(matchingFolders);
+
+ // do we have a matching folder?
+ if (match instanceof ResourceFolder) {
+ // get the ResourceFile from the filename
+ return ((ResourceFolder)match).getFile(name);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the list of source files for a given resource.
+ * Optionally, if a {@link FolderConfiguration} is given, then only the best
+ * match for this config is returned.
+ *
+ * @param type the type of the resource.
+ * @param name the name of the resource.
+ * @param referenceConfig an optional config for which only the best match will be returned.
+ *
+ * @return a list of files generating this resource or null if it was not found.
+ */
+ @Nullable
+ public List<ResourceFile> getSourceFiles(@NonNull ResourceType type, @NonNull String name,
+ @Nullable FolderConfiguration referenceConfig) {
+
+ Collection<ResourceItem> items = getResourceItemsOfType(type);
+
+ for (ResourceItem item : items) {
+ if (name.equals(item.getName())) {
+ if (referenceConfig != null) {
+ Configurable match = referenceConfig.findMatchingConfigurable(
+ item.getSourceFileList());
+
+ if (match instanceof ResourceFile) {
+ return Collections.singletonList((ResourceFile) match);
+ }
+
+ return null;
+ }
+ return item.getSourceFileList();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the resources values matching a given {@link FolderConfiguration}.
+ *
+ * @param referenceConfig the configuration that each value must match.
+ * @return a map with guaranteed to contain an entry for each {@link ResourceType}
+ */
+ @NonNull
+ public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
+ @NonNull FolderConfiguration referenceConfig) {
+ return doGetConfiguredResources(referenceConfig);
+ }
+
+ /**
+ * Returns the resources values matching a given {@link FolderConfiguration} for the current
+ * project.
+ *
+ * @param referenceConfig the configuration that each value must match.
+ * @return a map with guaranteed to contain an entry for each {@link ResourceType}
+ */
+ @NonNull
+ protected final Map<ResourceType, Map<String, ResourceValue>> doGetConfiguredResources(
+ @NonNull FolderConfiguration referenceConfig) {
+
+ Map<ResourceType, Map<String, ResourceValue>> map =
+ new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
+
+ for (ResourceType key : ResourceType.values()) {
+ // get the local results and put them in the map
+ map.put(key, getConfiguredResource(key, referenceConfig));
+ }
+
+ return map;
+ }
+
+ /**
+ * Returns the sorted list of languages used in the resources.
+ */
+ @NonNull
+ public SortedSet<String> getLanguages() {
+ SortedSet<String> set = new TreeSet<String>();
+
+ Collection<List<ResourceFolder>> folderList = mFolderMap.values();
+ for (List<ResourceFolder> folderSubList : folderList) {
+ for (ResourceFolder folder : folderSubList) {
+ FolderConfiguration config = folder.getConfiguration();
+ LanguageQualifier lang = config.getLanguageQualifier();
+ if (lang != null) {
+ set.add(lang.getShortDisplayValue());
+ }
+ }
+ }
+
+ return set;
+ }
+
+ /**
+ * Returns the sorted list of regions used in the resources with the given language.
+ * @param currentLanguage the current language the region must be associated with.
+ */
+ @NonNull
+ public SortedSet<String> getRegions(@NonNull String currentLanguage) {
+ SortedSet<String> set = new TreeSet<String>();
+
+ Collection<List<ResourceFolder>> folderList = mFolderMap.values();
+ for (List<ResourceFolder> folderSubList : folderList) {
+ for (ResourceFolder folder : folderSubList) {
+ FolderConfiguration config = folder.getConfiguration();
+
+ // get the language
+ LanguageQualifier lang = config.getLanguageQualifier();
+ if (lang != null && lang.getShortDisplayValue().equals(currentLanguage)) {
+ RegionQualifier region = config.getRegionQualifier();
+ if (region != null) {
+ set.add(region.getShortDisplayValue());
+ }
+ }
+ }
+ }
+
+ return set;
+ }
+
+ /**
+ * Loads the resources from a resource folder.
+ * <p/>
+ *
+ * @param rootFolder The folder to read the resources from. This is the top level
+ * resource folder (res/)
+ * @throws IOException
+ */
+ public void loadResources(@NonNull IAbstractFolder rootFolder)
+ throws IOException {
+ ScanningContext context = new ScanningContext(this);
+
+ IAbstractResource[] files = rootFolder.listMembers();
+ for (IAbstractResource file : files) {
+ if (file instanceof IAbstractFolder) {
+ IAbstractFolder folder = (IAbstractFolder) file;
+ ResourceFolder resFolder = processFolder(folder);
+
+ if (resFolder != null) {
+ // now we process the content of the folder
+ IAbstractResource[] children = folder.listMembers();
+
+ for (IAbstractResource childRes : children) {
+ if (childRes instanceof IAbstractFile) {
+ resFolder.processFile((IAbstractFile) childRes,
+ ResourceDeltaKind.ADDED, context);
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ protected void removeFile(@NonNull Collection<ResourceType> types,
+ @NonNull ResourceFile file) {
+ for (ResourceType type : types) {
+ removeFile(type, file);
+ }
+ }
+
+ protected void removeFile(@NonNull ResourceType type, @NonNull ResourceFile file) {
+ Map<String, ResourceItem> map = mResourceMap.get(type);
+ if (map != null) {
+ Collection<ResourceItem> values = map.values();
+ for (ResourceItem item : values) {
+ item.removeFile(file);
+ }
+ }
+ }
+
+ /**
+ * Returns a map of (resource name, resource value) for the given {@link ResourceType}.
+ * <p/>The values returned are taken from the resource files best matching a given
+ * {@link FolderConfiguration}.
+ * @param type the type of the resources.
+ * @param referenceConfig the configuration to best match.
+ */
+ @NonNull
+ private Map<String, ResourceValue> getConfiguredResource(@NonNull ResourceType type,
+ @NonNull FolderConfiguration referenceConfig) {
+
+ // get the resource item for the given type
+ Map<String, ResourceItem> items = mResourceMap.get(type);
+ if (items == null) {
+ return new HashMap<String, ResourceValue>();
+ }
+
+ // create the map
+ HashMap<String, ResourceValue> map = new HashMap<String, ResourceValue>(items.size());
+
+ for (ResourceItem item : items.values()) {
+ ResourceValue value = item.getResourceValue(type, referenceConfig,
+ isFrameworkRepository());
+ if (value != null) {
+ map.put(item.getName(), value);
+ }
+ }
+
+ return map;
+ }
+
+
+ /**
+ * Cleans up the repository of resource items that have no source file anymore.
+ */
+ public void postUpdateCleanUp() {
+ // Since removed files/folders remove source files from existing ResourceItem, loop through
+ // all resource items and remove the ones that have no source files.
+
+ Collection<Map<String, ResourceItem>> maps = mResourceMap.values();
+ for (Map<String, ResourceItem> map : maps) {
+ Set<String> keySet = map.keySet();
+ Iterator<String> iterator = keySet.iterator();
+ while (iterator.hasNext()) {
+ String name = iterator.next();
+ ResourceItem resourceItem = map.get(name);
+ if (resourceItem.hasNoSourceFile()) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Looks up an existing {@link ResourceItem} by {@link ResourceType} and name. This
+ * ignores inline resources.
+ * @param type the Resource Type.
+ * @param name the Resource name.
+ * @return the existing ResourceItem or null if no match was found.
+ */
+ @Nullable
+ private ResourceItem findDeclaredResourceItem(@NonNull ResourceType type,
+ @NonNull String name) {
+ Map<String, ResourceItem> map = mResourceMap.get(type);
+
+ if (map != null) {
+ ResourceItem resourceItem = map.get(name);
+ if (resourceItem != null && !resourceItem.isDeclaredInline()) {
+ return resourceItem;
+ }
+ }
+
+ return null;
+ }
+}
+
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceResolver.java b/sdk_common/src/com/android/ide/common/resources/ResourceResolver.java
new file mode 100644
index 0000000..d742c4a
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceResolver.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2011 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.resources;
+
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
+import static com.android.SdkConstants.PREFIX_ANDROID;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.SdkConstants.REFERENCE_STYLE;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.resources.ResourceType;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ResourceResolver extends RenderResources {
+
+ private final Map<ResourceType, Map<String, ResourceValue>> mProjectResources;
+ private final Map<ResourceType, Map<String, ResourceValue>> mFrameworkResources;
+
+ private final Map<StyleResourceValue, StyleResourceValue> mStyleInheritanceMap =
+ new HashMap<StyleResourceValue, StyleResourceValue>();
+
+ private StyleResourceValue mTheme;
+
+ private FrameworkResourceIdProvider mFrameworkProvider;
+ private LayoutLog mLogger;
+ private String mThemeName;
+ private boolean mIsProjectTheme;
+
+ private ResourceResolver(
+ Map<ResourceType, Map<String, ResourceValue>> projectResources,
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources) {
+ mProjectResources = projectResources;
+ mFrameworkResources = frameworkResources;
+ }
+
+ /**
+ * Creates a new {@link ResourceResolver} object.
+ *
+ * @param projectResources the project resources.
+ * @param frameworkResources the framework resources.
+ * @param themeName the name of the current theme.
+ * @param isProjectTheme Is this a project theme?
+ * @return a new {@link ResourceResolver}
+ */
+ public static ResourceResolver create(
+ Map<ResourceType, Map<String, ResourceValue>> projectResources,
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
+ String themeName, boolean isProjectTheme) {
+
+ ResourceResolver resolver = new ResourceResolver(
+ projectResources, frameworkResources);
+
+ resolver.computeStyleMaps(themeName, isProjectTheme);
+
+ return resolver;
+ }
+
+ // ---- Methods to help dealing with older LayoutLibs.
+
+ public String getThemeName() {
+ return mThemeName;
+ }
+
+ public boolean isProjectTheme() {
+ return mIsProjectTheme;
+ }
+
+ public Map<ResourceType, Map<String, ResourceValue>> getProjectResources() {
+ return mProjectResources;
+ }
+
+ public Map<ResourceType, Map<String, ResourceValue>> getFrameworkResources() {
+ return mFrameworkResources;
+ }
+
+ // ---- RenderResources Methods
+
+ @Override
+ public void setFrameworkResourceIdProvider(FrameworkResourceIdProvider provider) {
+ mFrameworkProvider = provider;
+ }
+
+ @Override
+ public void setLogger(LayoutLog logger) {
+ mLogger = logger;
+ }
+
+ @Override
+ public StyleResourceValue getCurrentTheme() {
+ return mTheme;
+ }
+
+ @Override
+ public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
+ ResourceValue theme = null;
+
+ if (frameworkTheme) {
+ Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(
+ ResourceType.STYLE);
+ theme = frameworkStyleMap.get(name);
+ } else {
+ Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE);
+ theme = projectStyleMap.get(name);
+ }
+
+ if (theme instanceof StyleResourceValue) {
+ return (StyleResourceValue) theme;
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
+ do {
+ childTheme = mStyleInheritanceMap.get(childTheme);
+ if (childTheme == null) {
+ return false;
+ } else if (childTheme == parentTheme) {
+ return true;
+ }
+ } while (true);
+ }
+
+ @Override
+ public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
+ return getResource(resourceType, resourceName, mFrameworkResources);
+ }
+
+ @Override
+ public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
+ return getResource(resourceType, resourceName, mProjectResources);
+ }
+
+ @Override
+ @Deprecated
+ public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
+ // this method is deprecated because it doesn't know about the namespace of the
+ // attribute so we search for the project namespace first and then in the
+ // android namespace if needed.
+ ResourceValue item = findItemInStyle(style, attrName, false /*isFrameworkAttr*/);
+ if (item == null) {
+ item = findItemInStyle(style, attrName, true /*isFrameworkAttr*/);
+ }
+
+ return item;
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
+ boolean isFrameworkAttr) {
+ ResourceValue item = style.findValue(itemName, isFrameworkAttr);
+
+ // if we didn't find it, we look in the parent style (if applicable)
+ if (item == null && mStyleInheritanceMap != null) {
+ StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
+ if (parentStyle != null) {
+ return findItemInStyle(parentStyle, itemName, isFrameworkAttr);
+ }
+ }
+
+ return item;
+ }
+
+ @Override
+ public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
+ if (reference == null) {
+ return null;
+ }
+ if (reference.startsWith(PREFIX_THEME_REF)) {
+ // no theme? no need to go further!
+ if (mTheme == null) {
+ return null;
+ }
+
+ boolean frameworkOnly = false;
+
+ // eliminate the prefix from the string
+ if (reference.startsWith(ANDROID_THEME_PREFIX)) {
+ frameworkOnly = true;
+ reference = reference.substring(ANDROID_THEME_PREFIX.length());
+ } else {
+ reference = reference.substring(PREFIX_THEME_REF.length());
+ }
+
+ // at this point, value can contain type/name (drawable/foo for instance).
+ // split it to make sure.
+ String[] segments = reference.split("\\/");
+
+ // we look for the referenced item name.
+ String referenceName = null;
+
+ if (segments.length == 2) {
+ // there was a resType in the reference. If it's attr, we ignore it
+ // else, we assert for now.
+ if (ResourceType.ATTR.getName().equals(segments[0])) {
+ referenceName = segments[1];
+ } else {
+ // At this time, no support for ?type/name where type is not "attr"
+ return null;
+ }
+ } else {
+ // it's just an item name.
+ referenceName = segments[0];
+ }
+
+ // now we look for android: in the referenceName in order to support format
+ // such as: ?attr/android:name
+ if (referenceName.startsWith(PREFIX_ANDROID)) {
+ frameworkOnly = true;
+ referenceName = referenceName.substring(PREFIX_ANDROID.length());
+ }
+
+ // Now look for the item in the theme, starting with the current one.
+ ResourceValue item = findItemInStyle(mTheme, referenceName,
+ forceFrameworkOnly || frameworkOnly);
+
+ if (item == null && mLogger != null) {
+ mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
+ String.format("Couldn't find theme resource %1$s for the current theme",
+ reference),
+ new ResourceValue(ResourceType.ATTR, referenceName, frameworkOnly));
+ }
+
+ return item;
+ } else if (reference.startsWith(PREFIX_RESOURCE_REF)) {
+ boolean frameworkOnly = false;
+
+ // check for the specific null reference value.
+ if (REFERENCE_NULL.equals(reference)) {
+ return null;
+ }
+
+ // Eliminate the prefix from the string.
+ if (reference.startsWith(ANDROID_PREFIX)) {
+ frameworkOnly = true;
+ reference = reference.substring(ANDROID_PREFIX.length());
+ } else {
+ reference = reference.substring(PREFIX_RESOURCE_REF.length());
+ }
+
+ // at this point, value contains type/[android:]name (drawable/foo for instance)
+ String[] segments = reference.split("\\/");
+
+ // now we look for android: in the resource name in order to support format
+ // such as: @drawable/android:name
+ if (segments[1].startsWith(PREFIX_ANDROID)) {
+ frameworkOnly = true;
+ segments[1] = segments[1].substring(PREFIX_ANDROID.length());
+ }
+
+ ResourceType type = ResourceType.getEnum(segments[0]);
+
+ // unknown type?
+ if (type == null) {
+ return null;
+ }
+
+ return findResValue(type, segments[1],
+ forceFrameworkOnly ? true :frameworkOnly);
+ }
+
+ // Looks like the value didn't reference anything. Return null.
+ return null;
+ }
+
+ @Override
+ public ResourceValue resolveValue(ResourceType type, String name, String value,
+ boolean isFrameworkValue) {
+ if (value == null) {
+ return null;
+ }
+
+ // get the ResourceValue referenced by this value
+ ResourceValue resValue = findResValue(value, isFrameworkValue);
+
+ // if resValue is null, but value is not null, this means it was not a reference.
+ // we return the name/value wrapper in a ResourceValue. the isFramework flag doesn't
+ // matter.
+ if (resValue == null) {
+ return new ResourceValue(type, name, value, isFrameworkValue);
+ }
+
+ // we resolved a first reference, but we need to make sure this isn't a reference also.
+ return resolveResValue(resValue);
+ }
+
+ @Override
+ public ResourceValue resolveResValue(ResourceValue resValue) {
+ if (resValue == null) {
+ return null;
+ }
+
+ // if the resource value is null, we simply return it.
+ String value = resValue.getValue();
+ if (value == null) {
+ return resValue;
+ }
+
+ // else attempt to find another ResourceValue referenced by this one.
+ ResourceValue resolvedResValue = findResValue(value, resValue.isFramework());
+
+ // if the value did not reference anything, then we simply return the input value
+ if (resolvedResValue == null) {
+ return resValue;
+ }
+
+ // detect potential loop due to mishandled namespace in attributes
+ if (resValue == resolvedResValue) {
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_BROKEN,
+ String.format("Potential stackoverflow trying to resolve '%s'. Render may not be accurate.", value),
+ null);
+ }
+ return resValue;
+ }
+
+ // otherwise, we attempt to resolve this new value as well
+ return resolveResValue(resolvedResValue);
+ }
+
+ // ---- Private helper methods.
+
+ /**
+ * Searches for, and returns a {@link ResourceValue} by its name, and type.
+ * @param resType the type of the resource
+ * @param resName the name of the resource
+ * @param frameworkOnly if <code>true</code>, the method does not search in the
+ * project resources
+ */
+ private ResourceValue findResValue(ResourceType resType, String resName,
+ boolean frameworkOnly) {
+ // map of ResouceValue for the given type
+ Map<String, ResourceValue> typeMap;
+
+ // if allowed, search in the project resources first.
+ if (frameworkOnly == false) {
+ typeMap = mProjectResources.get(resType);
+ ResourceValue item = typeMap.get(resName);
+ if (item != null) {
+ return item;
+ }
+ }
+
+ // now search in the framework resources.
+ typeMap = mFrameworkResources.get(resType);
+ ResourceValue item = typeMap.get(resName);
+ if (item != null) {
+ return item;
+ }
+
+ // if it was not found and the type is an id, it is possible that the ID was
+ // generated dynamically when compiling the framework resources.
+ // Look for it in the R map.
+ if (mFrameworkProvider != null && resType == ResourceType.ID) {
+ if (mFrameworkProvider.getId(resType, resName) != null) {
+ return new ResourceValue(resType, resName, true);
+ }
+ }
+
+ // didn't find the resource anywhere.
+ if (mLogger != null) {
+ mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE,
+ "Couldn't resolve resource @" +
+ (frameworkOnly ? "android:" : "") + resType + "/" + resName,
+ new ResourceValue(resType, resName, frameworkOnly));
+ }
+ return null;
+ }
+
+ private ResourceValue getResource(ResourceType resourceType, String resourceName,
+ Map<ResourceType, Map<String, ResourceValue>> resourceRepository) {
+ Map<String, ResourceValue> typeMap = resourceRepository.get(resourceType);
+ if (typeMap != null) {
+ ResourceValue item = typeMap.get(resourceName);
+ if (item != null) {
+ item = resolveResValue(item);
+ return item;
+ }
+ }
+
+ // didn't find the resource anywhere.
+ return null;
+
+ }
+
+ /**
+ * Compute style information from the given list of style for the project and framework.
+ * @param themeName the name of the current theme.
+ * @param isProjectTheme Is this a project theme?
+ */
+ private void computeStyleMaps(String themeName, boolean isProjectTheme) {
+ mThemeName = themeName;
+ mIsProjectTheme = isProjectTheme;
+ Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE);
+ Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(ResourceType.STYLE);
+
+ // first, get the theme
+ ResourceValue theme = null;
+
+ // project theme names have been prepended with a *
+ if (isProjectTheme) {
+ theme = projectStyleMap.get(themeName);
+ } else {
+ theme = frameworkStyleMap.get(themeName);
+ }
+
+ if (theme instanceof StyleResourceValue) {
+ // compute the inheritance map for both the project and framework styles
+ computeStyleInheritance(projectStyleMap.values(), projectStyleMap,
+ frameworkStyleMap);
+
+ // Compute the style inheritance for the framework styles/themes.
+ // Since, for those, the style parent values do not contain 'android:'
+ // we want to force looking in the framework style only to avoid using
+ // similarly named styles from the project.
+ // To do this, we pass null in lieu of the project style map.
+ computeStyleInheritance(frameworkStyleMap.values(), null /*inProjectStyleMap */,
+ frameworkStyleMap);
+
+ mTheme = (StyleResourceValue) theme;
+ }
+ }
+
+
+
+ /**
+ * Compute the parent style for all the styles in a given list.
+ * @param styles the styles for which we compute the parent.
+ * @param inProjectStyleMap the map of project styles.
+ * @param inFrameworkStyleMap the map of framework styles.
+ * @param outInheritanceMap the map of style inheritance. This is filled by the method.
+ */
+ private void computeStyleInheritance(Collection<ResourceValue> styles,
+ Map<String, ResourceValue> inProjectStyleMap,
+ Map<String, ResourceValue> inFrameworkStyleMap) {
+ for (ResourceValue value : styles) {
+ if (value instanceof StyleResourceValue) {
+ StyleResourceValue style = (StyleResourceValue)value;
+ StyleResourceValue parentStyle = null;
+
+ // first look for a specified parent.
+ String parentName = style.getParentStyle();
+
+ // no specified parent? try to infer it from the name of the style.
+ if (parentName == null) {
+ parentName = getParentName(value.getName());
+ }
+
+ if (parentName != null) {
+ parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap);
+
+ if (parentStyle != null) {
+ mStyleInheritanceMap.put(style, parentStyle);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Computes the name of the parent style, or <code>null</code> if the style is a root style.
+ */
+ private String getParentName(String styleName) {
+ int index = styleName.lastIndexOf('.');
+ if (index != -1) {
+ return styleName.substring(0, index);
+ }
+
+ return null;
+ }
+
+ /**
+ * Searches for and returns the {@link StyleResourceValue} from a given name.
+ * <p/>The format of the name can be:
+ * <ul>
+ * <li>[android:]&lt;name&gt;</li>
+ * <li>[android:]style/&lt;name&gt;</li>
+ * <li>@[android:]style/&lt;name&gt;</li>
+ * </ul>
+ * @param parentName the name of the style.
+ * @param inProjectStyleMap the project style map. Can be <code>null</code>
+ * @param inFrameworkStyleMap the framework style map.
+ * @return The matching {@link StyleResourceValue} object or <code>null</code> if not found.
+ */
+ private StyleResourceValue getStyle(String parentName,
+ Map<String, ResourceValue> inProjectStyleMap,
+ Map<String, ResourceValue> inFrameworkStyleMap) {
+ boolean frameworkOnly = false;
+
+ String name = parentName;
+
+ // remove the useless @ if it's there
+ if (name.startsWith(PREFIX_RESOURCE_REF)) {
+ name = name.substring(PREFIX_RESOURCE_REF.length());
+ }
+
+ // check for framework identifier.
+ if (name.startsWith(PREFIX_ANDROID)) {
+ frameworkOnly = true;
+ name = name.substring(PREFIX_ANDROID.length());
+ }
+
+ // at this point we could have the format <type>/<name>. we want only the name as long as
+ // the type is style.
+ if (name.startsWith(REFERENCE_STYLE)) {
+ name = name.substring(REFERENCE_STYLE.length());
+ } else if (name.indexOf('/') != -1) {
+ return null;
+ }
+
+ ResourceValue parent = null;
+
+ // if allowed, search in the project resources.
+ if (frameworkOnly == false && inProjectStyleMap != null) {
+ parent = inProjectStyleMap.get(name);
+ }
+
+ // if not found, then look in the framework resources.
+ if (parent == null) {
+ parent = inFrameworkStyleMap.get(name);
+ }
+
+ // make sure the result is the proper class type and return it.
+ if (parent instanceof StyleResourceValue) {
+ return (StyleResourceValue)parent;
+ }
+
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_RESOURCES_RESOLVE,
+ String.format("Unable to resolve parent style name: %s", parentName),
+ null /*data*/);
+ }
+
+ return null;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/ScanningContext.java b/sdk_common/src/com/android/ide/common/resources/ScanningContext.java
new file mode 100644
index 0000000..e4ed275
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ScanningContext.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.resources;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link ScanningContext} keeps track of state during a resource file scan,
+ * such as any parsing errors encountered, whether Android ids have changed, and
+ * so on.
+ */
+public class ScanningContext {
+ private final ResourceRepository mRepository;
+ private boolean mNeedsFullAapt;
+ private List<String> mErrors = null;
+
+ /**
+ * Constructs a new {@link ScanningContext}
+ *
+ * @param repository the associated resource repository
+ */
+ public ScanningContext(ResourceRepository repository) {
+ super();
+ mRepository = repository;
+ }
+
+ /**
+ * Returns a list of errors encountered during scanning
+ *
+ * @return a list of errors encountered during scanning (or null)
+ */
+ public List<String> getErrors() {
+ return mErrors;
+ }
+
+ /**
+ * Adds the given error to the scanning context. The error should use the
+ * same syntax as real aapt error messages such that the aapt parser can
+ * properly detect the filename, line number, etc.
+ *
+ * @param error the error message, including file name and line number at
+ * the beginning
+ */
+ public void addError(String error) {
+ if (mErrors == null) {
+ mErrors = new ArrayList<String>();
+ }
+ mErrors.add(error);
+ }
+
+ /**
+ * Returns the repository associated with this scanning context
+ *
+ * @return the associated repository, never null
+ */
+ public ResourceRepository getRepository() {
+ return mRepository;
+ }
+
+ /**
+ * Marks that a full aapt compilation of the resources is necessary because it has
+ * detected a change that cannot be incrementally handled.
+ */
+ protected void requestFullAapt() {
+ mNeedsFullAapt = true;
+ }
+
+ /**
+ * Returns whether this repository has been marked as "dirty"; if one or
+ * more of the constituent files have declared that the resource item names
+ * that they provide have changed.
+ *
+ * @return true if a full aapt compilation is required
+ */
+ public boolean needsFullAapt() {
+ return mNeedsFullAapt;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java b/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java
new file mode 100644
index 0000000..6b663e9
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java
@@ -0,0 +1,142 @@
+/*
+ * 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.resources;
+
+import com.android.ide.common.rendering.api.DensityBasedResourceValue;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.io.IAbstractFile;
+import com.android.resources.FolderTypeRelationship;
+import com.android.resources.ResourceType;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Represents a resource file describing a single resource.
+ * <p/>
+ * This is typically an XML file inside res/anim, res/layout, or res/menu or an image file
+ * under res/drawable.
+ */
+public class SingleResourceFile extends ResourceFile {
+
+ private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance();
+ static {
+ sParserFactory.setNamespaceAware(true);
+ }
+
+ private final String mResourceName;
+ private final ResourceType mType;
+ private ResourceValue mValue;
+
+ public SingleResourceFile(IAbstractFile file, ResourceFolder folder) {
+ super(file, folder);
+
+ // we need to infer the type of the resource from the folder type.
+ // This is easy since this is a single Resource file.
+ List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folder.getType());
+ mType = types.get(0);
+
+ // compute the resource name
+ mResourceName = getResourceName(mType);
+
+ // test if there's a density qualifier associated with the resource
+ DensityQualifier qualifier = folder.getConfiguration().getDensityQualifier();
+
+ if (qualifier == null) {
+ mValue = new ResourceValue(mType, getResourceName(mType),
+ file.getOsLocation(), isFramework());
+ } else {
+ mValue = new DensityBasedResourceValue(
+ mType,
+ getResourceName(mType),
+ file.getOsLocation(),
+ qualifier.getValue(),
+ isFramework());
+ }
+ }
+
+ @Override
+ protected void load(ScanningContext context) {
+ // get a resource item matching the given type and name
+ ResourceItem item = getRepository().getResourceItem(mType, mResourceName);
+
+ // add this file to the list of files generating this resource item.
+ item.add(this);
+
+ // Ask for an ID refresh since we're adding an item that will generate an ID
+ context.requestFullAapt();
+ }
+
+ @Override
+ protected void update(ScanningContext context) {
+ // when this happens, nothing needs to be done since the file only generates
+ // a single resources that doesn't actually change (its content is the file path)
+ }
+
+ @Override
+ protected void dispose(ScanningContext context) {
+ // only remove this file from the existing ResourceItem.
+ getFolder().getRepository().removeFile(mType, this);
+
+ // Ask for an ID refresh since we're removing an item that previously generated an ID
+ context.requestFullAapt();
+
+ // don't need to touch the content, it'll get reclaimed as this objects disappear.
+ // In the mean time other objects may need to access it.
+ }
+
+ @Override
+ public Collection<ResourceType> getResourceTypes() {
+ return FolderTypeRelationship.getRelatedResourceTypes(getFolder().getType());
+ }
+
+ @Override
+ public boolean hasResources(ResourceType type) {
+ return FolderTypeRelationship.match(type, getFolder().getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.android.ide.eclipse.editors.resources.manager.ResourceFile#getValue(com.android.ide.eclipse.common.resources.ResourceType, java.lang.String)
+ *
+ * This particular implementation does not care about the type or name since a
+ * SingleResourceFile represents a file generating only one resource.
+ * The value returned is the full absolute path of the file in OS form.
+ */
+ @Override
+ public ResourceValue getValue(ResourceType type, String name) {
+ return mValue;
+ }
+
+ /**
+ * Returns the name of the resources.
+ */
+ private String getResourceName(ResourceType type) {
+ // get the name from the filename.
+ String name = getFile().getName();
+
+ int pos = name.indexOf('.');
+ if (pos != -1) {
+ name = name.substring(0, pos);
+ }
+
+ return name;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java b/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java
new file mode 100644
index 0000000..aabfd35
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ValueResourceParser.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2008 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.resources;
+
+import com.android.ide.common.rendering.api.AttrResourceValue;
+import com.android.ide.common.rendering.api.DeclareStyleableResourceValue;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.resources.ResourceType;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * SAX handler to parser value resource files.
+ */
+public final class ValueResourceParser extends DefaultHandler {
+
+ // TODO: reuse definitions from somewhere else.
+ private final static String NODE_RESOURCES = "resources";
+ private final static String NODE_ITEM = "item";
+ private final static String ATTR_NAME = "name";
+ private final static String ATTR_TYPE = "type";
+ private final static String ATTR_PARENT = "parent";
+ private final static String ATTR_VALUE = "value";
+
+ private final static String DEFAULT_NS_PREFIX = "android:";
+ private final static int DEFAULT_NS_PREFIX_LEN = DEFAULT_NS_PREFIX.length();
+
+ public interface IValueResourceRepository {
+ void addResourceValue(ResourceValue value);
+ boolean hasResourceValue(ResourceType type, String name);
+ }
+
+ private boolean inResources = false;
+ private int mDepth = 0;
+ private ResourceValue mCurrentValue = null;
+ private StyleResourceValue mCurrentStyle = null;
+ private DeclareStyleableResourceValue mCurrentDeclareStyleable = null;
+ private AttrResourceValue mCurrentAttr;
+ private IValueResourceRepository mRepository;
+ private final boolean mIsFramework;
+
+ public ValueResourceParser(IValueResourceRepository repository, boolean isFramework) {
+ mRepository = repository;
+ mIsFramework = isFramework;
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (mCurrentValue != null) {
+ mCurrentValue.setValue(trimXmlWhitespaces(mCurrentValue.getValue()));
+ }
+
+ if (inResources && qName.equals(NODE_RESOURCES)) {
+ inResources = false;
+ } else if (mDepth == 2) {
+ mCurrentValue = null;
+ mCurrentStyle = null;
+ mCurrentDeclareStyleable = null;
+ mCurrentAttr = null;
+ } else if (mDepth == 3) {
+ mCurrentValue = null;
+ if (mCurrentDeclareStyleable != null) {
+ mCurrentAttr = null;
+ }
+ }
+
+ mDepth--;
+ super.endElement(uri, localName, qName);
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ try {
+ mDepth++;
+ if (inResources == false && mDepth == 1) {
+ if (qName.equals(NODE_RESOURCES)) {
+ inResources = true;
+ }
+ } else if (mDepth == 2 && inResources == true) {
+ ResourceType type = getType(qName, attributes);
+
+ if (type != null) {
+ // get the resource name
+ String name = attributes.getValue(ATTR_NAME);
+ if (name != null) {
+ switch (type) {
+ case STYLE:
+ String parent = attributes.getValue(ATTR_PARENT);
+ mCurrentStyle = new StyleResourceValue(type, name, parent,
+ mIsFramework);
+ mRepository.addResourceValue(mCurrentStyle);
+ break;
+ case DECLARE_STYLEABLE:
+ mCurrentDeclareStyleable = new DeclareStyleableResourceValue(
+ type, name, mIsFramework);
+ mRepository.addResourceValue(mCurrentDeclareStyleable);
+ break;
+ case ATTR:
+ mCurrentAttr = new AttrResourceValue(type, name, mIsFramework);
+ mRepository.addResourceValue(mCurrentAttr);
+ break;
+ default:
+ mCurrentValue = new ResourceValue(type, name, mIsFramework);
+ mRepository.addResourceValue(mCurrentValue);
+ break;
+ }
+ }
+ }
+ } else if (mDepth == 3) {
+ // get the resource name
+ String name = attributes.getValue(ATTR_NAME);
+ if (name != null) {
+
+ if (mCurrentStyle != null) {
+ // is the attribute in the android namespace?
+ boolean isFrameworkAttr = mIsFramework;
+ if (name.startsWith(DEFAULT_NS_PREFIX)) {
+ name = name.substring(DEFAULT_NS_PREFIX_LEN);
+ isFrameworkAttr = true;
+ }
+
+ mCurrentValue = new ResourceValue(null, name, mIsFramework);
+ mCurrentStyle.addValue(mCurrentValue, isFrameworkAttr);
+ } else if (mCurrentDeclareStyleable != null) {
+ // is the attribute in the android namespace?
+ boolean isFramework = mIsFramework;
+ if (name.startsWith(DEFAULT_NS_PREFIX)) {
+ name = name.substring(DEFAULT_NS_PREFIX_LEN);
+ isFramework = true;
+ }
+
+ mCurrentAttr = new AttrResourceValue(ResourceType.ATTR, name, isFramework);
+ mCurrentDeclareStyleable.addValue(mCurrentAttr);
+
+ // also add it to the repository.
+ mRepository.addResourceValue(mCurrentAttr);
+
+ } else if (mCurrentAttr != null) {
+ // get the enum/flag value
+ String value = attributes.getValue(ATTR_VALUE);
+
+ try {
+ // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we
+ // use Long.decode instead.
+ mCurrentAttr.addValue(name, (int)(long)Long.decode(value));
+ } catch (NumberFormatException e) {
+ // pass, we'll just ignore this value
+ }
+
+ }
+ }
+ } else if (mDepth == 4 && mCurrentAttr != null) {
+ // get the enum/flag name
+ String name = attributes.getValue(ATTR_NAME);
+ String value = attributes.getValue(ATTR_VALUE);
+
+ try {
+ // Integer.decode/parseInt can't deal with hex value > 0x7FFFFFFF so we
+ // use Long.decode instead.
+ mCurrentAttr.addValue(name, (int)(long)Long.decode(value));
+ } catch (NumberFormatException e) {
+ // pass, we'll just ignore this value
+ }
+ }
+ } finally {
+ super.startElement(uri, localName, qName, attributes);
+ }
+ }
+
+ private ResourceType getType(String qName, Attributes attributes) {
+ String typeValue;
+
+ // if the node is <item>, we get the type from the attribute "type"
+ if (NODE_ITEM.equals(qName)) {
+ typeValue = attributes.getValue(ATTR_TYPE);
+ } else {
+ // the type is the name of the node.
+ typeValue = qName;
+ }
+
+ ResourceType type = ResourceType.getEnum(typeValue);
+ return type;
+ }
+
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ if (mCurrentValue != null) {
+ String value = mCurrentValue.getValue();
+ if (value == null) {
+ mCurrentValue.setValue(new String(ch, start, length));
+ } else {
+ mCurrentValue.setValue(value + new String(ch, start, length));
+ }
+ }
+ }
+
+ public static String trimXmlWhitespaces(String value) {
+ if (value == null) {
+ return null;
+ }
+
+ // look for carriage return and replace all whitespace around it by just 1 space.
+ int index;
+
+ while ((index = value.indexOf('\n')) != -1) {
+ // look for whitespace on each side
+ int left = index - 1;
+ while (left >= 0) {
+ if (Character.isWhitespace(value.charAt(left))) {
+ left--;
+ } else {
+ break;
+ }
+ }
+
+ int right = index + 1;
+ int count = value.length();
+ while (right < count) {
+ if (Character.isWhitespace(value.charAt(right))) {
+ right++;
+ } else {
+ break;
+ }
+ }
+
+ // remove all between left and right (non inclusive) and replace by a single space.
+ String leftString = null;
+ if (left >= 0) {
+ leftString = value.substring(0, left + 1);
+ }
+ String rightString = null;
+ if (right < count) {
+ rightString = value.substring(right);
+ }
+
+ if (leftString != null) {
+ value = leftString;
+ if (rightString != null) {
+ value += " " + rightString;
+ }
+ } else {
+ value = rightString != null ? rightString : "";
+ }
+ }
+
+ // now we un-escape the string
+ int length = value.length();
+ char[] buffer = value.toCharArray();
+
+ for (int i = 0 ; i < length ; i++) {
+ if (buffer[i] == '\\' && i + 1 < length) {
+ if (buffer[i+1] == 'u') {
+ if (i + 5 < length) {
+ // this is unicode char \u1234
+ int unicodeChar = Integer.parseInt(new String(buffer, i+2, 4), 16);
+
+ // put the unicode char at the location of the \
+ buffer[i] = (char)unicodeChar;
+
+ // offset the rest of the buffer since we go from 6 to 1 char
+ if (i + 6 < buffer.length) {
+ System.arraycopy(buffer, i+6, buffer, i+1, length - i - 6);
+ }
+ length -= 5;
+ }
+ } else {
+ if (buffer[i+1] == 'n') {
+ // replace the 'n' char with \n
+ buffer[i+1] = '\n';
+ }
+
+ // offset the buffer to erase the \
+ System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
+ length--;
+ }
+ } else if (buffer[i] == '"') {
+ // if the " was escaped it would have been processed above.
+ // offset the buffer to erase the "
+ System.arraycopy(buffer, i+1, buffer, i, length - i - 1);
+ length--;
+
+ // unlike when unescaping, we want to process the next char too
+ i--;
+ }
+ }
+
+ return new String(buffer, 0, length);
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/Configurable.java b/sdk_common/src/com/android/ide/common/resources/configuration/Configurable.java
new file mode 100644
index 0000000..5e7f910
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/Configurable.java
@@ -0,0 +1,28 @@
+/*
+ * 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.resources.configuration;
+
+
+/**
+ * An object that is associated with a {@link FolderConfiguration}.
+ */
+public interface Configurable {
+ /**
+ * Returns the {@link FolderConfiguration} for this object.
+ */
+ public FolderConfiguration getConfiguration();
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
new file mode 100644
index 0000000..eb7cc0d
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2008 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.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Mobile Country Code.
+ */
+public final class CountryCodeQualifier extends ResourceQualifier {
+ /** Default pixel density value. This means the property is not set. */
+ private final static int DEFAULT_CODE = -1;
+
+ private final static Pattern sCountryCodePattern = Pattern.compile("^mcc(\\d{3})$");//$NON-NLS-1$
+
+ private final int mCode;
+
+ public static final String NAME = "Mobile Country Code";
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link CountryCodeQualifier} object or <code>null</code>
+ */
+ public static CountryCodeQualifier getQualifier(String segment) {
+ Matcher m = sCountryCodePattern.matcher(segment);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ int code = -1;
+ try {
+ code = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ return null;
+ }
+
+ CountryCodeQualifier qualifier = new CountryCodeQualifier(code);
+ return qualifier;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link CountryCodeQualifier} object.
+ * @param code the value of the qualifier, as returned by {@link #getCode()}.
+ */
+ public static String getFolderSegment(int code) {
+ if (code != DEFAULT_CODE && code >= 100 && code <=999) { // code is 3 digit.) {
+ return String.format("mcc%1$d", code); //$NON-NLS-1$
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public CountryCodeQualifier() {
+ this(DEFAULT_CODE);
+ }
+
+ public CountryCodeQualifier(int code) {
+ mCode = code;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Country Code";
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mCode != DEFAULT_CODE;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ CountryCodeQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setCountryCodeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof CountryCodeQualifier) {
+ return mCode == ((CountryCodeQualifier)qualifier).mCode;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCode;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return getFolderSegment(mCode);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mCode != DEFAULT_CODE) {
+ return String.format("MCC %1$d", mCode);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ return getShortDisplayValue();
+ }
+
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/DensityQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/DensityQualifier.java
new file mode 100644
index 0000000..a9e4a01
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/DensityQualifier.java
@@ -0,0 +1,130 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.Density;
+import com.android.resources.ResourceEnum;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Screen Pixel Density.
+ */
+public final class DensityQualifier extends EnumBasedResourceQualifier {
+ private final static Pattern sDensityLegacyPattern = Pattern.compile("^(\\d+)dpi$");//$NON-NLS-1$
+
+ public static final String NAME = "Density";
+
+ private Density mValue = Density.MEDIUM;
+
+ public DensityQualifier() {
+ // pass
+ }
+
+ public DensityQualifier(Density value) {
+ mValue = value;
+ }
+
+ public Density getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 4;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Density density = Density.getEnum(value);
+ if (density == null) {
+
+ // attempt to read a legacy value.
+ Matcher m = sDensityLegacyPattern.matcher(value);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ try {
+ density = Density.getEnum(Integer.parseInt(v));
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number
+ // which really shouldn't happen since the regexp would have failed.
+ }
+ }
+ }
+
+ if (density != null) {
+ DensityQualifier qualifier = new DensityQualifier();
+ qualifier.mValue = density;
+ config.setDensityQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof DensityQualifier) {
+ // as long as there's a density qualifier, it's always a match.
+ // The best match will be found later.
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ DensityQualifier compareQ = (DensityQualifier)compareTo;
+ DensityQualifier referenceQ = (DensityQualifier)reference;
+
+ if (compareQ.mValue == referenceQ.mValue) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mValue == referenceQ.mValue) {
+ // got new exact value, this is the best!
+ return true;
+ } else {
+ // in all case we're going to prefer the higher dpi.
+ // if reference is high, we want highest dpi.
+ // if reference is medium, we'll prefer to scale down high dpi, than scale up low dpi
+ // if reference if low, we'll prefer to scale down high than medium (2:1 over 4:3)
+ return mValue.getDpiValue() > compareQ.mValue.getDpiValue();
+ }
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/DeviceConfigHelper.java b/sdk_common/src/com/android/ide/common/resources/configuration/DeviceConfigHelper.java
new file mode 100644
index 0000000..27eaa01
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/DeviceConfigHelper.java
@@ -0,0 +1,112 @@
+/*
+ * 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.ide.common.resources.configuration;
+
+import com.android.annotations.Nullable;
+import com.android.resources.NightMode;
+import com.android.resources.UiMode;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Hardware;
+import com.android.sdklib.devices.Screen;
+import com.android.sdklib.devices.State;
+
+public class DeviceConfigHelper {
+ /**
+ * Returns a {@link FolderConfiguration} based on the given state
+ *
+ * @param state
+ * The {@link State} of the {@link Device} to base the
+ * {@link FolderConfiguration} on. Can be null.
+ * @return A {@link FolderConfiguration} based on the given {@link State}.
+ * If the given {@link State} is null, the result is also null;
+ */
+ @Nullable
+ public static FolderConfiguration getFolderConfig(@Nullable State state) {
+ if (state == null) {
+ return null;
+ }
+
+ Hardware hw = state.getHardware();
+
+ FolderConfiguration config = new FolderConfiguration();
+ config.createDefault();
+ Screen screen = hw.getScreen();
+ config.setDensityQualifier(new DensityQualifier(screen.getPixelDensity()));
+ config.setNavigationMethodQualifier(new NavigationMethodQualifier(hw.getNav()));
+ ScreenDimensionQualifier sdq;
+ if (screen.getXDimension() > screen.getYDimension()) {
+ sdq = new ScreenDimensionQualifier(screen.getXDimension(), screen.getYDimension());
+ } else {
+ sdq = new ScreenDimensionQualifier(screen.getYDimension(), screen.getXDimension());
+ }
+ config.setScreenDimensionQualifier(sdq);
+ config.setScreenRatioQualifier(new ScreenRatioQualifier(screen.getRatio()));
+ config.setScreenSizeQualifier(new ScreenSizeQualifier(screen.getSize()));
+ config.setTextInputMethodQualifier(new TextInputMethodQualifier(hw.getKeyboard()));
+ config.setTouchTypeQualifier(new TouchScreenQualifier(screen.getMechanism()));
+
+ config.setKeyboardStateQualifier(new KeyboardStateQualifier(state.getKeyState()));
+ config.setNavigationStateQualifier(new NavigationStateQualifier(state.getNavState()));
+ config.setScreenOrientationQualifier(
+ new ScreenOrientationQualifier(state.getOrientation()));
+
+ config.updateScreenWidthAndHeight();
+
+ // Setup some default qualifiers
+ config.setUiModeQualifier(new UiModeQualifier(UiMode.NORMAL));
+ config.setNightModeQualifier(new NightModeQualifier(NightMode.NOTNIGHT));
+ config.setCountryCodeQualifier(new CountryCodeQualifier());
+ config.setLanguageQualifier(new LanguageQualifier());
+ config.setNetworkCodeQualifier(new NetworkCodeQualifier());
+ config.setRegionQualifier(new RegionQualifier());
+ config.setVersionQualifier(new VersionQualifier());
+
+ return config;
+ }
+
+ /**
+ * Returns a {@link FolderConfiguration} based on the {@link State} given by
+ * the {@link Device} and the state name.
+ *
+ * @param d
+ * The {@link Device} to base the {@link FolderConfiguration} on.
+ * @param stateName
+ * The name of the state to base the {@link FolderConfiguration}
+ * on.
+ * @return The {@link FolderConfiguration} based on the determined
+ * {@link State}. If there is no {@link State} with the given state
+ * name for the given device, null is returned.
+ */
+ @Nullable
+ public static FolderConfiguration getFolderConfig(Device d, String stateName) {
+ return getFolderConfig(d.getState(stateName));
+ }
+
+ /**
+ * Returns a {@link FolderConfiguration} based on the default {@link State}
+ * for the given {@link Device}.
+ *
+ * @param d
+ * The {@link Device} to generate the {@link FolderConfiguration}
+ * from.
+ * @return A {@link FolderConfiguration} based on the default {@link State}
+ * for the given {@link Device}
+ */
+ public static FolderConfiguration getFolderConfig(Device d) {
+ return getFolderConfig(d.getDefaultState());
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java
new file mode 100644
index 0000000..7bfda2d
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java
@@ -0,0 +1,92 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+
+/**
+ * Base class for {@link ResourceQualifier} whose value is an {@link ResourceEnum}.
+ *
+ */
+abstract class EnumBasedResourceQualifier extends ResourceQualifier {
+
+ abstract ResourceEnum getEnumValue();
+
+ @Override
+ public boolean isValid() {
+ return getEnumValue() != null;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return getEnumValue().isFakeValue();
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof EnumBasedResourceQualifier) {
+ return getEnumValue() == ((EnumBasedResourceQualifier)qualifier).getEnumValue();
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ ResourceEnum value = getEnumValue();
+ if (value != null) {
+ return value.hashCode();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public final String getFolderSegment() {
+ ResourceEnum value = getEnumValue();
+ if (value != null) {
+ return value.getResourceValue();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+
+ @Override
+ public String getShortDisplayValue() {
+ ResourceEnum value = getEnumValue();
+ if (value != null) {
+ return value.getShortDisplayValue();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ ResourceEnum value = getEnumValue();
+ if (value != null) {
+ return value.getLongDisplayValue();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java b/sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java
new file mode 100644
index 0000000..e2fe767
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java
@@ -0,0 +1,886 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.SdkConstants;
+import com.android.resources.Density;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ScreenOrientation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Represents the configuration for Resource Folders. All the properties have a default
+ * value which means that the property is not set.
+ */
+public final class FolderConfiguration implements Comparable<FolderConfiguration> {
+
+ private final static ResourceQualifier[] DEFAULT_QUALIFIERS;
+
+ static {
+ // get the default qualifiers.
+ FolderConfiguration defaultConfig = new FolderConfiguration();
+ defaultConfig.createDefault();
+ DEFAULT_QUALIFIERS = defaultConfig.getQualifiers();
+ }
+
+
+ private final ResourceQualifier[] mQualifiers = new ResourceQualifier[INDEX_COUNT];
+
+ private final static int INDEX_COUNTRY_CODE = 0;
+ private final static int INDEX_NETWORK_CODE = 1;
+ private final static int INDEX_LANGUAGE = 2;
+ private final static int INDEX_REGION = 3;
+ private final static int INDEX_SMALLEST_SCREEN_WIDTH = 4;
+ private final static int INDEX_SCREEN_WIDTH = 5;
+ private final static int INDEX_SCREEN_HEIGHT = 6;
+ private final static int INDEX_SCREEN_LAYOUT_SIZE = 7;
+ private final static int INDEX_SCREEN_RATIO = 8;
+ private final static int INDEX_SCREEN_ORIENTATION = 9;
+ private final static int INDEX_UI_MODE = 10;
+ private final static int INDEX_NIGHT_MODE = 11;
+ private final static int INDEX_PIXEL_DENSITY = 12;
+ private final static int INDEX_TOUCH_TYPE = 13;
+ private final static int INDEX_KEYBOARD_STATE = 14;
+ private final static int INDEX_TEXT_INPUT_METHOD = 15;
+ private final static int INDEX_NAVIGATION_STATE = 16;
+ private final static int INDEX_NAVIGATION_METHOD = 17;
+ private final static int INDEX_SCREEN_DIMENSION = 18;
+ private final static int INDEX_VERSION = 19;
+ private final static int INDEX_COUNT = 20;
+
+ /**
+ * Creates a {@link FolderConfiguration} matching the folder segments.
+ * @param folderSegments The segments of the folder name. The first segments should contain
+ * the name of the folder
+ * @return a FolderConfiguration object, or null if the folder name isn't valid..
+ */
+ public static FolderConfiguration getConfig(String[] folderSegments) {
+ FolderConfiguration config = new FolderConfiguration();
+
+ // we are going to loop through the segments, and match them with the first
+ // available qualifier. If the segment doesn't match we try with the next qualifier.
+ // Because the order of the qualifier is fixed, we do not reset the first qualifier
+ // after each successful segment.
+ // If we run out of qualifier before processing all the segments, we fail.
+
+ int qualifierIndex = 0;
+ int qualifierCount = DEFAULT_QUALIFIERS.length;
+
+ for (int i = 1 ; i < folderSegments.length; i++) {
+ String seg = folderSegments[i];
+ if (seg.length() > 0) {
+ while (qualifierIndex < qualifierCount &&
+ DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config) == false) {
+ qualifierIndex++;
+ }
+
+ // if we reached the end of the qualifier we didn't find a matching qualifier.
+ if (qualifierIndex == qualifierCount) {
+ return null;
+ }
+
+ } else {
+ return null;
+ }
+ }
+
+ return config;
+ }
+
+ /**
+ * Returns the number of {@link ResourceQualifier} that make up a Folder configuration.
+ */
+ public static int getQualifierCount() {
+ return INDEX_COUNT;
+ }
+
+ /**
+ * Sets the config from the qualifiers of a given <var>config</var>.
+ * <p/>This is equivalent to <code>set(config, false)</code>
+ * @param config the configuration to set
+ *
+ * @see #set(FolderConfiguration, boolean)
+ */
+ public void set(FolderConfiguration config) {
+ set(config, false /*nonFakeValuesOnly*/);
+ }
+
+ /**
+ * Sets the config from the qualifiers of a given <var>config</var>.
+ * @param config the configuration to set
+ * @param nonFakeValuesOnly if set to true this ignore qualifiers for which the
+ * current value is a fake value.
+ *
+ * @see ResourceQualifier#hasFakeValue()
+ */
+ public void set(FolderConfiguration config, boolean nonFakeValuesOnly) {
+ if (config != null) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier q = config.mQualifiers[i];
+ if (nonFakeValuesOnly == false || q == null || q.hasFakeValue() == false) {
+ mQualifiers[i] = q;
+ }
+ }
+ }
+ }
+
+ /**
+ * Reset the config.
+ * <p/>This makes qualifiers at all indices <code>null</code>.
+ */
+ public void reset() {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ mQualifiers[i] = null;
+ }
+ }
+
+ /**
+ * Removes the qualifiers from the receiver if they are present (and valid)
+ * in the given configuration.
+ */
+ public void substract(FolderConfiguration config) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (config.mQualifiers[i] != null && config.mQualifiers[i].isValid()) {
+ mQualifiers[i] = null;
+ }
+ }
+ }
+
+ /**
+ * Adds the non-qualifiers from the given config.
+ * Qualifiers that are null in the given config do not change in the receiver.
+ */
+ public void add(FolderConfiguration config) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (config.mQualifiers[i] != null) {
+ mQualifiers[i] = config.mQualifiers[i];
+ }
+ }
+ }
+
+ /**
+ * Returns the first invalid qualifier, or <code>null<code> if they are all valid (or if none
+ * exists).
+ */
+ public ResourceQualifier getInvalidQualifier() {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null && mQualifiers[i].isValid() == false) {
+ return mQualifiers[i];
+ }
+ }
+
+ // all allocated qualifiers are valid, we return null.
+ return null;
+ }
+
+ /**
+ * Returns whether the Region qualifier is valid. Region qualifier can only be present if a
+ * Language qualifier is present as well.
+ * @return true if the Region qualifier is valid.
+ */
+ public boolean checkRegion() {
+ if (mQualifiers[INDEX_LANGUAGE] == null && mQualifiers[INDEX_REGION] != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds a qualifier to the {@link FolderConfiguration}
+ * @param qualifier the {@link ResourceQualifier} to add.
+ */
+ public void addQualifier(ResourceQualifier qualifier) {
+ if (qualifier instanceof CountryCodeQualifier) {
+ mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
+
+ } else if (qualifier instanceof NetworkCodeQualifier) {
+ mQualifiers[INDEX_NETWORK_CODE] = qualifier;
+
+ } else if (qualifier instanceof LanguageQualifier) {
+ mQualifiers[INDEX_LANGUAGE] = qualifier;
+
+ } else if (qualifier instanceof RegionQualifier) {
+ mQualifiers[INDEX_REGION] = qualifier;
+
+ } else if (qualifier instanceof SmallestScreenWidthQualifier) {
+ mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = qualifier;
+
+ } else if (qualifier instanceof ScreenWidthQualifier) {
+ mQualifiers[INDEX_SCREEN_WIDTH] = qualifier;
+
+ } else if (qualifier instanceof ScreenHeightQualifier) {
+ mQualifiers[INDEX_SCREEN_HEIGHT] = qualifier;
+
+ } else if (qualifier instanceof ScreenSizeQualifier) {
+ mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = qualifier;
+
+ } else if (qualifier instanceof ScreenRatioQualifier) {
+ mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
+
+ } else if (qualifier instanceof ScreenOrientationQualifier) {
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
+
+ } else if (qualifier instanceof UiModeQualifier) {
+ mQualifiers[INDEX_UI_MODE] = qualifier;
+
+ } else if (qualifier instanceof NightModeQualifier) {
+ mQualifiers[INDEX_NIGHT_MODE] = qualifier;
+
+ } else if (qualifier instanceof DensityQualifier) {
+ mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
+
+ } else if (qualifier instanceof TouchScreenQualifier) {
+ mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
+
+ } else if (qualifier instanceof KeyboardStateQualifier) {
+ mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
+
+ } else if (qualifier instanceof TextInputMethodQualifier) {
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
+
+ } else if (qualifier instanceof NavigationStateQualifier) {
+ mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
+
+ } else if (qualifier instanceof NavigationMethodQualifier) {
+ mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
+
+ } else if (qualifier instanceof ScreenDimensionQualifier) {
+ mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
+
+ } else if (qualifier instanceof VersionQualifier) {
+ mQualifiers[INDEX_VERSION] = qualifier;
+
+ }
+ }
+
+ /**
+ * Removes a given qualifier from the {@link FolderConfiguration}.
+ * @param qualifier the {@link ResourceQualifier} to remove.
+ */
+ public void removeQualifier(ResourceQualifier qualifier) {
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] == qualifier) {
+ mQualifiers[i] = null;
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns a qualifier by its index. The total number of qualifiers can be accessed by
+ * {@link #getQualifierCount()}.
+ * @param index the index of the qualifier to return.
+ * @return the qualifier or null if there are none at the index.
+ */
+ public ResourceQualifier getQualifier(int index) {
+ return mQualifiers[index];
+ }
+
+ public void setCountryCodeQualifier(CountryCodeQualifier qualifier) {
+ mQualifiers[INDEX_COUNTRY_CODE] = qualifier;
+ }
+
+ public CountryCodeQualifier getCountryCodeQualifier() {
+ return (CountryCodeQualifier)mQualifiers[INDEX_COUNTRY_CODE];
+ }
+
+ public void setNetworkCodeQualifier(NetworkCodeQualifier qualifier) {
+ mQualifiers[INDEX_NETWORK_CODE] = qualifier;
+ }
+
+ public NetworkCodeQualifier getNetworkCodeQualifier() {
+ return (NetworkCodeQualifier)mQualifiers[INDEX_NETWORK_CODE];
+ }
+
+ public void setLanguageQualifier(LanguageQualifier qualifier) {
+ mQualifiers[INDEX_LANGUAGE] = qualifier;
+ }
+
+ public LanguageQualifier getLanguageQualifier() {
+ return (LanguageQualifier)mQualifiers[INDEX_LANGUAGE];
+ }
+
+ public void setRegionQualifier(RegionQualifier qualifier) {
+ mQualifiers[INDEX_REGION] = qualifier;
+ }
+
+ public RegionQualifier getRegionQualifier() {
+ return (RegionQualifier)mQualifiers[INDEX_REGION];
+ }
+
+ public void setSmallestScreenWidthQualifier(SmallestScreenWidthQualifier qualifier) {
+ mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = qualifier;
+ }
+
+ public SmallestScreenWidthQualifier getSmallestScreenWidthQualifier() {
+ return (SmallestScreenWidthQualifier) mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH];
+ }
+
+ public void setScreenWidthQualifier(ScreenWidthQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_WIDTH] = qualifier;
+ }
+
+ public ScreenWidthQualifier getScreenWidthQualifier() {
+ return (ScreenWidthQualifier) mQualifiers[INDEX_SCREEN_WIDTH];
+ }
+
+ public void setScreenHeightQualifier(ScreenHeightQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_HEIGHT] = qualifier;
+ }
+
+ public ScreenHeightQualifier getScreenHeightQualifier() {
+ return (ScreenHeightQualifier) mQualifiers[INDEX_SCREEN_HEIGHT];
+ }
+
+ public void setScreenSizeQualifier(ScreenSizeQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = qualifier;
+ }
+
+ public ScreenSizeQualifier getScreenSizeQualifier() {
+ return (ScreenSizeQualifier)mQualifiers[INDEX_SCREEN_LAYOUT_SIZE];
+ }
+
+ public void setScreenRatioQualifier(ScreenRatioQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_RATIO] = qualifier;
+ }
+
+ public ScreenRatioQualifier getScreenRatioQualifier() {
+ return (ScreenRatioQualifier)mQualifiers[INDEX_SCREEN_RATIO];
+ }
+
+ public void setScreenOrientationQualifier(ScreenOrientationQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier;
+ }
+
+ public ScreenOrientationQualifier getScreenOrientationQualifier() {
+ return (ScreenOrientationQualifier)mQualifiers[INDEX_SCREEN_ORIENTATION];
+ }
+
+ public void setUiModeQualifier(UiModeQualifier qualifier) {
+ mQualifiers[INDEX_UI_MODE] = qualifier;
+ }
+
+ public UiModeQualifier getUiModeQualifier() {
+ return (UiModeQualifier)mQualifiers[INDEX_UI_MODE];
+ }
+
+ public void setNightModeQualifier(NightModeQualifier qualifier) {
+ mQualifiers[INDEX_NIGHT_MODE] = qualifier;
+ }
+
+ public NightModeQualifier getNightModeQualifier() {
+ return (NightModeQualifier)mQualifiers[INDEX_NIGHT_MODE];
+ }
+
+ public void setDensityQualifier(DensityQualifier qualifier) {
+ mQualifiers[INDEX_PIXEL_DENSITY] = qualifier;
+ }
+
+ public DensityQualifier getDensityQualifier() {
+ return (DensityQualifier)mQualifiers[INDEX_PIXEL_DENSITY];
+ }
+
+ public void setTouchTypeQualifier(TouchScreenQualifier qualifier) {
+ mQualifiers[INDEX_TOUCH_TYPE] = qualifier;
+ }
+
+ public TouchScreenQualifier getTouchTypeQualifier() {
+ return (TouchScreenQualifier)mQualifiers[INDEX_TOUCH_TYPE];
+ }
+
+ public void setKeyboardStateQualifier(KeyboardStateQualifier qualifier) {
+ mQualifiers[INDEX_KEYBOARD_STATE] = qualifier;
+ }
+
+ public KeyboardStateQualifier getKeyboardStateQualifier() {
+ return (KeyboardStateQualifier)mQualifiers[INDEX_KEYBOARD_STATE];
+ }
+
+ public void setTextInputMethodQualifier(TextInputMethodQualifier qualifier) {
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier;
+ }
+
+ public TextInputMethodQualifier getTextInputMethodQualifier() {
+ return (TextInputMethodQualifier)mQualifiers[INDEX_TEXT_INPUT_METHOD];
+ }
+
+ public void setNavigationStateQualifier(NavigationStateQualifier qualifier) {
+ mQualifiers[INDEX_NAVIGATION_STATE] = qualifier;
+ }
+
+ public NavigationStateQualifier getNavigationStateQualifier() {
+ return (NavigationStateQualifier)mQualifiers[INDEX_NAVIGATION_STATE];
+ }
+
+ public void setNavigationMethodQualifier(NavigationMethodQualifier qualifier) {
+ mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier;
+ }
+
+ public NavigationMethodQualifier getNavigationMethodQualifier() {
+ return (NavigationMethodQualifier)mQualifiers[INDEX_NAVIGATION_METHOD];
+ }
+
+ public void setScreenDimensionQualifier(ScreenDimensionQualifier qualifier) {
+ mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier;
+ }
+
+ public ScreenDimensionQualifier getScreenDimensionQualifier() {
+ return (ScreenDimensionQualifier)mQualifiers[INDEX_SCREEN_DIMENSION];
+ }
+
+ public void setVersionQualifier(VersionQualifier qualifier) {
+ mQualifiers[INDEX_VERSION] = qualifier;
+ }
+
+ public VersionQualifier getVersionQualifier() {
+ return (VersionQualifier)mQualifiers[INDEX_VERSION];
+ }
+
+ /**
+ * Updates the {@link SmallestScreenWidthQualifier}, {@link ScreenWidthQualifier}, and
+ * {@link ScreenHeightQualifier} based on the (required) values of
+ * {@link ScreenDimensionQualifier} {@link DensityQualifier}, and
+ * {@link ScreenOrientationQualifier}.
+ *
+ * Also the density cannot be {@link Density#NODPI} as it's not valid on a device.
+ */
+ public void updateScreenWidthAndHeight() {
+
+ ResourceQualifier sizeQ = mQualifiers[INDEX_SCREEN_DIMENSION];
+ ResourceQualifier densityQ = mQualifiers[INDEX_PIXEL_DENSITY];
+ ResourceQualifier orientQ = mQualifiers[INDEX_SCREEN_ORIENTATION];
+
+ if (sizeQ != null && densityQ != null && orientQ != null) {
+ Density density = ((DensityQualifier) densityQ).getValue();
+ if (density == Density.NODPI) {
+ return;
+ }
+
+ ScreenOrientation orientation = ((ScreenOrientationQualifier) orientQ).getValue();
+
+ int size1 = ((ScreenDimensionQualifier) sizeQ).getValue1();
+ int size2 = ((ScreenDimensionQualifier) sizeQ).getValue2();
+
+ // make sure size1 is the biggest (should be the case, but make sure)
+ if (size1 < size2) {
+ int a = size1;
+ size1 = size2;
+ size2 = a;
+ }
+
+ // compute the dp. round them up since we want -w480dp to match a 480.5dp screen
+ int dp1 = (int) Math.ceil(size1 * Density.DEFAULT_DENSITY / density.getDpiValue());
+ int dp2 = (int) Math.ceil(size2 * Density.DEFAULT_DENSITY / density.getDpiValue());
+
+ setSmallestScreenWidthQualifier(new SmallestScreenWidthQualifier(dp2));
+
+ switch (orientation) {
+ case PORTRAIT:
+ setScreenWidthQualifier(new ScreenWidthQualifier(dp2));
+ setScreenHeightQualifier(new ScreenHeightQualifier(dp1));
+ break;
+ case LANDSCAPE:
+ setScreenWidthQualifier(new ScreenWidthQualifier(dp1));
+ setScreenHeightQualifier(new ScreenHeightQualifier(dp2));
+ break;
+ case SQUARE:
+ setScreenWidthQualifier(new ScreenWidthQualifier(dp2));
+ setScreenHeightQualifier(new ScreenHeightQualifier(dp2));
+ break;
+ }
+ }
+ }
+
+ /**
+ * Returns whether an object is equals to the receiver.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj instanceof FolderConfiguration) {
+ FolderConfiguration fc = (FolderConfiguration)obj;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier qualifier = mQualifiers[i];
+ ResourceQualifier fcQualifier = fc.mQualifiers[i];
+ if (qualifier != null) {
+ if (qualifier.equals(fcQualifier) == false) {
+ return false;
+ }
+ } else if (fcQualifier != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ /**
+ * Returns whether the Configuration has only default values.
+ */
+ public boolean isDefault() {
+ for (ResourceQualifier irq : mQualifiers) {
+ if (irq != null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the name of a folder with the configuration.
+ */
+ public String getFolderName(ResourceFolderType folder) {
+ StringBuilder result = new StringBuilder(folder.getName());
+
+ for (ResourceQualifier qualifier : mQualifiers) {
+ if (qualifier != null) {
+ String segment = qualifier.getFolderSegment();
+ if (segment != null && segment.length() > 0) {
+ result.append(SdkConstants.RES_QUALIFIER_SEP);
+ result.append(segment);
+ }
+ }
+ }
+
+ return result.toString();
+ }
+
+ /**
+ * Returns {@link #toDisplayString()}.
+ */
+ @Override
+ public String toString() {
+ return toDisplayString();
+ }
+
+ /**
+ * Returns a string valid for display purpose.
+ */
+ public String toDisplayString() {
+ if (isDefault()) {
+ return "default";
+ }
+
+ StringBuilder result = null;
+ int index = 0;
+ ResourceQualifier qualifier = null;
+
+ // pre- language/region qualifiers
+ while (index < INDEX_LANGUAGE) {
+ qualifier = mQualifiers[index++];
+ if (qualifier != null) {
+ if (result == null) {
+ result = new StringBuilder();
+ } else {
+ result.append(", "); //$NON-NLS-1$
+ }
+ result.append(qualifier.getLongDisplayValue());
+
+ }
+ }
+
+ // process the language/region qualifier in a custom way, if there are both non null.
+ if (mQualifiers[INDEX_LANGUAGE] != null && mQualifiers[INDEX_REGION] != null) {
+ String language = mQualifiers[INDEX_LANGUAGE].getLongDisplayValue();
+ String region = mQualifiers[INDEX_REGION].getLongDisplayValue();
+
+ if (result == null) {
+ result = new StringBuilder();
+ } else {
+ result.append(", "); //$NON-NLS-1$
+ }
+ result.append(String.format("Locale %s_%s", language, region)); //$NON-NLS-1$
+
+ index += 2;
+ }
+
+ // post language/region qualifiers.
+ while (index < INDEX_COUNT) {
+ qualifier = mQualifiers[index++];
+ if (qualifier != null) {
+ if (result == null) {
+ result = new StringBuilder();
+ } else {
+ result.append(", "); //$NON-NLS-1$
+ }
+ result.append(qualifier.getLongDisplayValue());
+
+ }
+ }
+
+ return result == null ? null : result.toString();
+ }
+
+ @Override
+ public int compareTo(FolderConfiguration folderConfig) {
+ // default are always at the top.
+ if (isDefault()) {
+ if (folderConfig.isDefault()) {
+ return 0;
+ }
+ return -1;
+ }
+
+ // now we compare the qualifiers
+ for (int i = 0 ; i < INDEX_COUNT; i++) {
+ ResourceQualifier qualifier1 = mQualifiers[i];
+ ResourceQualifier qualifier2 = folderConfig.mQualifiers[i];
+
+ if (qualifier1 == null) {
+ if (qualifier2 == null) {
+ continue;
+ } else {
+ return -1;
+ }
+ } else {
+ if (qualifier2 == null) {
+ return 1;
+ } else {
+ int result = qualifier1.compareTo(qualifier2);
+
+ if (result == 0) {
+ continue;
+ }
+
+ return result;
+ }
+ }
+ }
+
+ // if we arrive here, all the qualifier matches
+ return 0;
+ }
+
+ /**
+ * Returns the best matching {@link Configurable} for this configuration.
+ *
+ * @param configurables the list of {@link Configurable} to choose from.
+ *
+ * @return an item from the given list of {@link Configurable} or null.
+ *
+ * @see http://d.android.com/guide/topics/resources/resources-i18n.html#best-match
+ */
+ public Configurable findMatchingConfigurable(List<? extends Configurable> configurables) {
+ //
+ // 1: eliminate resources that contradict the reference configuration
+ // 2: pick next qualifier type
+ // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4.
+ // 4: eliminate resources that don't use this qualifier.
+ // 5: if more than one resource left, go back to 2.
+ //
+ // The precedence of the qualifiers is more important than the number of qualifiers that
+ // exactly match the device.
+
+ // 1: eliminate resources that contradict
+ ArrayList<Configurable> matchingConfigurables = new ArrayList<Configurable>();
+ for (int i = 0 ; i < configurables.size(); i++) {
+ Configurable res = configurables.get(i);
+
+ if (res.getConfiguration().isMatchFor(this)) {
+ matchingConfigurables.add(res);
+ }
+ }
+
+ // if there is only one match, just take it
+ if (matchingConfigurables.size() == 1) {
+ return matchingConfigurables.get(0);
+ } else if (matchingConfigurables.size() == 0) {
+ return null;
+ }
+
+ // 2. Loop on the qualifiers, and eliminate matches
+ final int count = FolderConfiguration.getQualifierCount();
+ for (int q = 0 ; q < count ; q++) {
+ // look to see if one configurable has this qualifier.
+ // At the same time also record the best match value for the qualifier (if applicable).
+
+ // The reference value, to find the best match.
+ // Note that this qualifier could be null. In which case any qualifier found in the
+ // possible match, will all be considered best match.
+ ResourceQualifier referenceQualifier = getQualifier(q);
+
+ boolean found = false;
+ ResourceQualifier bestMatch = null; // this is to store the best match.
+ for (Configurable configurable : matchingConfigurables) {
+ ResourceQualifier qualifier = configurable.getConfiguration().getQualifier(q);
+ if (qualifier != null) {
+ // set the flag.
+ found = true;
+
+ // Now check for a best match. If the reference qualifier is null ,
+ // any qualifier is a "best" match (we don't need to record all of them.
+ // Instead the non compatible ones are removed below)
+ if (referenceQualifier != null) {
+ if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) {
+ bestMatch = qualifier;
+ }
+ }
+ }
+ }
+
+ // 4. If a configurable has a qualifier at the current index, remove all the ones that
+ // do not have one, or whose qualifier value does not equal the best match found above
+ // unless there's no reference qualifier, in which case they are all considered
+ // "best" match.
+ if (found) {
+ for (int i = 0 ; i < matchingConfigurables.size(); ) {
+ Configurable configurable = matchingConfigurables.get(i);
+ ResourceQualifier qualifier = configurable.getConfiguration().getQualifier(q);
+
+ if (qualifier == null) {
+ // this resources has no qualifier of this type: rejected.
+ matchingConfigurables.remove(configurable);
+ } else if (referenceQualifier != null && bestMatch != null &&
+ bestMatch.equals(qualifier) == false) {
+ // there's a reference qualifier and there is a better match for it than
+ // this resource, so we reject it.
+ matchingConfigurables.remove(configurable);
+ } else {
+ // looks like we keep this resource, move on to the next one.
+ i++;
+ }
+ }
+
+ // at this point we may have run out of matching resources before going
+ // through all the qualifiers.
+ if (matchingConfigurables.size() < 2) {
+ break;
+ }
+ }
+ }
+
+ // Because we accept resources whose configuration have qualifiers where the reference
+ // configuration doesn't, we can end up with more than one match. In this case, we just
+ // take the first one.
+ if (matchingConfigurables.size() == 0) {
+ return null;
+ }
+ return matchingConfigurables.get(0);
+ }
+
+
+ /**
+ * Returns whether the configuration is a match for the given reference config.
+ * <p/>A match means that, for each qualifier of this config
+ * <ul>
+ * <li>The reference config has no value set
+ * <li>or, the qualifier of the reference config is a match. Depending on the qualifier type
+ * this does not mean the same exact value.</li>
+ * </ul>
+ * @param referenceConfig The reference configuration to test against.
+ * @return true if the configuration matches.
+ */
+ public boolean isMatchFor(FolderConfiguration referenceConfig) {
+ if (referenceConfig == null) {
+ return false;
+ }
+
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ ResourceQualifier testQualifier = mQualifiers[i];
+ ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i];
+
+ // it's only a non match if both qualifiers are non-null, and they don't match.
+ if (testQualifier != null && referenceQualifier != null &&
+ testQualifier.isMatchFor(referenceQualifier) == false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the index of the first non null {@link ResourceQualifier} starting at index
+ * <var>startIndex</var>
+ * @param startIndex
+ * @return -1 if no qualifier was found.
+ */
+ public int getHighestPriorityQualifier(int startIndex) {
+ for (int i = startIndex ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Create default qualifiers.
+ * <p/>This creates qualifiers with no values for all indices.
+ */
+ public void createDefault() {
+ mQualifiers[INDEX_COUNTRY_CODE] = new CountryCodeQualifier();
+ mQualifiers[INDEX_NETWORK_CODE] = new NetworkCodeQualifier();
+ mQualifiers[INDEX_LANGUAGE] = new LanguageQualifier();
+ mQualifiers[INDEX_REGION] = new RegionQualifier();
+ mQualifiers[INDEX_SMALLEST_SCREEN_WIDTH] = new SmallestScreenWidthQualifier();
+ mQualifiers[INDEX_SCREEN_WIDTH] = new ScreenWidthQualifier();
+ mQualifiers[INDEX_SCREEN_HEIGHT] = new ScreenHeightQualifier();
+ mQualifiers[INDEX_SCREEN_LAYOUT_SIZE] = new ScreenSizeQualifier();
+ mQualifiers[INDEX_SCREEN_RATIO] = new ScreenRatioQualifier();
+ mQualifiers[INDEX_SCREEN_ORIENTATION] = new ScreenOrientationQualifier();
+ mQualifiers[INDEX_UI_MODE] = new UiModeQualifier();
+ mQualifiers[INDEX_NIGHT_MODE] = new NightModeQualifier();
+ mQualifiers[INDEX_PIXEL_DENSITY] = new DensityQualifier();
+ mQualifiers[INDEX_TOUCH_TYPE] = new TouchScreenQualifier();
+ mQualifiers[INDEX_KEYBOARD_STATE] = new KeyboardStateQualifier();
+ mQualifiers[INDEX_TEXT_INPUT_METHOD] = new TextInputMethodQualifier();
+ mQualifiers[INDEX_NAVIGATION_STATE] = new NavigationStateQualifier();
+ mQualifiers[INDEX_NAVIGATION_METHOD] = new NavigationMethodQualifier();
+ mQualifiers[INDEX_SCREEN_DIMENSION] = new ScreenDimensionQualifier();
+ mQualifiers[INDEX_VERSION] = new VersionQualifier();
+ }
+
+ /**
+ * Returns an array of all the non null qualifiers.
+ */
+ public ResourceQualifier[] getQualifiers() {
+ int count = 0;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ count++;
+ }
+ }
+
+ ResourceQualifier[] array = new ResourceQualifier[count];
+ int index = 0;
+ for (int i = 0 ; i < INDEX_COUNT ; i++) {
+ if (mQualifiers[i] != null) {
+ array[index++] = mQualifiers[i];
+ }
+ }
+
+ return array;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java
new file mode 100644
index 0000000..1397808
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2008 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.resources.configuration;
+
+import com.android.resources.KeyboardState;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for keyboard state.
+ */
+public final class KeyboardStateQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Keyboard State";
+
+ private KeyboardState mValue = null;
+
+ public KeyboardStateQualifier() {
+ // pass
+ }
+
+ public KeyboardStateQualifier(KeyboardState value) {
+ mValue = value;
+ }
+
+ public KeyboardState getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Keyboard";
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ KeyboardState orientation = KeyboardState.getEnum(value);
+ if (orientation != null) {
+ KeyboardStateQualifier qualifier = new KeyboardStateQualifier();
+ qualifier.mValue = orientation;
+ config.setKeyboardStateQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof KeyboardStateQualifier) {
+ KeyboardStateQualifier referenceQualifier = (KeyboardStateQualifier)qualifier;
+
+ // special case where EXPOSED can be used for SOFT
+ if (referenceQualifier.mValue == KeyboardState.SOFT &&
+ mValue == KeyboardState.EXPOSED) {
+ return true;
+ }
+
+ return referenceQualifier.mValue == mValue;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ KeyboardStateQualifier compareQualifier = (KeyboardStateQualifier)compareTo;
+ KeyboardStateQualifier referenceQualifier = (KeyboardStateQualifier)reference;
+
+ if (referenceQualifier.mValue == KeyboardState.SOFT) { // only case where there could be a
+ // better qualifier
+ // only return true if it's a better value.
+ if (compareQualifier.mValue == KeyboardState.EXPOSED && mValue == KeyboardState.SOFT) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java
new file mode 100644
index 0000000..76514e2
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java
@@ -0,0 +1,165 @@
+/*
+ * 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.resources.configuration;
+
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Language.
+ */
+public final class LanguageQualifier extends ResourceQualifier {
+ private final static Pattern sLanguagePattern = Pattern.compile("^[a-z]{2}$"); //$NON-NLS-1$
+
+ public static final String FAKE_LANG_VALUE = "__"; //$NON-NLS-1$
+ public static final String NAME = "Language";
+
+ private String mValue;
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link LanguageQualifier} object or <code>null</code>
+ */
+ public static LanguageQualifier getQualifier(String segment) {
+ if (sLanguagePattern.matcher(segment).matches()) {
+ LanguageQualifier qualifier = new LanguageQualifier();
+ qualifier.mValue = segment;
+
+ return qualifier;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link LanguageQualifier} object.
+ * @param value the value of the qualifier, as returned by {@link #getValue()}.
+ */
+ public static String getFolderSegment(String value) {
+ String segment = value.toLowerCase(Locale.US);
+ if (sLanguagePattern.matcher(segment).matches()) {
+ return segment;
+ }
+
+ return null;
+ }
+
+ public LanguageQualifier() {
+
+ }
+
+ public LanguageQualifier(String value) {
+ mValue = value;
+ }
+
+ public String getValue() {
+ if (mValue != null) {
+ return mValue;
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue != null;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return FAKE_LANG_VALUE.equals(mValue);
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ LanguageQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setLanguageQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof LanguageQualifier) {
+ if (mValue == null) {
+ return ((LanguageQualifier)qualifier).mValue == null;
+ }
+ return mValue.equals(((LanguageQualifier)qualifier).mValue);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mValue != null) {
+ return mValue.hashCode();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ if (mValue != null) {
+ return getFolderSegment(mValue);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mValue != null) {
+ return mValue;
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (mValue != null) {
+ return String.format("Language %s", mValue);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java
new file mode 100644
index 0000000..f40bc6c
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java
@@ -0,0 +1,74 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.Navigation;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for Navigation Method.
+ */
+public final class NavigationMethodQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Navigation Method";
+
+ private Navigation mValue;
+
+ public NavigationMethodQualifier() {
+ // pass
+ }
+
+ public NavigationMethodQualifier(Navigation value) {
+ mValue = value;
+ }
+
+ public Navigation getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Navigation method = Navigation.getEnum(value);
+ if (method != null) {
+ NavigationMethodQualifier qualifier = new NavigationMethodQualifier(method);
+ config.setNavigationMethodQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java
new file mode 100644
index 0000000..91b81df
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java
@@ -0,0 +1,75 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.NavigationState;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for navigation state.
+ */
+public final class NavigationStateQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Navigation State";
+
+ private NavigationState mValue = null;
+
+ public NavigationStateQualifier() {
+ // pass
+ }
+
+ public NavigationStateQualifier(NavigationState value) {
+ mValue = value;
+ }
+
+ public NavigationState getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ NavigationState state = NavigationState.getEnum(value);
+ if (state != null) {
+ NavigationStateQualifier qualifier = new NavigationStateQualifier();
+ qualifier.mValue = state;
+ config.setNavigationStateQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
new file mode 100644
index 0000000..1ef2015
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2008 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.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Mobile Network Code Pixel Density.
+ */
+public final class NetworkCodeQualifier extends ResourceQualifier {
+ /** Default pixel density value. This means the property is not set. */
+ private final static int DEFAULT_CODE = -1;
+
+ private final static Pattern sNetworkCodePattern = Pattern.compile("^mnc(\\d{1,3})$"); //$NON-NLS-1$
+
+ private final int mCode;
+
+ public final static String NAME = "Mobile Network Code";
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link CountryCodeQualifier} object or <code>null</code>
+ */
+ public static NetworkCodeQualifier getQualifier(String segment) {
+ Matcher m = sNetworkCodePattern.matcher(segment);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ int code = -1;
+ try {
+ code = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ return null;
+ }
+
+ NetworkCodeQualifier qualifier = new NetworkCodeQualifier(code);
+ return qualifier;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link NetworkCodeQualifier} object.
+ * @param code the value of the qualifier, as returned by {@link #getCode()}.
+ */
+ public static String getFolderSegment(int code) {
+ if (code != DEFAULT_CODE && code >= 1 && code <= 999) { // code is 1-3 digit.
+ return String.format("mnc%1$d", code); //$NON-NLS-1$
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public NetworkCodeQualifier() {
+ this(DEFAULT_CODE);
+ }
+
+ public NetworkCodeQualifier(int code) {
+ mCode = code;
+ }
+
+ public int getCode() {
+ return mCode;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Network Code";
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mCode != DEFAULT_CODE;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Matcher m = sNetworkCodePattern.matcher(value);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ int code = -1;
+ try {
+ code = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ return false;
+ }
+
+ NetworkCodeQualifier qualifier = new NetworkCodeQualifier(code);
+ config.setNetworkCodeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof NetworkCodeQualifier) {
+ return mCode == ((NetworkCodeQualifier)qualifier).mCode;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCode;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return getFolderSegment(mCode);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mCode != DEFAULT_CODE) {
+ return String.format("MNC %1$d", mCode);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ return getShortDisplayValue();
+ }
+
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java
new file mode 100644
index 0000000..d3b6760
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java
@@ -0,0 +1,74 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.NightMode;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for Navigation Method.
+ */
+public final class NightModeQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Night Mode";
+
+ private NightMode mValue;
+
+ public NightModeQualifier() {
+ // pass
+ }
+
+ public NightModeQualifier(NightMode value) {
+ mValue = value;
+ }
+
+ public NightMode getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Night Mode";
+ }
+
+ @Override
+ public int since() {
+ return 8;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ NightMode mode = NightMode.getEnum(value);
+ if (mode != null) {
+ NightModeQualifier qualifier = new NightModeQualifier(mode);
+ config.setNightModeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java
new file mode 100644
index 0000000..bd033bd
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java
@@ -0,0 +1,166 @@
+/*
+ * 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.resources.configuration;
+
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Region.
+ */
+public final class RegionQualifier extends ResourceQualifier {
+ private final static Pattern sRegionPattern = Pattern.compile("^r([A-Z]{2})$"); //$NON-NLS-1$
+
+ public static final String FAKE_REGION_VALUE = "__"; //$NON-NLS-1$
+ public static final String NAME = "Region";
+
+ private String mValue;
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link RegionQualifier} object or <code>null</code>
+ */
+ public static RegionQualifier getQualifier(String segment) {
+ Matcher m = sRegionPattern.matcher(segment);
+ if (m.matches()) {
+ RegionQualifier qualifier = new RegionQualifier();
+ qualifier.mValue = m.group(1);
+
+ return qualifier;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link RegionQualifier} object.
+ * @param value the value of the qualifier, as returned by {@link #getValue()}.
+ */
+ public static String getFolderSegment(String value) {
+ if (value != null) {
+ // See http://developer.android.com/reference/java/util/Locale.html#default_locale
+ String segment = "r" + value.toUpperCase(Locale.US); //$NON-NLS-1$
+ if (sRegionPattern.matcher(segment).matches()) {
+ return segment;
+ }
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public RegionQualifier() {
+
+ }
+
+ public RegionQualifier(String value) {
+ mValue = value;
+ }
+
+ public String getValue() {
+ if (mValue != null) {
+ return mValue;
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue != null;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return FAKE_REGION_VALUE.equals(mValue);
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ RegionQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setRegionQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof RegionQualifier) {
+ if (mValue == null) {
+ return ((RegionQualifier)qualifier).mValue == null;
+ }
+ return mValue.equals(((RegionQualifier)qualifier).mValue);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mValue != null) {
+ return mValue.hashCode();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return getFolderSegment(mValue);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mValue != null) {
+ return mValue;
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (mValue != null) {
+ return String.format("Region %s", mValue);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java
new file mode 100644
index 0000000..2997c8f
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java
@@ -0,0 +1,134 @@
+/*
+ * 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.resources.configuration;
+
+
+/**
+ * Base class for resource qualifiers.
+ * <p/>The resource qualifier classes are designed as immutable.
+ */
+public abstract class ResourceQualifier implements Comparable<ResourceQualifier> {
+
+ /**
+ * Returns the human readable name of the qualifier.
+ */
+ public abstract String getName();
+
+ /**
+ * Returns a shorter human readable name for the qualifier.
+ * @see #getName()
+ */
+ public abstract String getShortName();
+
+ /**
+ * Returns when this qualifier was added to Android.
+ */
+ public abstract int since();
+
+ /**
+ * Whether this qualifier is deprecated.
+ */
+ public boolean deprecated() {
+ return false;
+ }
+
+ /**
+ * Returns whether the qualifier has a valid filter value.
+ */
+ public abstract boolean isValid();
+
+ /**
+ * Returns whether the qualifier has a fake value.
+ * <p/>Fake values are used internally and should not be used as real qualifier value.
+ */
+ public abstract boolean hasFakeValue();
+
+ /**
+ * Check if the value is valid for this qualifier, and if so sets the value
+ * into a Folder Configuration.
+ * @param value The value to check and set. Must not be null.
+ * @param config The folder configuration to receive the value. Must not be null.
+ * @return true if the value was valid and was set.
+ */
+ public abstract boolean checkAndSet(String value, FolderConfiguration config);
+
+ /**
+ * Returns a string formated to be used in a folder name.
+ * <p/>This is declared as abstract to force children classes to implement it.
+ */
+ public abstract String getFolderSegment();
+
+ /**
+ * Returns whether the given qualifier is a match for the receiver.
+ * <p/>The default implementation returns the result of {@link #equals(Object)}.
+ * <p/>Children class that re-implements this must implement
+ * {@link #isBetterMatchThan(ResourceQualifier, ResourceQualifier)} too.
+ * @param qualifier the reference qualifier
+ * @return true if the receiver is a match.
+ */
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ return equals(qualifier);
+ }
+
+ /**
+ * Returns true if the receiver is a better match for the given <var>reference</var> than
+ * the given <var>compareTo</var> comparable.
+ * @param compareTo The {@link ResourceQualifier} to compare to. Can be null, in which
+ * case the method must return <code>true</code>.
+ * @param reference The reference qualifier value for which the match is.
+ * @return true if the receiver is a better match.
+ */
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ // the default is to always return false. This gives less overhead than always returning
+ // true, as it would only compare same values anyway.
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getFolderSegment();
+ }
+
+ /**
+ * Returns a string formatted for display purpose.
+ */
+ public abstract String getShortDisplayValue();
+
+ /**
+ * Returns a string formatted for display purpose.
+ */
+ public abstract String getLongDisplayValue();
+
+ /**
+ * Returns <code>true</code> if both objects are equal.
+ * <p/>This is declared as abstract to force children classes to implement it.
+ */
+ @Override
+ public abstract boolean equals(Object object);
+
+ /**
+ * Returns a hash code value for the object.
+ * <p/>This is declared as abstract to force children classes to implement it.
+ */
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public final int compareTo(ResourceQualifier o) {
+ return toString().compareTo(o.toString());
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java
new file mode 100644
index 0000000..dce6c68
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java
@@ -0,0 +1,172 @@
+/*
+ * 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.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Screen Dimension.
+ */
+public final class ScreenDimensionQualifier extends ResourceQualifier {
+ /** Default screen size value. This means the property is not set */
+ final static int DEFAULT_SIZE = -1;
+
+ private final static Pattern sDimensionPattern = Pattern.compile(
+ "^(\\d+)x(\\d+)$"); //$NON-NLS-1$
+
+ public static final String NAME = "Screen Dimension";
+
+ /** Screen size 1 value. This is not size X or Y because the folder name always
+ * contains the biggest size first. So if the qualifier is 400x200, size 1 will always be
+ * 400 but that'll be X in landscape and Y in portrait.
+ * Default value is <code>DEFAULT_SIZE</code> */
+ private int mValue1 = DEFAULT_SIZE;
+
+ /** Screen size 2 value. This is not size X or Y because the folder name always
+ * contains the biggest size first. So if the qualifier is 400x200, size 2 will always be
+ * 200 but that'll be Y in landscape and X in portrait.
+ * Default value is <code>DEFAULT_SIZE</code> */
+ private int mValue2 = DEFAULT_SIZE;
+
+ public ScreenDimensionQualifier() {
+ // pass
+ }
+
+ public ScreenDimensionQualifier(int value1, int value2) {
+ mValue1 = value1;
+ mValue2 = value2;
+ }
+
+ public int getValue1() {
+ return mValue1;
+ }
+
+ public int getValue2() {
+ return mValue2;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Dimension";
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean deprecated() {
+ return true;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue1 != DEFAULT_SIZE && mValue2 != DEFAULT_SIZE;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Matcher m = sDimensionPattern.matcher(value);
+ if (m.matches()) {
+ String d1 = m.group(1);
+ String d2 = m.group(2);
+
+ ScreenDimensionQualifier qualifier = getQualifier(d1, d2);
+ if (qualifier != null) {
+ config.setScreenDimensionQualifier(qualifier);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof ScreenDimensionQualifier) {
+ ScreenDimensionQualifier q = (ScreenDimensionQualifier)qualifier;
+ return (mValue1 == q.mValue1 && mValue2 == q.mValue2);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ public static ScreenDimensionQualifier getQualifier(String size1, String size2) {
+ try {
+ int s1 = Integer.parseInt(size1);
+ int s2 = Integer.parseInt(size2);
+
+ ScreenDimensionQualifier qualifier = new ScreenDimensionQualifier();
+
+ if (s1 > s2) {
+ qualifier.mValue1 = s1;
+ qualifier.mValue2 = s2;
+ } else {
+ qualifier.mValue1 = s2;
+ qualifier.mValue2 = s1;
+ }
+
+ return qualifier;
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return String.format("%1$dx%2$d", mValue1, mValue2); //$NON-NLS-1$
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (isValid()) {
+ return String.format("%1$dx%2$d", mValue1, mValue2);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (isValid()) {
+ return String.format("Screen resolution %1$dx%2$d", mValue1, mValue2);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/ScreenHeightQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenHeightQualifier.java
new file mode 100644
index 0000000..08bba61
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenHeightQualifier.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2011 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.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Screen Pixel Density.
+ */
+public final class ScreenHeightQualifier extends ResourceQualifier {
+ /** Default screen size value. This means the property is not set */
+ final static int DEFAULT_SIZE = -1;
+
+ private final static Pattern sParsePattern = Pattern.compile("^h(\\d+)dp$");//$NON-NLS-1$
+ private final static String sPrintPattern = "h%1$ddp";
+
+ public static final String NAME = "Screen Height";
+
+ private int mValue = DEFAULT_SIZE;
+
+ public ScreenHeightQualifier() {
+ // pass
+ }
+
+ public ScreenHeightQualifier(int value) {
+ mValue = value;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 13;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue != DEFAULT_SIZE;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Matcher m = sParsePattern.matcher(value);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ ScreenHeightQualifier qualifier = getQualifier(v);
+ if (qualifier != null) {
+ config.setScreenHeightQualifier(qualifier);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static ScreenHeightQualifier getQualifier(String value) {
+ try {
+ int dp = Integer.parseInt(value);
+
+ ScreenHeightQualifier qualifier = new ScreenHeightQualifier();
+ qualifier.mValue = dp;
+ return qualifier;
+
+ } catch (NumberFormatException e) {
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ // this is the match only of the current dp value is lower or equal to the
+ if (qualifier instanceof ScreenHeightQualifier) {
+ return mValue <= ((ScreenHeightQualifier) qualifier).mValue;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ ScreenHeightQualifier compareQ = (ScreenHeightQualifier)compareTo;
+ ScreenHeightQualifier referenceQ = (ScreenHeightQualifier)reference;
+
+ if (compareQ.mValue == referenceQ.mValue) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mValue == referenceQ.mValue) {
+ // got new exact value, this is the best!
+ return true;
+ } else {
+ // get the qualifier that has the width that is the closest to the reference, but not
+ // above. (which is guaranteed when this is called as isMatchFor is called first.
+ return mValue > compareQ.mValue;
+ }
+ }
+
+ @Override
+ public String getFolderSegment() {
+ return String.format(sPrintPattern, mValue);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (isValid()) {
+ return getFolderSegment();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (isValid()) {
+ return getFolderSegment();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+
+ @Override
+ public int hashCode() {
+ return mValue;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ScreenHeightQualifier other = (ScreenHeightQualifier) obj;
+ if (mValue != other.mValue) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java
new file mode 100644
index 0000000..732a078
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java
@@ -0,0 +1,73 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.ScreenOrientation;
+
+/**
+ * Resource Qualifier for Screen Orientation.
+ */
+public final class ScreenOrientationQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Screen Orientation";
+
+ private ScreenOrientation mValue = null;
+
+ public ScreenOrientationQualifier() {
+ }
+
+ public ScreenOrientationQualifier(ScreenOrientation value) {
+ mValue = value;
+ }
+
+ public ScreenOrientation getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Orientation";
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ ScreenOrientation orientation = ScreenOrientation.getEnum(value);
+ if (orientation != null) {
+ ScreenOrientationQualifier qualifier = new ScreenOrientationQualifier(orientation);
+ config.setScreenOrientationQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java
new file mode 100644
index 0000000..b45946b
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2009 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.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.ScreenRatio;
+
+public class ScreenRatioQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Screen Ratio";
+
+ private ScreenRatio mValue = null;
+
+ public ScreenRatioQualifier() {
+ }
+
+ public ScreenRatioQualifier(ScreenRatio value) {
+ mValue = value;
+ }
+
+ public ScreenRatio getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Ratio";
+ }
+
+ @Override
+ public int since() {
+ return 4;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ ScreenRatio size = ScreenRatio.getEnum(value);
+ if (size != null) {
+ ScreenRatioQualifier qualifier = new ScreenRatioQualifier(size);
+ config.setScreenRatioQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
new file mode 100644
index 0000000..77193a2
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 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.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.ScreenSize;
+
+/**
+ * Resource Qualifier for Screen Size. Size can be "small", "normal", "large" and "x-large"
+ */
+public class ScreenSizeQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Screen Size";
+
+ private ScreenSize mValue = null;
+
+
+ public ScreenSizeQualifier() {
+ }
+
+ public ScreenSizeQualifier(ScreenSize value) {
+ mValue = value;
+ }
+
+ public ScreenSize getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Size";
+ }
+
+ @Override
+ public int since() {
+ return 4;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ ScreenSize size = ScreenSize.getEnum(value);
+ if (size != null) {
+ ScreenSizeQualifier qualifier = new ScreenSizeQualifier(size);
+ config.setScreenSizeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/ScreenWidthQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenWidthQualifier.java
new file mode 100644
index 0000000..ab9134b
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/ScreenWidthQualifier.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2011 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.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Screen Pixel Density.
+ */
+public final class ScreenWidthQualifier extends ResourceQualifier {
+ /** Default screen size value. This means the property is not set */
+ final static int DEFAULT_SIZE = -1;
+
+ private final static Pattern sParsePattern = Pattern.compile("^w(\\d+)dp$"); //$NON-NLS-1$
+ private final static String sPrintPattern = "w%1$ddp"; //$NON-NLS-1$
+
+ public static final String NAME = "Screen Width";
+
+ private int mValue = DEFAULT_SIZE;
+
+ public ScreenWidthQualifier() {
+ // pass
+ }
+
+ public ScreenWidthQualifier(int value) {
+ mValue = value;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 13;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue != DEFAULT_SIZE;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Matcher m = sParsePattern.matcher(value);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ ScreenWidthQualifier qualifier = getQualifier(v);
+ if (qualifier != null) {
+ config.setScreenWidthQualifier(qualifier);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static ScreenWidthQualifier getQualifier(String value) {
+ try {
+ int dp = Integer.parseInt(value);
+
+ ScreenWidthQualifier qualifier = new ScreenWidthQualifier();
+ qualifier.mValue = dp;
+ return qualifier;
+
+ } catch (NumberFormatException e) {
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ // this is the match only of the current dp value is lower or equal to the
+ if (qualifier instanceof ScreenWidthQualifier) {
+ return mValue <= ((ScreenWidthQualifier) qualifier).mValue;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ ScreenWidthQualifier compareQ = (ScreenWidthQualifier)compareTo;
+ ScreenWidthQualifier referenceQ = (ScreenWidthQualifier)reference;
+
+ if (compareQ.mValue == referenceQ.mValue) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mValue == referenceQ.mValue) {
+ // got new exact value, this is the best!
+ return true;
+ } else {
+ // get the qualifier that has the width that is the closest to the reference, but not
+ // above. (which is guaranteed when this is called as isMatchFor is called first.
+ return mValue > compareQ.mValue;
+ }
+ }
+
+ @Override
+ public String getFolderSegment() {
+ return String.format(sPrintPattern, mValue);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (isValid()) {
+ return getFolderSegment();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (isValid()) {
+ return getFolderSegment();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public int hashCode() {
+ return mValue;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ScreenWidthQualifier other = (ScreenWidthQualifier) obj;
+ if (mValue != other.mValue) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/SmallestScreenWidthQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/SmallestScreenWidthQualifier.java
new file mode 100644
index 0000000..35d1ab1
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/SmallestScreenWidthQualifier.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2011 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.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Screen Pixel Density.
+ */
+public final class SmallestScreenWidthQualifier extends ResourceQualifier {
+ /** Default screen size value. This means the property is not set */
+ final static int DEFAULT_SIZE = -1;
+
+ private final static Pattern sParsePattern = Pattern.compile("^sw(\\d+)dp$"); //$NON-NLS-1$
+ private final static String sPrintPattern = "sw%1$ddp"; //$NON-NLS-1$
+
+ public static final String NAME = "Smallest Screen Width";
+
+ private int mValue = DEFAULT_SIZE;
+
+ public SmallestScreenWidthQualifier() {
+ // pass
+ }
+
+ public SmallestScreenWidthQualifier(int value) {
+ mValue = value;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 13;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mValue != DEFAULT_SIZE;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Matcher m = sParsePattern.matcher(value);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ SmallestScreenWidthQualifier qualifier = getQualifier(v);
+ if (qualifier != null) {
+ config.setSmallestScreenWidthQualifier(qualifier);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static SmallestScreenWidthQualifier getQualifier(String value) {
+ try {
+ int dp = Integer.parseInt(value);
+
+ SmallestScreenWidthQualifier qualifier = new SmallestScreenWidthQualifier();
+ qualifier.mValue = dp;
+ return qualifier;
+
+ } catch (NumberFormatException e) {
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ // this is the match only of the current dp value is lower or equal to the
+ if (qualifier instanceof SmallestScreenWidthQualifier) {
+ return mValue <= ((SmallestScreenWidthQualifier) qualifier).mValue;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ SmallestScreenWidthQualifier compareQ = (SmallestScreenWidthQualifier)compareTo;
+ SmallestScreenWidthQualifier referenceQ = (SmallestScreenWidthQualifier)reference;
+
+ if (compareQ.mValue == referenceQ.mValue) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mValue == referenceQ.mValue) {
+ // got new exact value, this is the best!
+ return true;
+ } else {
+ // get the qualifier that has the width that is the closest to the reference, but not
+ // above. (which is guaranteed when this is called as isMatchFor is called first.
+ return mValue > compareQ.mValue;
+ }
+ }
+
+ @Override
+ public String getFolderSegment() {
+ return String.format(sPrintPattern, mValue);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (isValid()) {
+ return getFolderSegment();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (isValid()) {
+ return getFolderSegment();
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public int hashCode() {
+ return mValue;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SmallestScreenWidthQualifier other = (SmallestScreenWidthQualifier) obj;
+ if (mValue != other.mValue) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java
new file mode 100644
index 0000000..784d43d
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java
@@ -0,0 +1,76 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.Keyboard;
+import com.android.resources.ResourceEnum;
+
+/**
+ * Resource Qualifier for Text Input Method.
+ */
+public final class TextInputMethodQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Text Input Method";
+
+ private Keyboard mValue;
+
+
+ public TextInputMethodQualifier() {
+ // pass
+ }
+
+ public TextInputMethodQualifier(Keyboard value) {
+ mValue = value;
+ }
+
+ public Keyboard getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Text Input";
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ Keyboard method = Keyboard.getEnum(value);
+ if (method != null) {
+ TextInputMethodQualifier qualifier = new TextInputMethodQualifier();
+ qualifier.mValue = method;
+ config.setTextInputMethodQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java
new file mode 100644
index 0000000..dce9f1d
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java
@@ -0,0 +1,76 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.TouchScreen;
+
+
+/**
+ * Resource Qualifier for Touch Screen type.
+ */
+public final class TouchScreenQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "Touch Screen";
+
+ private TouchScreen mValue;
+
+ public TouchScreenQualifier() {
+ // pass
+ }
+
+ public TouchScreenQualifier(TouchScreen touchValue) {
+ mValue = touchValue;
+ }
+
+ public TouchScreen getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ TouchScreen type = TouchScreen.getEnum(value);
+ if (type != null) {
+ TouchScreenQualifier qualifier = new TouchScreenQualifier();
+ qualifier.mValue = type;
+ config.setTouchTypeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/UiModeQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/UiModeQualifier.java
new file mode 100644
index 0000000..1e302c5
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/UiModeQualifier.java
@@ -0,0 +1,108 @@
+/*
+ * 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.resources.configuration;
+
+import com.android.resources.ResourceEnum;
+import com.android.resources.UiMode;
+
+/**
+ * Resource Qualifier for UI Mode.
+ */
+public final class UiModeQualifier extends EnumBasedResourceQualifier {
+
+ public static final String NAME = "UI Mode";
+
+ private UiMode mValue;
+
+ public UiModeQualifier() {
+ // pass
+ }
+
+ public UiModeQualifier(UiMode value) {
+ mValue = value;
+ }
+
+ public UiMode getValue() {
+ return mValue;
+ }
+
+ @Override
+ ResourceEnum getEnumValue() {
+ return mValue;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return NAME;
+ }
+
+ @Override
+ public int since() {
+ return 8;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ UiMode mode = UiMode.getEnum(value);
+ if (mode != null) {
+ UiModeQualifier qualifier = new UiModeQualifier(mode);
+ config.setUiModeQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ // only normal is a match for all UI mode, because it's not an actual mode.
+ if (mValue == UiMode.NORMAL) {
+ return true;
+ }
+
+ // others must be an exact match
+ return ((UiModeQualifier)qualifier).mValue == mValue;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ UiModeQualifier compareQualifier = (UiModeQualifier)compareTo;
+ UiModeQualifier referenceQualifier = (UiModeQualifier)reference;
+
+ if (compareQualifier.getValue() == referenceQualifier.getValue()) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mValue == referenceQualifier.mValue) {
+ // got new exact value, this is the best!
+ return true;
+ } else if (mValue == UiMode.NORMAL) {
+ // else "normal" can be a match in case there's no exact match
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java b/sdk_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java
new file mode 100644
index 0000000..078d4af
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2009 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.resources.configuration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Resource Qualifier for Platform Version.
+ */
+public final class VersionQualifier extends ResourceQualifier {
+ /** Default pixel density value. This means the property is not set. */
+ private final static int DEFAULT_VERSION = -1;
+
+ private final static Pattern sVersionPattern = Pattern.compile("^v(\\d+)$");//$NON-NLS-1$
+
+ private int mVersion = DEFAULT_VERSION;
+
+ public static final String NAME = "Platform Version";
+
+ /**
+ * Creates and returns a qualifier from the given folder segment. If the segment is incorrect,
+ * <code>null</code> is returned.
+ * @param segment the folder segment from which to create a qualifier.
+ * @return a new {@link VersionQualifier} object or <code>null</code>
+ */
+ public static VersionQualifier getQualifier(String segment) {
+ Matcher m = sVersionPattern.matcher(segment);
+ if (m.matches()) {
+ String v = m.group(1);
+
+ int code = -1;
+ try {
+ code = Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ // looks like the string we extracted wasn't a valid number.
+ return null;
+ }
+
+ VersionQualifier qualifier = new VersionQualifier();
+ qualifier.mVersion = code;
+ return qualifier;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the folder name segment for the given value. This is equivalent to calling
+ * {@link #toString()} on a {@link VersionQualifier} object.
+ * @param version the value of the qualifier, as returned by {@link #getVersion()}.
+ */
+ public static String getFolderSegment(int version) {
+ if (version != DEFAULT_VERSION) {
+ return String.format("v%1$d", version); //$NON-NLS-1$
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ public VersionQualifier(int apiLevel) {
+ mVersion = apiLevel;
+ }
+
+ public VersionQualifier() {
+ //pass
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String getShortName() {
+ return "Version";
+ }
+
+ @Override
+ public int since() {
+ return 1;
+ }
+
+ @Override
+ public boolean isValid() {
+ return mVersion != DEFAULT_VERSION;
+ }
+
+ @Override
+ public boolean hasFakeValue() {
+ return false;
+ }
+
+ @Override
+ public boolean checkAndSet(String value, FolderConfiguration config) {
+ VersionQualifier qualifier = getQualifier(value);
+ if (qualifier != null) {
+ config.setVersionQualifier(qualifier);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object qualifier) {
+ if (qualifier instanceof VersionQualifier) {
+ return mVersion == ((VersionQualifier)qualifier).mVersion;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isMatchFor(ResourceQualifier qualifier) {
+ if (qualifier instanceof VersionQualifier) {
+ // it is considered a match if the api level is equal or lower to the given qualifier
+ return mVersion <= ((VersionQualifier) qualifier).mVersion;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isBetterMatchThan(ResourceQualifier compareTo, ResourceQualifier reference) {
+ if (compareTo == null) {
+ return true;
+ }
+
+ VersionQualifier compareQ = (VersionQualifier)compareTo;
+ VersionQualifier referenceQ = (VersionQualifier)reference;
+
+ if (compareQ.mVersion == referenceQ.mVersion) {
+ // what we have is already the best possible match (exact match)
+ return false;
+ } else if (mVersion == referenceQ.mVersion) {
+ // got new exact value, this is the best!
+ return true;
+ } else {
+ // in all case we're going to prefer the higher version (since they have been filtered
+ // to not be too high
+ return mVersion > compareQ.mVersion;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mVersion;
+ }
+
+ /**
+ * Returns the string used to represent this qualifier in the folder name.
+ */
+ @Override
+ public String getFolderSegment() {
+ return getFolderSegment(mVersion);
+ }
+
+ @Override
+ public String getShortDisplayValue() {
+ if (mVersion != DEFAULT_VERSION) {
+ return String.format("API %1$d", mVersion);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getLongDisplayValue() {
+ if (mVersion != DEFAULT_VERSION) {
+ return String.format("API Level %1$d", mVersion);
+ }
+
+ return ""; //$NON-NLS-1$
+ }
+}
diff --git a/sdk_common/src/com/android/ide/common/sdk/LoadStatus.java b/sdk_common/src/com/android/ide/common/sdk/LoadStatus.java
new file mode 100644
index 0000000..babbd63
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/sdk/LoadStatus.java
@@ -0,0 +1,24 @@
+/*
+ * 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.sdk;
+
+/**
+ * Enum for loading status of various SDK parts.
+ */
+public enum LoadStatus {
+ LOADING, LOADED, FAILED;
+}
diff --git a/sdk_common/src/com/android/ide/common/xml/AndroidManifestParser.java b/sdk_common/src/com/android/ide/common/xml/AndroidManifestParser.java
new file mode 100644
index 0000000..38dc1c4
--- /dev/null
+++ b/sdk_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/sdk_common/src/com/android/ide/common/xml/ManifestData.java b/sdk_common/src/com/android/ide/common/xml/ManifestData.java
new file mode 100644
index 0000000..9b68d60
--- /dev/null
+++ b/sdk_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);
+ }
+ }
+
+}