blob: 1e9e3db9fdf53eb50cc7aa1f3e9e5c5e23a995eb [file] [log] [blame]
/*
* Copyright (C) 2013 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.repository.local;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISystemImage;
import com.android.sdklib.ISystemImage.LocationType;
import com.android.sdklib.SdkManager.LayoutlibVersion;
import com.android.sdklib.SystemImage;
import com.android.sdklib.internal.androidTarget.PlatformTarget;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.packages.PlatformPackage;
import com.android.sdklib.io.FileOp;
import com.android.sdklib.io.IFileOp;
import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.MajorRevision;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.descriptors.IPkgDesc;
import com.android.sdklib.repository.descriptors.IdDisplay;
import com.android.sdklib.repository.descriptors.PkgDesc;
import com.android.sdklib.repository.descriptors.PkgType;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.TreeMultimap;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
@SuppressWarnings("ConstantConditions")
public class LocalPlatformPkgInfo extends LocalPkgInfo {
public static final String PROP_VERSION_SDK = "ro.build.version.sdk"; //$NON-NLS-1$
public static final String PROP_VERSION_CODENAME = "ro.build.version.codename"; //$NON-NLS-1$
public static final String PROP_VERSION_RELEASE = "ro.build.version.release"; //$NON-NLS-1$
@NonNull
private final IPkgDesc mDesc;
/** Android target, lazyly loaded from #getAndroidTarget */
private IAndroidTarget mTarget;
private boolean mLoaded;
public LocalPlatformPkgInfo(@NonNull LocalSdk localSdk,
@NonNull File localDir,
@NonNull Properties sourceProps,
@NonNull AndroidVersion version,
@NonNull MajorRevision revision,
@NonNull FullRevision minToolsRev) {
super(localSdk, localDir, sourceProps);
mDesc = PkgDesc.Builder.newPlatform(version, revision, minToolsRev).create();
}
@NonNull
@Override
public IPkgDesc getDesc() {
return mDesc;
}
/** The "path" of a Platform is its Target Hash. */
@NonNull
public String getTargetHash() {
return getDesc().getPath();
}
@Nullable
public IAndroidTarget getAndroidTarget() {
if (!mLoaded) {
mTarget = createAndroidTarget();
mLoaded = true;
}
return mTarget;
}
public boolean isLoaded() {
return mLoaded;
}
@Override
public Package getPackage() {
Package pkg = super.getPackage();
if (pkg != null) {
return pkg;
}
pkg = createPackage();
setPackage(pkg);
return pkg;
}
//-----
/**
* Creates a PlatformPackage wrapping the IAndroidTarget if defined.
* Invoked by {@link #getPackage()}.
*
* @return A Package or null if target isn't available.
*/
@Nullable
protected Package createPackage() {
IAndroidTarget target = getAndroidTarget();
if (target != null) {
return PlatformPackage.create(target, getSourceProperties());
}
return null;
}
/**
* Creates the PlatformTarget. Invoked by {@link #getAndroidTarget()}.
*/
@SuppressWarnings("ConstantConditions")
@Nullable
protected IAndroidTarget createAndroidTarget() {
LocalSdk sdk = getLocalSdk();
IFileOp fileOp = sdk.getFileOp();
File platformFolder = getLocalDir();
File buildProp = new File(platformFolder, SdkConstants.FN_BUILD_PROP);
File sourcePropFile = new File(platformFolder, SdkConstants.FN_SOURCE_PROP);
if (!fileOp.isFile(buildProp) || !fileOp.isFile(sourcePropFile)) {
appendLoadError("Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
platformFolder.getName(),
SdkConstants.FN_BUILD_PROP);
return null;
}
Map<String, String> platformProp = new HashMap<String, String>();
// add all the property files
Map<String, String> map = null;
try {
map = ProjectProperties.parsePropertyStream(
fileOp.newFileInputStream(buildProp),
buildProp.getPath(),
null /*log*/);
if (map != null) {
platformProp.putAll(map);
}
} catch (FileNotFoundException ignore) {}
try {
map = ProjectProperties.parsePropertyStream(
fileOp.newFileInputStream(sourcePropFile),
sourcePropFile.getPath(),
null /*log*/);
if (map != null) {
platformProp.putAll(map);
}
} catch (FileNotFoundException ignore) {}
File sdkPropFile = new File(platformFolder, SdkConstants.FN_SDK_PROP);
if (fileOp.isFile(sdkPropFile)) { // obsolete platforms don't have this.
try {
map = ProjectProperties.parsePropertyStream(
fileOp.newFileInputStream(sdkPropFile),
sdkPropFile.getPath(),
null /*log*/);
if (map != null) {
platformProp.putAll(map);
}
} catch (FileNotFoundException ignore) {}
}
// look for some specific values in the map.
// api level
int apiNumber;
String stringValue = platformProp.get(PROP_VERSION_SDK);
if (stringValue == null) {
appendLoadError("Ignoring platform '%1$s': %2$s is missing from '%3$s'",
platformFolder.getName(), PROP_VERSION_SDK,
SdkConstants.FN_BUILD_PROP);
return null;
} else {
try {
apiNumber = Integer.parseInt(stringValue);
} catch (NumberFormatException e) {
// looks like apiNumber does not parse to a number.
// Ignore this platform.
appendLoadError(
"Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
platformFolder.getName(), PROP_VERSION_SDK,
SdkConstants.FN_BUILD_PROP);
return null;
}
}
// Codename must be either null or a platform codename.
// REL means it's a release version and therefore the codename should be null.
AndroidVersion apiVersion =
new AndroidVersion(apiNumber, platformProp.get(PROP_VERSION_CODENAME));
// version string
String apiName = platformProp.get(PkgProps.PLATFORM_VERSION);
if (apiName == null) {
apiName = platformProp.get(PROP_VERSION_RELEASE);
}
if (apiName == null) {
appendLoadError(
"Ignoring platform '%1$s': %2$s is missing from '%3$s'",
platformFolder.getName(), PROP_VERSION_RELEASE,
SdkConstants.FN_BUILD_PROP);
return null;
}
// platform rev number & layoutlib version are extracted from the source.properties
// saved by the SDK Manager when installing the package.
int revision = 1;
LayoutlibVersion layoutlibVersion = null;
try {
revision = Integer.parseInt(platformProp.get(PkgProps.PKG_REVISION));
} catch (NumberFormatException e) {
// do nothing, we'll keep the default value of 1.
}
try {
String propApi = platformProp.get(PkgProps.LAYOUTLIB_API);
String propRev = platformProp.get(PkgProps.LAYOUTLIB_REV);
int llApi = propApi == null ? LayoutlibVersion.NOT_SPECIFIED :
Integer.parseInt(propApi);
int llRev = propRev == null ? LayoutlibVersion.NOT_SPECIFIED :
Integer.parseInt(propRev);
if (llApi > LayoutlibVersion.NOT_SPECIFIED &&
llRev >= LayoutlibVersion.NOT_SPECIFIED) {
layoutlibVersion = new LayoutlibVersion(llApi, llRev);
}
} catch (NumberFormatException e) {
// do nothing, we'll ignore the layoutlib version if it's invalid
}
// api number and name look valid, perform a few more checks
String err = checkPlatformContent(fileOp, platformFolder);
if (err != null) {
appendLoadError("%s", err); //$NLN-NLS-1$
return null;
}
ISystemImage[] systemImages = getPlatformSystemImages(fileOp, platformFolder, apiVersion);
// create the target.
PlatformTarget pt = new PlatformTarget(
sdk.getLocation().getPath(),
platformFolder.getAbsolutePath(),
apiVersion,
apiName,
revision,
layoutlibVersion,
systemImages,
platformProp,
sdk.getLatestBuildTool());
// add the skins from the platform. Make a copy to not modify the original collection.
List<File> skins = new ArrayList<File>(parseSkinFolder(pt.getFile(IAndroidTarget.SKINS)));
// add the system-image specific skins, if any.
for (ISystemImage systemImage : systemImages) {
skins.addAll(Arrays.asList(systemImage.getSkins()));
}
pt.setSkins(skins.toArray(new File[skins.size()]));
// add path to the non-legacy samples package if it exists
LocalPkgInfo samples = sdk.getPkgInfo(PkgType.PKG_SAMPLE, getDesc().getAndroidVersion());
if (samples != null) {
pt.setSamplesPath(samples.getLocalDir().getAbsolutePath());
}
// add path to the non-legacy sources package if it exists
LocalPkgInfo sources = sdk.getPkgInfo(PkgType.PKG_SOURCE, getDesc().getAndroidVersion());
if (sources != null) {
pt.setSourcesPath(sources.getLocalDir().getAbsolutePath());
}
return pt;
}
/**
* Get all the system images supported by a platform target.
* For a platform, we first look in the new sdk/system-images folders then we
* look for sub-folders in the platform/images directory and/or the one legacy
* folder.
* If any given API appears twice or more, the first occurrence wins.
*
* @param fileOp File operation wrapper.
* @param platformDir Root of the platform target being loaded.
* @param apiVersion API level + codename of platform being loaded.
* @return an array of ISystemImage containing all the system images for the target.
* The list can be empty but not null.
*/
@NonNull
private ISystemImage[] getPlatformSystemImages(IFileOp fileOp,
File platformDir,
AndroidVersion apiVersion) {
Set<ISystemImage> found = new TreeSet<ISystemImage>();
SetMultimap<IdDisplay, String> tagToAbiFound = TreeMultimap.create();
// Look in the SDK/system-image/platform-n/tag/abi folders.
// Look in the SDK/system-image/platform-n/abi folders.
// If we find multiple occurrences of the same platform/abi, the first one read wins.
LocalPkgInfo[] sysImgInfos = getLocalSdk().getPkgsInfos(PkgType.PKG_SYS_IMAGE);
for (LocalPkgInfo pkg : sysImgInfos) {
IPkgDesc d = pkg.getDesc();
if (pkg instanceof LocalSysImgPkgInfo &&
!d.hasVendor() &&
apiVersion.equals(d.getAndroidVersion())) {
IdDisplay tag = d.getTag();
String abi = d.getPath();
if (tag != null && abi != null && !tagToAbiFound.containsEntry(tag, abi)) {
List<File> parsedSkins = parseSkinFolder(
new File(pkg.getLocalDir(), SdkConstants.FD_SKINS));
File[] skins = FileOp.EMPTY_FILE_ARRAY;
if (!parsedSkins.isEmpty()) {
skins = parsedSkins.toArray(new File[parsedSkins.size()]);
}
found.add(new SystemImage(
pkg.getLocalDir(),
LocationType.IN_SYSTEM_IMAGE,
tag,
abi,
skins));
tagToAbiFound.put(tag, abi);
}
}
}
// Look in either the platform/images/abi or the legacy folder
File imgDir = new File(platformDir, SdkConstants.OS_IMAGES_FOLDER);
File[] files = fileOp.listFiles(imgDir);
boolean useLegacy = true;
boolean hasImgFiles = false;
final IdDisplay defaultTag = SystemImage.DEFAULT_TAG;
// Look for sub-directories
for (File file : files) {
if (fileOp.isDirectory(file)) {
useLegacy = false;
String abi = file.getName();
if (!tagToAbiFound.containsEntry(defaultTag, abi)) {
found.add(new SystemImage(
file,
LocationType.IN_IMAGES_SUBFOLDER,
defaultTag,
abi,
FileOp.EMPTY_FILE_ARRAY));
tagToAbiFound.put(defaultTag, abi);
}
} else if (!hasImgFiles && fileOp.isFile(file)) {
if (file.getName().endsWith(".img")) { //$NON-NLS-1$
hasImgFiles = true;
}
}
}
if (useLegacy &&
hasImgFiles &&
fileOp.isDirectory(imgDir) &&
!tagToAbiFound.containsEntry(defaultTag, SdkConstants.ABI_ARMEABI)) {
// We found no sub-folder system images but it looks like the top directory
// has some img files in it. It must be a legacy ARM EABI system image folder.
found.add(new SystemImage(
imgDir,
LocationType.IN_LEGACY_FOLDER,
defaultTag,
SdkConstants.ABI_ARMEABI,
FileOp.EMPTY_FILE_ARRAY));
}
return found.toArray(new ISystemImage[found.size()]);
}
/**
* Parses the skin folder and builds the skin list.
* @param skinRootFolder The path to the skin root folder.
*/
@NonNull
protected List<File> parseSkinFolder(@NonNull File skinRootFolder) {
IFileOp fileOp = getLocalSdk().getFileOp();
if (fileOp.isDirectory(skinRootFolder)) {
ArrayList<File> skinList = new ArrayList<File>();
File[] files = fileOp.listFiles(skinRootFolder);
for (File skinFolder : files) {
if (fileOp.isDirectory(skinFolder)) {
// check for layout file
File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
if (fileOp.isFile(layout)) {
// for now we don't parse the content of the layout and
// simply add the directory to the list.
skinList.add(skinFolder);
}
}
}
return skinList;
}
return Collections.emptyList();
}
/** List of items in the platform to check when parsing it. These paths are relative to the
* platform root folder. */
private static final String[] sPlatformContentList = new String[] {
SdkConstants.FN_FRAMEWORK_LIBRARY,
SdkConstants.FN_FRAMEWORK_AIDL,
};
/**
* Checks the given platform has all the required files, and returns true if they are all
* present.
* <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
* aidl(.exe), dx(.bat), and dx.jar
*
* @param fileOp File operation wrapper.
* @param platform The folder containing the platform.
* @return An error description if platform is rejected; null if no error is detected.
*/
@NonNull
private static String checkPlatformContent(IFileOp fileOp, @NonNull File platform) {
for (String relativePath : sPlatformContentList) {
File f = new File(platform, relativePath);
if (!fileOp.exists(f)) {
return String.format(
"Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
platform.getName(), relativePath);
}
}
return null;
}
}