/*
* 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.sdklib.internal.repository;
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.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
import com.android.util.Pair;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Scans a local SDK to find which packages are currently installed.
*/
public class LocalSdkParser {
private Package[] mPackages;
public LocalSdkParser() {
// pass
}
/**
* Returns the packages found by the last call to
* {@link #parseSdk(String, SdkManager, ISdkLog)}.
* <p/>
* This returns initially returns null.
* Once the parseSdk() method has been called, this returns a possibly empty but non-null array.
*/
public Package[] getPackages() {
return mPackages;
}
/**
* Clear the internal packages list. After this call, {@link #getPackages()} will return
* null till {@link #parseSdk(String, SdkManager, ISdkLog)} is called.
*/
public void clearPackages() {
mPackages = null;
}
/**
* Scan the give SDK to find all the packages already installed at this location.
* <p/>
* Store the packages internally. You can use {@link #getPackages()} to retrieve them
* at any time later.
*
* @param osSdkRoot The path to the SDK folder.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @param log An SDK logger object. Cannot be null.
* @return The packages found. Can be retrieved later using {@link #getPackages()}.
*/
public Package[] parseSdk(String osSdkRoot, SdkManager sdkManager, ISdkLog log) {
ArrayList<Package> packages = new ArrayList<Package>();
HashSet<File> visited = new HashSet<File>();
File dir = new File(osSdkRoot, SdkConstants.FD_DOCS);
Package pkg = scanDoc(dir, log);
if (pkg != null) {
packages.add(pkg);
visited.add(dir);
}
dir = new File(osSdkRoot, SdkConstants.FD_TOOLS);
pkg = scanTools(dir, log);
if (pkg != null) {
packages.add(pkg);
visited.add(dir);
}
dir = new File(osSdkRoot, SdkConstants.FD_PLATFORM_TOOLS);
pkg = scanPlatformTools(dir, log);
if (pkg != null) {
packages.add(pkg);
visited.add(dir);
}
File samplesRoot = new File(osSdkRoot, SdkConstants.FD_SAMPLES);
// for platforms, add-ons and samples, rely on the SdkManager parser
for(IAndroidTarget target : sdkManager.getTargets()) {
Properties props = parseProperties(new File(target.getLocation(),
SdkConstants.FN_SOURCE_PROP));
try {
if (target.isPlatform()) {
pkg = PlatformPackage.create(target, props);
if (samplesRoot.isDirectory()) {
// Get the samples dir for a platform if it is located in the new
// root /samples dir. We purposely ignore "old" samples that are
// located under the platform dir.
File samplesDir = new File(target.getPath(IAndroidTarget.SAMPLES));
if (samplesDir.exists() && samplesDir.getParentFile().equals(samplesRoot)) {
Properties samplesProps = parseProperties(
new File(samplesDir, SdkConstants.FN_SOURCE_PROP));
if (samplesProps != null) {
Package pkg2 = SamplePackage.create(target, samplesProps);
packages.add(pkg2);
}
visited.add(samplesDir);
}
}
} else {
pkg = AddonPackage.create(target, props);
}
} catch (Exception e) {
log.error(e, null);
}
if (pkg != null) {
packages.add(pkg);
visited.add(new File(target.getLocation()));
}
}
scanMissingAddons(sdkManager, visited, packages, log);
scanMissingSamples(osSdkRoot, visited, packages, log);
scanExtras(osSdkRoot, visited, packages, log);
scanExtrasDirectory(osSdkRoot, visited, packages, log);
Collections.sort(packages);
mPackages = packages.toArray(new Package[packages.size()]);
return mPackages;
}
/**
* Find any directory in the /extras/vendors/path folders for extra packages.
* This isn't a recursive search.
*/
private void scanExtras(String osSdkRoot,
HashSet<File> visited,
ArrayList<Package> packages,
ISdkLog log) {
File root = new File(osSdkRoot, SdkConstants.FD_EXTRAS);
if (!root.isDirectory()) {
// This should not happen. It makes listFiles() return null so let's avoid it.
return;
}
for (File vendor : root.listFiles()) {
if (vendor.isDirectory()) {
scanExtrasDirectory(vendor.getAbsolutePath(), visited, packages, log);
}
}
}
/**
* Find any other directory in the given "root" directory that hasn't been visited yet
* and assume they contain extra packages. This is <em>not</em> a recursive search.
*/
private void scanExtrasDirectory(String extrasRoot,
HashSet<File> visited,
ArrayList<Package> packages,
ISdkLog log) {
File root = new File(extrasRoot);
if (!root.isDirectory()) {
// This should not happen. It makes listFiles() return null so let's avoid it.
return;
}
for (File dir : root.listFiles()) {
if (dir.isDirectory() && !visited.contains(dir)) {
Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
if (props != null) {
try {
Package pkg = ExtraPackage.create(
null, //source
props, //properties
null, //vendor
dir.getName(), //path
0, //revision
null, //license
"Tools", //description
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
dir.getPath() //archiveOsPath
);
packages.add(pkg);
visited.add(dir);
} catch (Exception e) {
log.error(e, null);
}
}
}
}
}
/**
* Find any other sub-directories under the /samples root that hasn't been visited yet
* and assume they contain sample packages. This is <em>not</em> a recursive search.
* <p/>
* The use case is to find samples dirs under /samples when their target isn't loaded.
*/
private void scanMissingSamples(String osSdkRoot,
HashSet<File> visited,
ArrayList<Package> packages,
ISdkLog log) {
File root = new File(osSdkRoot);
root = new File(root, SdkConstants.FD_SAMPLES);
if (!root.isDirectory()) {
// It makes listFiles() return null so let's avoid it.
return;
}
for (File dir : root.listFiles()) {
if (dir.isDirectory() && !visited.contains(dir)) {
Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
if (props != null) {
try {
Package pkg = SamplePackage.create(dir.getAbsolutePath(), props);
packages.add(pkg);
visited.add(dir);
} catch (Exception e) {
log.error(e, null);
}
}
}
}
}
/**
* The sdk manager only lists valid addons. However here we also want to find "broken"
* addons, i.e. addons that failed to load for some reason.
* <p/>
* Find any other sub-directories under the /add-ons root that hasn't been visited yet
* and assume they contain broken addons.
*/
private void scanMissingAddons(SdkManager sdkManager,
HashSet<File> visited,
ArrayList<Package> packages,
ISdkLog log) {
File addons = new File(new File(sdkManager.getLocation()), SdkConstants.FD_ADDONS);
if (!addons.isDirectory()) {
// It makes listFiles() return null so let's avoid it.
return;
}
for (File dir : addons.listFiles()) {
if (dir.isDirectory() && !visited.contains(dir)) {
Pair<Map<String, String>, String> infos =
SdkManager.parseAddonProperties(dir, sdkManager.getTargets(), log);
Map<String, String> props = infos.getFirst();
String error = infos.getSecond();
try {
Package pkg = AddonPackage.create(dir.getAbsolutePath(), props, error);
packages.add(pkg);
visited.add(dir);
} catch (Exception e) {
log.error(e, null);
}
}
}
}
/**
* Try to find a tools package at the given location.
* Returns null if not found.
*/
private Package scanTools(File toolFolder, ISdkLog log) {
// Can we find some properties?
Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP));
// We're not going to check that all tools are present. At the very least
// we should expect to find android and an emulator adapted to the current OS.
Set<String> names = new HashSet<String>();
File[] files = toolFolder.listFiles();
if (files != null) {
for (File file : files) {
names.add(file.getName());
}
}
if (!names.contains(SdkConstants.androidCmdName()) ||
!names.contains(SdkConstants.FN_EMULATOR)) {
return null;
}
// Create our package. use the properties if we found any.
try {
Package pkg = ToolPackage.create(
null, //source
props, //properties
0, //revision
null, //license
"Tools", //description
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
toolFolder.getPath() //archiveOsPath
);
return pkg;
} catch (Exception e) {
log.error(e, null);
}
return null;
}
/**
* Try to find a platform-tools package at the given location.
* Returns null if not found.
*/
private Package scanPlatformTools(File platformToolsFolder, ISdkLog log) {
// Can we find some properties?
Properties props = parseProperties(new File(platformToolsFolder,
SdkConstants.FN_SOURCE_PROP));
// We're not going to check that all tools are present. At the very least
// we should expect to find adb, aidl, aapt and dx (adapted to the current OS).
if (platformToolsFolder.listFiles() == null) {
// ListFiles is null if the directory doesn't even exist.
// Not going to find anything in there...
return null;
}
// Create our package. use the properties if we found any.
try {
Package pkg = PlatformToolPackage.create(
null, //source
props, //properties
0, //revision
null, //license
"Platform Tools", //description
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
platformToolsFolder.getPath() //archiveOsPath
);
return pkg;
} catch (Exception e) {
log.error(e, null);
}
return null;
}
/**
* Try to find a docs package at the given location.
* Returns null if not found.
*/
private Package scanDoc(File docFolder, ISdkLog log) {
// Can we find some properties?
Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP));
// To start with, a doc folder should have an "index.html" to be acceptable.
// We don't actually check the content of the file.
if (new File(docFolder, "index.html").isFile()) {
try {
Package pkg = DocPackage.create(
null, //source
props, //properties
0, //apiLevel
null, //codename
0, //revision
null, //license
null, //description
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
docFolder.getPath() //archiveOsPath
);
return pkg;
} catch (Exception e) {
log.error(e, null);
}
}
return null;
}
/**
* Parses the given file as properties file if it exists.
* Returns null if the file does not exist, cannot be parsed or has no properties.
*/
private Properties parseProperties(File propsFile) {
FileInputStream fis = null;
try {
if (propsFile.exists()) {
fis = new FileInputStream(propsFile);
Properties props = new Properties();
props.load(fis);
// To be valid, there must be at least one property in it.
if (props.size() > 0) {
return props;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
return null;
}
}