| /* |
| * Copyright (C) 2008 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.eclipse.adt.internal.sdk; |
| |
| import com.android.ddmlib.IDevice; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer; |
| import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor; |
| import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IProjectListener; |
| import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.LayoutBridge; |
| import com.android.prefs.AndroidLocation.AndroidLocationException; |
| import com.android.sdklib.AndroidVersion; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.sdklib.ISdkLog; |
| import com.android.sdklib.SdkConstants; |
| import com.android.sdklib.SdkManager; |
| import com.android.sdklib.internal.avd.AvdManager; |
| import com.android.sdklib.internal.project.ApkConfigurationHelper; |
| import com.android.sdklib.internal.project.ApkSettings; |
| import com.android.sdklib.internal.project.ProjectProperties; |
| import com.android.sdklib.internal.project.ProjectProperties.PropertyType; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used |
| * at the same time. |
| * |
| * To start using an SDK, call {@link #loadSdk(String)} which returns the instance of |
| * the Sdk object. |
| * |
| * To get the list of platforms or add-ons present in the SDK, call {@link #getTargets()}. |
| */ |
| public class Sdk implements IProjectListener { |
| private static Sdk sCurrentSdk = null; |
| |
| private final SdkManager mManager; |
| private final AvdManager mAvdManager; |
| |
| private final HashMap<IProject, IAndroidTarget> mProjectTargetMap = |
| new HashMap<IProject, IAndroidTarget>(); |
| private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap = |
| new HashMap<IAndroidTarget, AndroidTargetData>(); |
| private final HashMap<IProject, ApkSettings> mApkSettingsMap = |
| new HashMap<IProject, ApkSettings>(); |
| private final String mDocBaseUrl; |
| |
| private final LayoutDeviceManager mLayoutDeviceManager = new LayoutDeviceManager(); |
| |
| /** |
| * Classes implementing this interface will receive notification when targets are changed. |
| */ |
| public interface ITargetChangeListener { |
| /** |
| * Sent when project has its target changed. |
| */ |
| void onProjectTargetChange(IProject changedProject); |
| |
| /** |
| * Called when the targets are loaded (either the SDK finished loading when Eclipse starts, |
| * or the SDK is changed). |
| */ |
| void onTargetsLoaded(); |
| } |
| |
| /** |
| * Loads an SDK and returns an {@link Sdk} object if success. |
| * @param sdkLocation the OS path to the SDK. |
| */ |
| public static Sdk loadSdk(String sdkLocation) { |
| if (sCurrentSdk != null) { |
| sCurrentSdk.dispose(); |
| sCurrentSdk = null; |
| } |
| |
| final ArrayList<String> logMessages = new ArrayList<String>(); |
| ISdkLog log = new ISdkLog() { |
| public void error(Throwable throwable, String errorFormat, Object... arg) { |
| if (errorFormat != null) { |
| logMessages.add(String.format("Error: " + errorFormat, arg)); |
| } |
| |
| if (throwable != null) { |
| logMessages.add(throwable.getMessage()); |
| } |
| } |
| |
| public void warning(String warningFormat, Object... arg) { |
| logMessages.add(String.format("Warning: " + warningFormat, arg)); |
| } |
| |
| public void printf(String msgFormat, Object... arg) { |
| logMessages.add(String.format(msgFormat, arg)); |
| } |
| }; |
| |
| // get an SdkManager object for the location |
| SdkManager manager = SdkManager.createManager(sdkLocation, log); |
| if (manager != null) { |
| AvdManager avdManager = null; |
| try { |
| avdManager = new AvdManager(manager, log); |
| } catch (AndroidLocationException e) { |
| log.error(e, "Error parsing the AVDs"); |
| } |
| sCurrentSdk = new Sdk(manager, avdManager); |
| return sCurrentSdk; |
| } else { |
| StringBuilder sb = new StringBuilder("Error Loading the SDK:\n"); |
| for (String msg : logMessages) { |
| sb.append('\n'); |
| sb.append(msg); |
| } |
| AdtPlugin.displayError("Android SDK", sb.toString()); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the current {@link Sdk} object. |
| */ |
| public static Sdk getCurrent() { |
| return sCurrentSdk; |
| } |
| |
| /** |
| * Returns the location (OS path) of the current SDK. |
| */ |
| public String getSdkLocation() { |
| return mManager.getLocation(); |
| } |
| |
| /** |
| * Returns the URL to the local documentation. |
| * Can return null if no documentation is found in the current SDK. |
| * |
| * @return A file:// URL on the local documentation folder if it exists or null. |
| */ |
| public String getDocumentationBaseUrl() { |
| return mDocBaseUrl; |
| } |
| |
| /** |
| * Returns the list of targets that are available in the SDK. |
| */ |
| public IAndroidTarget[] getTargets() { |
| return mManager.getTargets(); |
| } |
| |
| /** |
| * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}. |
| * |
| * @param hash the {@link IAndroidTarget} hash string. |
| * @return The matching {@link IAndroidTarget} or null. |
| */ |
| public IAndroidTarget getTargetFromHashString(String hash) { |
| return mManager.getTargetFromHashString(hash); |
| } |
| |
| /** |
| * Sets a new target and a new list of Apk configuration for a given project. |
| * |
| * @param project the project to receive the new apk configurations |
| * @param target The new target to set, or <code>null</code> to not change the current target. |
| * @param apkConfigMap a map of apk configurations. The map contains (name, filter) where name |
| * is the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of |
| * resource configuration to include in the apk (see aapt -c). Can be <code>null</code> if the |
| * apk configurations should not be updated. |
| */ |
| public void setProject(IProject project, IAndroidTarget target, |
| ApkSettings settings) { |
| synchronized (AdtPlugin.getDefault().getSdkLockObject()) { |
| boolean resolveProject = false; |
| |
| ProjectProperties properties = ProjectProperties.load( |
| project.getLocation().toOSString(), PropertyType.DEFAULT); |
| if (properties == null) { |
| // doesn't exist yet? we create it. |
| properties = ProjectProperties.create(project.getLocation().toOSString(), |
| PropertyType.DEFAULT); |
| } |
| |
| if (target != null) { |
| // look for the current target of the project |
| IAndroidTarget previousTarget = mProjectTargetMap.get(project); |
| |
| if (target != previousTarget) { |
| // save the target hash string in the project persistent property |
| properties.setAndroidTarget(target); |
| |
| // put it in a local map for easy access. |
| mProjectTargetMap.put(project, target); |
| |
| resolveProject = true; |
| } |
| } |
| |
| // if there's no settings, force default values (to reset possibly changed |
| // values in a previous call. |
| if (settings == null) { |
| settings = new ApkSettings(); |
| } |
| |
| // save the project settings into the project persistent property |
| ApkConfigurationHelper.setProperties(properties, settings); |
| |
| // put it in a local map for easy access. |
| mApkSettingsMap.put(project, settings); |
| |
| // we are done with the modification. Save the property file. |
| try { |
| properties.save(); |
| } catch (IOException e) { |
| AdtPlugin.log(e, "Failed to save default.properties for project '%s'", |
| project.getName()); |
| } |
| |
| if (resolveProject) { |
| // force a resolve of the project by updating the classpath container. |
| // This will also force a recompile. |
| IJavaProject javaProject = JavaCore.create(project); |
| AndroidClasspathContainerInitializer.updateProjects( |
| new IJavaProject[] { javaProject }); |
| } else { |
| // always do a full clean/build. |
| try { |
| project.build(IncrementalProjectBuilder.CLEAN_BUILD, null); |
| } catch (CoreException e) { |
| // failed to build? force resolve instead. |
| IJavaProject javaProject = JavaCore.create(project); |
| AndroidClasspathContainerInitializer.updateProjects( |
| new IJavaProject[] { javaProject }); |
| } |
| } |
| |
| // finally, update the opened editors. |
| if (resolveProject) { |
| AdtPlugin.getDefault().updateTargetListener(project); |
| } |
| } |
| } |
| |
| /** |
| * Returns the {@link IAndroidTarget} object associated with the given {@link IProject}. |
| */ |
| public IAndroidTarget getTarget(IProject project) { |
| synchronized (AdtPlugin.getDefault().getSdkLockObject()) { |
| IAndroidTarget target = mProjectTargetMap.get(project); |
| if (target == null) { |
| // get the value from the project persistent property. |
| String targetHashString = loadProjectProperties(project, this); |
| |
| if (targetHashString != null) { |
| target = mManager.getTargetFromHashString(targetHashString); |
| } |
| } |
| |
| return target; |
| } |
| } |
| |
| |
| /** |
| * Parses the project properties and returns the hash string uniquely identifying the |
| * target of the given project. |
| * <p/> |
| * This methods reads the content of the <code>default.properties</code> file present in |
| * the root folder of the project. |
| * <p/>The returned string is equivalent to the return of {@link IAndroidTarget#hashString()}. |
| * @param project The project for which to return the target hash string. |
| * @param sdkStorage The sdk in which to store the Apk Configs. Can be null. |
| * @return the hash string or null if the project does not have a target set. |
| */ |
| private static String loadProjectProperties(IProject project, Sdk sdkStorage) { |
| // load the default.properties from the project folder. |
| IPath location = project.getLocation(); |
| if (location == null) { // can return null when the project is being deleted. |
| // do nothing and return null; |
| return null; |
| } |
| ProjectProperties properties = ProjectProperties.load(location.toOSString(), |
| PropertyType.DEFAULT); |
| if (properties == null) { |
| AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'", |
| project.getName()); |
| return null; |
| } |
| |
| if (sdkStorage != null) { |
| synchronized (AdtPlugin.getDefault().getSdkLockObject()) { |
| ApkSettings settings = ApkConfigurationHelper.getSettings(properties); |
| |
| if (settings != null) { |
| sdkStorage.mApkSettingsMap.put(project, settings); |
| } |
| } |
| } |
| |
| return properties.getProperty(ProjectProperties.PROPERTY_TARGET); |
| } |
| |
| /** |
| * Returns the hash string uniquely identifying the target of a project. |
| * <p/> |
| * This methods reads the content of the <code>default.properties</code> file present in |
| * the root folder of the project. |
| * <p/>The string is equivalent to the return of {@link IAndroidTarget#hashString()}. |
| * @param project The project for which to return the target hash string. |
| * @return the hash string or null if the project does not have a target set. |
| */ |
| public static String getProjectTargetHashString(IProject project) { |
| return loadProjectProperties(project, null /*storeConfigs*/); |
| } |
| |
| /** |
| * Sets a target hash string in given project's <code>default.properties</code> file. |
| * @param project The project in which to save the hash string. |
| * @param targetHashString The target hash string to save. This must be the result from |
| * {@link IAndroidTarget#hashString()}. |
| */ |
| public static void setProjectTargetHashString(IProject project, String targetHashString) { |
| // because we don't want to erase other properties from default.properties, we first load |
| // them |
| ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString(), |
| PropertyType.DEFAULT); |
| if (properties == null) { |
| // doesn't exist yet? we create it. |
| properties = ProjectProperties.create(project.getLocation().toOSString(), |
| PropertyType.DEFAULT); |
| } |
| |
| // add/change the target hash string. |
| properties.setProperty(ProjectProperties.PROPERTY_TARGET, targetHashString); |
| |
| // and rewrite the file. |
| try { |
| properties.save(); |
| } catch (IOException e) { |
| AdtPlugin.log(e, "Failed to save default.properties for project '%s'", |
| project.getName()); |
| } |
| } |
| /** |
| * Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}. |
| */ |
| public AndroidTargetData getTargetData(IAndroidTarget target) { |
| synchronized (AdtPlugin.getDefault().getSdkLockObject()) { |
| return mTargetDataMap.get(target); |
| } |
| } |
| |
| /** |
| * Return the {@link AndroidTargetData} for a given {@link IProject}. |
| */ |
| public AndroidTargetData getTargetData(IProject project) { |
| synchronized (AdtPlugin.getDefault().getSdkLockObject()) { |
| IAndroidTarget target = getTarget(project); |
| if (target != null) { |
| return getTargetData(target); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the APK settings for a given project. |
| */ |
| public ApkSettings getApkSettings(IProject project) { |
| synchronized (AdtPlugin.getDefault().getSdkLockObject()) { |
| return mApkSettingsMap.get(project); |
| } |
| } |
| |
| /** |
| * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could |
| * be <code>null</code>. |
| */ |
| public AvdManager getAvdManager() { |
| return mAvdManager; |
| } |
| |
| public static AndroidVersion getDeviceVersion(IDevice device) { |
| try { |
| Map<String, String> props = device.getProperties(); |
| String apiLevel = props.get(IDevice.PROP_BUILD_API_LEVEL); |
| if (apiLevel == null) { |
| return null; |
| } |
| |
| return new AndroidVersion(Integer.parseInt(apiLevel), |
| props.get((IDevice.PROP_BUILD_CODENAME))); |
| } catch (NumberFormatException e) { |
| return null; |
| } |
| } |
| |
| public LayoutDeviceManager getLayoutDeviceManager() { |
| return mLayoutDeviceManager; |
| } |
| |
| private Sdk(SdkManager manager, AvdManager avdManager) { |
| mManager = manager; |
| mAvdManager = avdManager; |
| |
| // listen to projects closing |
| ResourceMonitor monitor = ResourceMonitor.getMonitor(); |
| monitor.addProjectListener(this); |
| |
| // pre-compute some paths |
| mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() + |
| SdkConstants.OS_SDK_DOCS_FOLDER); |
| |
| // load the built-in and user layout devices |
| mLayoutDeviceManager.load(mManager.getLocation()); |
| // and the ones from the add-on |
| loadLayoutDevices(); |
| } |
| |
| /** |
| * Cleans and unloads the SDK. |
| */ |
| private void dispose() { |
| ResourceMonitor.getMonitor().removeProjectListener(this); |
| } |
| |
| void setTargetData(IAndroidTarget target, AndroidTargetData data) { |
| synchronized (AdtPlugin.getDefault().getSdkLockObject()) { |
| mTargetDataMap.put(target, data); |
| } |
| } |
| |
| /** |
| * Returns the URL to the local documentation. |
| * Can return null if no documentation is found in the current SDK. |
| * |
| * @param osDocsPath Path to the documentation folder in the current SDK. |
| * The folder may not actually exist. |
| * @return A file:// URL on the local documentation folder if it exists or null. |
| */ |
| private String getDocumentationBaseUrl(String osDocsPath) { |
| File f = new File(osDocsPath); |
| |
| if (f.isDirectory()) { |
| try { |
| // Note: to create a file:// URL, one would typically use something like |
| // f.toURI().toURL().toString(). However this generates a broken path on |
| // Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of |
| // "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll |
| // do the correct thing manually. |
| |
| String path = f.getAbsolutePath(); |
| if (File.separatorChar != '/') { |
| path = path.replace(File.separatorChar, '/'); |
| } |
| |
| // For some reason the URL class doesn't add the mandatory "//" after |
| // the "file:" protocol name, so it has to be hacked into the path. |
| URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$ |
| String result = url.toString(); |
| return result; |
| } catch (MalformedURLException e) { |
| // ignore malformed URLs |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Parses the SDK add-ons to look for files called {@link SdkConstants#FN_DEVICES_XML} to |
| * load {@link LayoutDevice} from them. |
| */ |
| private void loadLayoutDevices() { |
| IAndroidTarget[] targets = mManager.getTargets(); |
| for (IAndroidTarget target : targets) { |
| if (target.isPlatform() == false) { |
| File deviceXml = new File(target.getLocation(), SdkConstants.FN_DEVICES_XML); |
| if (deviceXml.isFile()) { |
| mLayoutDeviceManager.parseAddOnLayoutDevice(deviceXml); |
| } |
| } |
| } |
| |
| mLayoutDeviceManager.sealAddonLayoutDevices(); |
| } |
| |
| public void projectClosed(IProject project) { |
| // get the target project |
| synchronized (AdtPlugin.getDefault().getSdkLockObject()) { |
| IAndroidTarget target = mProjectTargetMap.get(project); |
| if (target != null) { |
| // get the bridge for the target, and clear the cache for this project. |
| AndroidTargetData data = mTargetDataMap.get(target); |
| if (data != null) { |
| LayoutBridge bridge = data.getLayoutBridge(); |
| if (bridge != null && bridge.status == LoadStatus.LOADED) { |
| bridge.bridge.clearCaches(project); |
| } |
| } |
| } |
| |
| // now remove the project for the maps. |
| mProjectTargetMap.remove(project); |
| mApkSettingsMap.remove(project); |
| } |
| } |
| |
| public void projectDeleted(IProject project) { |
| projectClosed(project); |
| } |
| |
| public void projectOpened(IProject project) { |
| // ignore this. The project will be added to the map the first time the target needs |
| // to be resolved. |
| } |
| |
| public void projectOpenedWithWorkspace(IProject project) { |
| // ignore this. The project will be added to the map the first time the target needs |
| // to be resolved. |
| } |
| } |
| |