1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17 package com.android.sdklib.internal.repository; 18 19 import com.android.annotations.VisibleForTesting; 20 import com.android.annotations.VisibleForTesting.Visibility; 21 import com.android.sdklib.AndroidVersion; 22 import com.android.sdklib.SdkConstants; 23 import com.android.sdklib.SdkManager; 24 import com.android.sdklib.SystemImage; 25 import com.android.sdklib.AndroidVersion.AndroidVersionException; 26 import com.android.sdklib.internal.repository.Archive.Arch; 27 import com.android.sdklib.internal.repository.Archive.Os; 28 import com.android.sdklib.repository.PkgProps; 29 import com.android.sdklib.repository.SdkRepoConstants; 30 31 import org.w3c.dom.Node; 32 33 import java.io.File; 34 import java.util.Map; 35 import java.util.Properties; 36 37 /** 38 * Represents a system-image XML node in an SDK repository. 39 */ 40 public class SystemImagePackage extends Package 41 implements IPackageVersion, IPlatformDependency { 42 43 /** The package version, for platform, add-on and doc packages. */ 44 private final AndroidVersion mVersion; 45 46 /** The ABI of the system-image. Must not be null nor empty. */ 47 private final String mAbi; 48 49 /** 50 * Creates a new system-image package from the attributes and elements of the given XML node. 51 * This constructor should throw an exception if the package cannot be created. 52 * 53 * @param source The {@link SdkSource} where this is loaded from. 54 * @param packageNode The XML element being parsed. 55 * @param nsUri The namespace URI of the originating XML document, to be able to deal with 56 * parameters that vary according to the originating XML schema. 57 * @param licenses The licenses loaded from the XML originating document. 58 */ 59 SystemImagePackage(SdkSource source, 60 Node packageNode, 61 String nsUri, 62 Map<String,String> licenses) { 63 super(source, packageNode, nsUri, licenses); 64 65 int apiLevel = XmlParserUtils.getXmlInt(packageNode, SdkRepoConstants.NODE_API_LEVEL, 0); 66 String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME); 67 if (codeName.length() == 0) { 68 codeName = null; 69 } 70 mVersion = new AndroidVersion(apiLevel, codeName); 71 72 mAbi = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_ABI); 73 } 74 75 @VisibleForTesting(visibility=Visibility.PRIVATE) 76 protected SystemImagePackage( 77 AndroidVersion platformVersion, 78 int revision, 79 String abi, 80 Properties props, 81 String localOsPath) { 82 this(null /*source*/, platformVersion, revision, abi, props, localOsPath); 83 } 84 85 @VisibleForTesting(visibility=Visibility.PRIVATE) 86 protected SystemImagePackage( 87 SdkSource source, 88 AndroidVersion platformVersion, 89 int revision, 90 String abi, 91 Properties props, 92 String localOsPath) { 93 super( source, //source 94 props, //properties 95 revision, //revision 96 null, //license 97 null, //description 98 null, //descUrl 99 Os.getCurrentOs(), //archiveOs 100 Arch.getCurrentArch(), //archiveArch 101 localOsPath //archiveOsPath 102 ); 103 mVersion = platformVersion; 104 if (abi == null && props != null) { 105 abi = props.getProperty(PkgProps.SYS_IMG_ABI); 106 } 107 assert abi != null : "To use this SystemImagePackage constructor you must pass an ABI as a parameter or as a PROP_ABI property"; 108 mAbi = abi; 109 } 110 111 /** 112 * Creates a {@link BrokenPackage} representing a system image that failed to load 113 * with the regular {@link SdkManager} workflow. 114 * 115 * @param abiDir The SDK/system-images/android-N/abi folder 116 * @param props The properties located in {@code abiDir} or null if not found. 117 * @return A new {@link BrokenPackage} that represents this installed package. 118 */ 119 public static Package createBroken(File abiDir, Properties props) { 120 AndroidVersion version = null; 121 String abiType = abiDir.getName(); 122 String error = null; 123 124 // Try to load the android version & ABI from the sources.props. 125 // If we don't find them, it would explain why this package is broken. 126 if (props == null) { 127 error = String.format("Missing file %1$s", SdkConstants.FN_SOURCE_PROP); 128 } else { 129 try { 130 version = new AndroidVersion(props); 131 132 String abi = props.getProperty(PkgProps.SYS_IMG_ABI); 133 if (abi != null) { 134 abiType = abi; 135 } else { 136 error = String.format("Invalid file %1$s: Missing property %2$s", 137 SdkConstants.FN_SOURCE_PROP, 138 PkgProps.SYS_IMG_ABI); 139 } 140 } catch (AndroidVersionException e) { 141 error = String.format("Invalid file %1$s: %2$s", 142 SdkConstants.FN_SOURCE_PROP, 143 e.getMessage()); 144 } 145 } 146 147 if (version == null) { 148 try { 149 // Try to parse the first number out of the platform folder name. 150 String platform = abiDir.getParentFile().getName(); 151 platform = platform.replaceAll("[^0-9]+", " ").trim(); //$NON-NLS-1$ //$NON-NLS-2$ 152 int pos = platform.indexOf(' '); 153 if (pos >= 0) { 154 platform = platform.substring(0, pos); 155 } 156 int apiLevel = Integer.parseInt(platform); 157 version = new AndroidVersion(apiLevel, null /*codename*/); 158 } catch (Exception ignore) { 159 } 160 } 161 162 StringBuilder sb = new StringBuilder( 163 String.format("Broken %1$s System Image", getAbiDisplayNameInternal(abiType))); 164 if (version != null) { 165 sb.append(String.format(", API %1$s", version.getApiString())); 166 } 167 168 String shortDesc = sb.toString(); 169 170 if (error != null) { 171 sb.append('\n').append(error); 172 } 173 174 String longDesc = sb.toString(); 175 176 return new BrokenPackage(props, shortDesc, longDesc, 177 IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED, 178 version==null ? IExactApiLevelDependency.API_LEVEL_INVALID : version.getApiLevel(), 179 abiDir.getAbsolutePath()); 180 } 181 182 /** 183 * Save the properties of the current packages in the given {@link Properties} object. 184 * These properties will later be given to a constructor that takes a {@link Properties} object. 185 */ 186 @Override 187 void saveProperties(Properties props) { 188 super.saveProperties(props); 189 190 mVersion.saveProperties(props); 191 props.setProperty(PkgProps.SYS_IMG_ABI, mAbi); 192 } 193 194 /** Returns the ABI of the system-image. Cannot be null nor empty. */ 195 public String getAbi() { 196 return mAbi; 197 } 198 199 /** Returns a display-friendly name for the ABI of the system-image. */ 200 public String getAbiDisplayName() { 201 return getAbiDisplayNameInternal(mAbi); 202 } 203 204 private static String getAbiDisplayNameInternal(String abi) { 205 return abi.replace("armeabi", "ARM EABI") //$NON-NLS-1$ //$NON-NLS-2$ 206 .replace("x86", "Intel x86 Atom") //$NON-NLS-1$ //$NON-NLS-2$ 207 .replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$ 208 } 209 210 /** 211 * Returns the version of the platform dependency of this package. 212 * <p/> 213 * A system-image has the same {@link AndroidVersion} as the platform it depends on. 214 */ 215 public AndroidVersion getVersion() { 216 return mVersion; 217 } 218 219 /** 220 * Returns a string identifier to install this package from the command line. 221 * For system images, we use "sysimg-N" where N is the API or the preview codename. 222 * <p/> 223 * {@inheritDoc} 224 */ 225 @Override 226 public String installId() { 227 return "sysimg-" + mVersion.getApiString(); //$NON-NLS-1$ 228 } 229 230 /** 231 * Returns a description of this package that is suitable for a list display. 232 * <p/> 233 * {@inheritDoc} 234 */ 235 @Override 236 public String getListDescription() { 237 return String.format("%1$s System Image%2$s", 238 getAbiDisplayName(), 239 isObsolete() ? " (Obsolete)" : ""); 240 } 241 242 /** 243 * Returns a short description for an {@link IDescription}. 244 */ 245 @Override 246 public String getShortDescription() { 247 return String.format("%1$s System Image, Android API %2$s, revision %3$s%4$s", 248 getAbiDisplayName(), 249 mVersion.getApiString(), 250 getRevision(), 251 isObsolete() ? " (Obsolete)" : ""); 252 } 253 254 /** 255 * Returns a long description for an {@link IDescription}. 256 * 257 * The long description is whatever the XML contains for the {@code description} field, 258 * or the short description if the former is empty. 259 */ 260 @Override 261 public String getLongDescription() { 262 String s = getDescription(); 263 if (s == null || s.length() == 0) { 264 s = getShortDescription(); 265 } 266 267 if (s.indexOf("revision") == -1) { 268 s += String.format("\nRevision %1$d%2$s", 269 getRevision(), 270 isObsolete() ? " (Obsolete)" : ""); 271 } 272 273 s += String.format("\nRequires SDK Platform Android API %1$s", 274 mVersion.getApiString()); 275 return s; 276 } 277 278 /** 279 * Computes a potential installation folder if an archive of this package were 280 * to be installed right away in the given SDK root. 281 * <p/> 282 * A system-image package is typically installed in SDK/systems/platform/abi. 283 * The name needs to be sanitized to be acceptable as a directory name. 284 * 285 * @param osSdkRoot The OS path of the SDK root folder. 286 * @param sdkManager An existing SDK manager to list current platforms and addons. 287 * @return A new {@link File} corresponding to the directory to use to install this package. 288 */ 289 @Override 290 public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) { 291 File folder = new File(osSdkRoot, SdkConstants.FD_SYSTEM_IMAGES); 292 folder = new File(folder, SystemImage.ANDROID_PREFIX + mVersion.getApiString()); 293 294 // Computes a folder directory using the sanitized abi string. 295 String abi = mAbi; 296 abi = abi.toLowerCase(); 297 abi = abi.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$ 298 abi = abi.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$ 299 300 folder = new File(folder, abi); 301 return folder; 302 } 303 304 @Override 305 public boolean sameItemAs(Package pkg) { 306 if (pkg instanceof SystemImagePackage) { 307 SystemImagePackage newPkg = (SystemImagePackage)pkg; 308 309 // check they are the same abi and version. 310 return getAbi().equals(newPkg.getAbi()) && 311 getVersion().equals(newPkg.getVersion()); 312 } 313 314 return false; 315 } 316 317 @Override 318 public int hashCode() { 319 final int prime = 31; 320 int result = super.hashCode(); 321 result = prime * result + ((mAbi == null) ? 0 : mAbi.hashCode()); 322 result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode()); 323 return result; 324 } 325 326 @Override 327 public boolean equals(Object obj) { 328 if (this == obj) { 329 return true; 330 } 331 if (!super.equals(obj)) { 332 return false; 333 } 334 if (!(obj instanceof SystemImagePackage)) { 335 return false; 336 } 337 SystemImagePackage other = (SystemImagePackage) obj; 338 if (mAbi == null) { 339 if (other.mAbi != null) { 340 return false; 341 } 342 } else if (!mAbi.equals(other.mAbi)) { 343 return false; 344 } 345 if (mVersion == null) { 346 if (other.mVersion != null) { 347 return false; 348 } 349 } else if (!mVersion.equals(other.mVersion)) { 350 return false; 351 } 352 return true; 353 } 354 } 355