2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.sdklib;
19 import com.android.prefs.AndroidLocation;
20 import com.android.prefs.AndroidLocation.AndroidLocationException;
21 import com.android.sdklib.AndroidVersion.AndroidVersionException;
22 import com.android.sdklib.internal.project.ProjectProperties;
25 import java.io.FileInputStream;
26 import java.io.FileNotFoundException;
27 import java.io.FileWriter;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
34 import java.util.Properties;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
39 * The SDK manager parses the SDK folder and gives access to the content.
43 public final class SdkManager {
45 public final static String PROP_VERSION_SDK = "ro.build.version.sdk";
46 public final static String PROP_VERSION_CODENAME = "ro.build.version.codename";
47 public final static String PROP_VERSION_RELEASE = "ro.build.version.release";
49 private final static String ADDON_NAME = "name";
50 private final static String ADDON_VENDOR = "vendor";
51 private final static String ADDON_API = "api";
52 private final static String ADDON_DESCRIPTION = "description";
53 private final static String ADDON_LIBRARIES = "libraries";
54 private final static String ADDON_DEFAULT_SKIN = "skin";
55 private final static String ADDON_USB_VENDOR = "usb-vendor";
56 private final static String ADDON_REVISION = "revision";
57 private final static String ADDON_REVISION_OLD = "version";
60 private final static Pattern PATTERN_LIB_DATA = Pattern.compile(
61 "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE);
63 // usb ids are 16-bit hexadecimal values.
64 private final static Pattern PATTERN_USB_IDS = Pattern.compile(
65 "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE);
67 /** List of items in the platform to check when parsing it. These paths are relative to the
68 * platform root folder. */
69 private final static String[] sPlatformContentList = new String[] {
70 SdkConstants.FN_FRAMEWORK_LIBRARY,
71 SdkConstants.FN_FRAMEWORK_AIDL,
72 SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AAPT,
73 SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AIDL,
74 SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_DX,
75 SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + SdkConstants.FN_DX_JAR,
78 /** Preference file containing the usb ids for adb */
79 private final static String ADB_INI_FILE = "adb_usb.ini";
80 //0--------90--------90--------90--------90--------90--------90--------90--------9
81 private final static String ADB_INI_HEADER =
82 "# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.\n" +
83 "# USE 'android update adb' TO GENERATE.\n" +
84 "# 1 USB VENDOR ID PER LINE.\n";
86 /** the location of the SDK */
87 private final String mSdkLocation;
88 private IAndroidTarget[] mTargets;
91 * Create a new {@link SdkManager} instance.
92 * External users should use {@link #createManager(String, ISdkLog)}.
94 * @param sdkLocation the location of the SDK.
96 private SdkManager(String sdkLocation) {
97 mSdkLocation = sdkLocation;
101 * Creates an {@link SdkManager} for a given sdk location.
102 * @param sdkLocation the location of the SDK.
103 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
104 * @return the created {@link SdkManager} or null if the location is not valid.
106 public static SdkManager createManager(String sdkLocation, ISdkLog log) {
108 SdkManager manager = new SdkManager(sdkLocation);
109 ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
110 loadPlatforms(sdkLocation, list, log);
111 loadAddOns(sdkLocation, list, log);
113 // sort the targets/add-ons
114 Collections.sort(list);
116 manager.setTargets(list.toArray(new IAndroidTarget[list.size()]));
118 // load the samples, after the targets have been set.
119 manager.loadSamples(log);
122 } catch (IllegalArgumentException e) {
123 log.error(e, "Error parsing the sdk.");
130 * Returns the location of the SDK.
132 public String getLocation() {
137 * Returns the targets that are available in the SDK.
139 * The array can be empty but not null.
141 public IAndroidTarget[] getTargets() {
146 * Sets the targets that are available in the SDK.
148 * The array can be empty but not null.
150 private void setTargets(IAndroidTarget[] targets) {
151 assert targets != null;
156 * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
158 * @param hash the {@link IAndroidTarget} hash string.
159 * @return The matching {@link IAndroidTarget} or null.
161 public IAndroidTarget getTargetFromHashString(String hash) {
163 for (IAndroidTarget target : mTargets) {
164 if (hash.equals(target.hashString())) {
174 * Updates adb with the USB devices declared in the SDK add-ons.
175 * @throws AndroidLocationException
176 * @throws IOException
178 public void updateAdb() throws AndroidLocationException, IOException {
179 FileWriter writer = null;
181 // get the android prefs location to know where to write the file.
182 File adbIni = new File(AndroidLocation.getFolder(), ADB_INI_FILE);
183 writer = new FileWriter(adbIni);
185 // first, put all the vendor id in an HashSet to remove duplicate.
186 HashSet<Integer> set = new HashSet<Integer>();
187 IAndroidTarget[] targets = getTargets();
188 for (IAndroidTarget target : targets) {
189 if (target.getUsbVendorId() != IAndroidTarget.NO_USB_ID) {
190 set.add(target.getUsbVendorId());
194 // write file header.
195 writer.write(ADB_INI_HEADER);
197 // now write the Id in a text file, one per line.
198 for (Integer i : set) {
199 writer.write(String.format("0x%04x\n", i));
202 if (writer != null) {
209 * Reloads the content of the SDK.
211 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
213 public void reloadSdk(ISdkLog log) {
214 // get the current target list.
215 ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
216 loadPlatforms(mSdkLocation, list, log);
217 loadAddOns(mSdkLocation, list, log);
219 // For now replace the old list with the new one.
220 // In the future we may want to keep the current objects, so that ADT doesn't have to deal
221 // with new IAndroidTarget objects when a target didn't actually change.
223 // sort the targets/add-ons
224 Collections.sort(list);
225 setTargets(list.toArray(new IAndroidTarget[list.size()]));
227 // load the samples, after the targets have been set.
232 * Loads the Platforms from the SDK.
233 * @param location Location of the SDK
234 * @param list the list to fill with the platforms.
235 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
237 private static void loadPlatforms(String location, ArrayList<IAndroidTarget> list,
239 File platformFolder = new File(location, SdkConstants.FD_PLATFORMS);
240 if (platformFolder.isDirectory()) {
241 File[] platforms = platformFolder.listFiles();
243 for (File platform : platforms) {
244 if (platform.isDirectory()) {
245 PlatformTarget target = loadPlatform(platform, log);
246 if (target != null) {
250 log.warning("Ignoring platform '%1$s', not a folder.", platform.getName());
257 String message = null;
258 if (platformFolder.exists() == false) {
259 message = "%s is missing.";
261 message = "%s is not a folder.";
264 throw new IllegalArgumentException(String.format(message,
265 platformFolder.getAbsolutePath()));
269 * Loads a specific Platform at a given location.
270 * @param platform the location of the platform.
271 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
273 private static PlatformTarget loadPlatform(File platform, ISdkLog log) {
274 File buildProp = new File(platform, SdkConstants.FN_BUILD_PROP);
276 if (buildProp.isFile()) {
277 Map<String, String> map = ProjectProperties.parsePropertyFile(buildProp, log);
280 // look for some specific values in the map.
283 String apiName = map.get(PROP_VERSION_RELEASE);
284 if (apiName == null) {
286 "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
287 platform.getName(), PROP_VERSION_RELEASE,
288 SdkConstants.FN_BUILD_PROP);
294 String stringValue = map.get(PROP_VERSION_SDK);
295 if (stringValue == null) {
297 "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
298 platform.getName(), PROP_VERSION_SDK,
299 SdkConstants.FN_BUILD_PROP);
303 apiNumber = Integer.parseInt(stringValue);
304 } catch (NumberFormatException e) {
305 // looks like apiNumber does not parse to a number.
306 // Ignore this platform.
308 "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
309 platform.getName(), PROP_VERSION_SDK,
310 SdkConstants.FN_BUILD_PROP);
315 // codename (optional)
316 String apiCodename = map.get(PROP_VERSION_CODENAME);
317 if (apiCodename != null && apiCodename.equals("REL")) {
318 apiCodename = null; // REL means it's a release version and therefore the
319 // codename is irrelevant at this point.
322 // platform rev number
324 File sourcePropFile = new File(platform, SdkConstants.FN_SOURCE_PROP);
325 Map<String, String> sourceProp = ProjectProperties.parsePropertyFile(sourcePropFile,
327 if (sourceProp != null) {
329 revision = Integer.parseInt(sourceProp.get("Pkg.Revision"));
330 } catch (NumberFormatException e) {
331 // do nothing, we'll keep the default value of 1.
333 map.putAll(sourceProp);
337 File sdkPropFile = new File(platform, SdkConstants.FN_SDK_PROP);
338 Map<String, String> antProp = ProjectProperties.parsePropertyFile(sdkPropFile, log);
339 if (antProp != null) {
343 // api number and name look valid, perform a few more checks
344 if (checkPlatformContent(platform, log) == false) {
347 // create the target.
348 PlatformTarget target = new PlatformTarget(
349 platform.getAbsolutePath(),
356 // need to parse the skins.
357 String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
358 target.setSkins(skins);
363 log.warning(null, "Ignoring platform '%1$s': %2$s is missing.", platform.getName(),
364 SdkConstants.FN_BUILD_PROP);
372 * Loads the Add-on from the SDK.
373 * @param location Location of the SDK
374 * @param list the list to fill with the add-ons.
375 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
377 private static void loadAddOns(String location, ArrayList<IAndroidTarget> list, ISdkLog log) {
378 File addonFolder = new File(location, SdkConstants.FD_ADDONS);
379 if (addonFolder.isDirectory()) {
380 File[] addons = addonFolder.listFiles();
382 for (File addon : addons) {
383 // Add-ons have to be folders. Ignore files and no need to warn about them.
384 if (addon.isDirectory()) {
385 AddOnTarget target = loadAddon(addon, list, log);
386 if (target != null) {
395 String message = null;
396 if (addonFolder.exists() == false) {
397 message = "%s is missing.";
399 message = "%s is not a folder.";
402 throw new IllegalArgumentException(String.format(message,
403 addonFolder.getAbsolutePath()));
407 * Loads a specific Add-on at a given location.
408 * @param addon the location of the addon.
409 * @param targetList The list of Android target that were already loaded from the SDK.
410 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
412 private static AddOnTarget loadAddon(File addon, ArrayList<IAndroidTarget> targetList,
414 File addOnManifest = new File(addon, SdkConstants.FN_MANIFEST_INI);
416 if (addOnManifest.isFile()) {
417 Map<String, String> propertyMap = ProjectProperties.parsePropertyFile(addOnManifest,
420 if (propertyMap != null) {
421 // look for some specific values in the map.
422 // we require name, vendor, and api
423 String name = propertyMap.get(ADDON_NAME);
425 displayAddonManifestWarning(log, addon.getName(), ADDON_NAME);
429 String vendor = propertyMap.get(ADDON_VENDOR);
430 if (vendor == null) {
431 displayAddonManifestWarning(log, addon.getName(), ADDON_VENDOR);
435 String api = propertyMap.get(ADDON_API);
436 PlatformTarget baseTarget = null;
438 displayAddonManifestWarning(log, addon.getName(), ADDON_API);
441 // Look for a platform that has a matching api level or codename.
442 for (IAndroidTarget target : targetList) {
443 if (target.isPlatform() && target.getVersion().equals(api)) {
444 baseTarget = (PlatformTarget)target;
449 if (baseTarget == null) {
450 // Ignore this add-on.
452 "Ignoring add-on '%1$s': Unable to find base platform with API level '%2$s'",
453 addon.getName(), api);
458 // get the optional description
459 String description = propertyMap.get(ADDON_DESCRIPTION);
461 // get the add-on revision
462 int revisionValue = 1;
463 String revision = propertyMap.get(ADDON_REVISION);
464 if (revision == null) {
465 revision = propertyMap.get(ADDON_REVISION_OLD);
467 if (revision != null) {
469 revisionValue = Integer.parseInt(revision);
470 } catch (NumberFormatException e) {
471 // looks like apiNumber does not parse to a number.
472 // Ignore this add-on.
474 "Ignoring add-on '%1$s': %2$s is not a valid number in %3$s.",
475 addon.getName(), ADDON_REVISION, SdkConstants.FN_BUILD_PROP);
480 // get the optional libraries
481 String librariesValue = propertyMap.get(ADDON_LIBRARIES);
482 Map<String, String[]> libMap = null;
484 if (librariesValue != null) {
485 librariesValue = librariesValue.trim();
486 if (librariesValue.length() > 0) {
487 // split in the string into the libraries name
488 String[] libraries = librariesValue.split(";");
489 if (libraries.length > 0) {
490 libMap = new HashMap<String, String[]>();
491 for (String libName : libraries) {
492 libName = libName.trim();
494 // get the library data from the properties
495 String libData = propertyMap.get(libName);
497 if (libData != null) {
498 // split the jar file from the description
499 Matcher m = PATTERN_LIB_DATA.matcher(libData);
501 libMap.put(libName, new String[] {
502 m.group(1), m.group(2) });
505 "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
510 "Ignoring library '%1$s', missing property value",
518 AddOnTarget target = new AddOnTarget(addon.getAbsolutePath(), name, vendor,
519 revisionValue, description, libMap, baseTarget);
521 // need to parse the skins.
522 String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
524 // get the default skin, or take it from the base platform if needed.
525 String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN);
526 if (defaultSkin == null) {
527 if (skins.length == 1) {
528 defaultSkin = skins[0];
530 defaultSkin = baseTarget.getDefaultSkin();
534 // get the USB ID (if available)
535 int usbVendorId = convertId(propertyMap.get(ADDON_USB_VENDOR));
536 if (usbVendorId != IAndroidTarget.NO_USB_ID) {
537 target.setUsbVendorId(usbVendorId);
540 target.setSkins(skins, defaultSkin);
545 log.warning(null, "Ignoring add-on '%1$s': %2$s is missing.", addon.getName(),
546 SdkConstants.FN_MANIFEST_INI);
553 * Converts a string representation of an hexadecimal ID into an int.
554 * @param value the string to convert.
555 * @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the convertion failed.
557 private static int convertId(String value) {
558 if (value != null && value.length() > 0) {
559 if (PATTERN_USB_IDS.matcher(value).matches()) {
560 String v = value.substring(2);
562 return Integer.parseInt(v, 16);
563 } catch (NumberFormatException e) {
564 // this shouldn't happen since we check the pattern above, but this is safer.
565 // the method will return 0 below.
570 return IAndroidTarget.NO_USB_ID;
574 * Displays a warning in the log about the addon being ignored due to a missing manifest value.
576 * @param log The logger object. Cannot be null.
577 * @param addonName The addon name, for display.
578 * @param valueName The missing manifest value, for display.
580 private static void displayAddonManifestWarning(ISdkLog log, String addonName, String valueName) {
581 log.warning(null, "Ignoring add-on '%1$s': '%2$s' is missing from %3$s.",
582 addonName, valueName, SdkConstants.FN_MANIFEST_INI);
586 * Checks the given platform has all the required files, and returns true if they are all
588 * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
589 * aidl(.exe), dx(.bat), and dx.jar
591 * @param platform The folder containing the platform.
592 * @param log Logger. Cannot be null.
594 private static boolean checkPlatformContent(File platform, ISdkLog log) {
595 for (String relativePath : sPlatformContentList) {
596 File f = new File(platform, relativePath);
599 "Ignoring platform '%1$s': %2$s is missing.",
600 platform.getName(), relativePath);
610 * Parses the skin folder and builds the skin list.
611 * @param osPath The path of the skin root folder.
613 private static String[] parseSkinFolder(String osPath) {
614 File skinRootFolder = new File(osPath);
616 if (skinRootFolder.isDirectory()) {
617 ArrayList<String> skinList = new ArrayList<String>();
619 File[] files = skinRootFolder.listFiles();
621 for (File skinFolder : files) {
622 if (skinFolder.isDirectory()) {
623 // check for layout file
624 File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
626 if (layout.isFile()) {
627 // for now we don't parse the content of the layout and
628 // simply add the directory to the list.
629 skinList.add(skinFolder.getName());
634 return skinList.toArray(new String[skinList.size()]);
637 return new String[0];
641 * Loads all samples from the {@link SdkConstants#FD_SAMPLES} directory.
643 * @param log Logger. Cannot be null.
645 private void loadSamples(ISdkLog log) {
646 File sampleFolder = new File(mSdkLocation, SdkConstants.FD_SAMPLES);
647 if (sampleFolder.isDirectory()) {
648 File[] platforms = sampleFolder.listFiles();
650 for (File platform : platforms) {
651 if (platform.isDirectory()) {
652 // load the source.properties file and get an AndroidVersion object from it.
653 AndroidVersion version = getSamplesVersion(platform, log);
655 if (version != null) {
656 // locate the platform matching this version
657 for (IAndroidTarget target : mTargets) {
658 if (target.isPlatform() && target.getVersion().equals(version)) {
659 ((PlatformTarget)target).setSamplesPath(platform.getAbsolutePath());
670 * Returns the {@link AndroidVersion} of the sample in the given folder.
672 * @param folder The sample's folder.
673 * @param log Logger for errors. Cannot be null.
674 * @return An {@link AndroidVersion} or null on error.
676 private AndroidVersion getSamplesVersion(File folder, ISdkLog log) {
677 File sourceProp = new File(folder, SdkConstants.FN_SOURCE_PROP);
679 Properties p = new Properties();
680 p.load(new FileInputStream(sourceProp));
682 return new AndroidVersion(p);
683 } catch (FileNotFoundException e) {
684 log.warning("Ignoring sample '%1$s': does not contain %2$s.", //$NON-NLS-1$
685 folder.getName(), SdkConstants.FN_SOURCE_PROP);
686 } catch (IOException e) {
687 log.warning("Ignoring sample '%1$s': failed reading %2$s.", //$NON-NLS-1$
688 folder.getName(), SdkConstants.FN_SOURCE_PROP);
689 } catch (AndroidVersionException e) {
690 log.warning("Ignoring sample '%1$s': no android version found in %2$s.", //$NON-NLS-1$
691 folder.getName(), SdkConstants.FN_SOURCE_PROP);