blob: 9bd930e02288f5e6db24dc504a883d65cafcb80f [file] [log] [blame]
/*
* Copyright (C) 2008 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.avd;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.io.CancellableFileIo;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.ISystemImage;
import com.android.sdklib.PathFileWrapper;
import com.android.sdklib.SystemImageTags;
import com.android.sdklib.devices.Device;
import com.android.sdklib.repository.IdDisplay;
import com.android.sdklib.repository.targets.SystemImage;
import com.android.utils.ILogger;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import java.io.File;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
/** An immutable structure describing an Android Virtual Device. */
public final class AvdInfo {
/**
* Status for an {@link AvdInfo}. Indicates whether or not this AVD is valid.
*/
public enum AvdStatus {
/** No error */
OK,
/** Missing 'path' property in the ini file */
ERROR_PATH,
/** Missing config.ini file in the AVD data folder */
ERROR_CONFIG,
/** Unable to parse config.ini */
ERROR_PROPERTIES,
/** System Image folder in config.ini doesn't exist */
ERROR_IMAGE_DIR,
/** The {@link Device} this AVD is based on has changed from its original configuration*/
ERROR_DEVICE_CHANGED,
/** The {@link Device} this AVD is based on is no longer available */
ERROR_DEVICE_MISSING,
/** the {@link SystemImage} this AVD is based on is no longer available */
ERROR_IMAGE_MISSING,
/** The AVD's .ini file is corrupted */
ERROR_CORRUPTED_INI
}
@NonNull private final String mName;
@NonNull private final Path mIniFile;
@NonNull private final Path mFolderPath;
@NonNull private final ImmutableMap<String, String> mProperties;
@NonNull private final ImmutableMap<String, String> mUserSettings;
@NonNull private final AvdStatus mStatus;
@Nullable private final ISystemImage mSystemImage;
private final boolean mHasPlayStore;
/**
* Creates a new valid AVD info. Values are immutable.
*
* <p>Such an AVD is available and can be used. The error string is set to null.
*
* @param name The name of the AVD (for display or reference)
* @param iniFile The path to the config.ini file
* @param folderPath The path to the data directory
* @param systemImage The system image.
* @param properties The configuration properties. If null, an empty map will be created.
*/
public AvdInfo(
@NonNull String name,
@NonNull Path iniFile,
@NonNull Path folderPath,
@NonNull ISystemImage systemImage,
@Nullable Map<String, String> properties,
@Nullable Map<String, String> userSettings) {
this(name, iniFile, folderPath, systemImage, properties, userSettings, AvdStatus.OK);
}
/**
* Creates a new <em>invalid</em> AVD info. Values are immutable.
*
* <p>Such an AVD is not complete and cannot be used. The error string must be non-null.
*
* @param name The name of the AVD (for display or reference)
* @param iniFile The path to the config.ini file
* @param folderPath The path to the data directory
* @param systemImage The system image. Can be null if the image wasn't found.
* @param properties The configuration properties. If null, an empty map will be created.
* @param status The {@link AvdStatus} of this AVD. Cannot be null.
*/
public AvdInfo(
@NonNull String name,
@NonNull Path iniFile,
@NonNull Path folderPath,
@Nullable ISystemImage systemImage,
@Nullable Map<String, String> properties,
@Nullable Map<String, String> userSettings,
@NonNull AvdStatus status) {
mName = name;
mIniFile = iniFile;
mFolderPath = folderPath;
mSystemImage = systemImage;
mProperties = properties == null ? ImmutableMap.of() : ImmutableMap.copyOf(properties);
mUserSettings =
userSettings == null ? ImmutableMap.of() : ImmutableMap.copyOf(userSettings);
mStatus = status;
String psString = mProperties.get(AvdManager.AVD_INI_PLAYSTORE_ENABLED);
mHasPlayStore = "true".equalsIgnoreCase(psString) || "yes".equalsIgnoreCase(psString);
}
/** Returns a stable ID for the AVD that doesn't change even if the device is renamed */
@NonNull
public String getId() {
return mFolderPath.toString();
}
/** Returns the name of the AVD. Do not use this as a device ID; use getId instead. */
@NonNull
public String getName() {
return mName;
}
/** Returns the name of the AVD for use in UI. */
@NonNull
public String getDisplayName() {
String name = getProperties().get(AvdManager.AVD_INI_DISPLAY_NAME);
return name == null ? mName.replace('_', ' ') : name;
}
/** Returns the path of the AVD data directory. */
@NonNull
public Path getDataFolderPath() {
return mFolderPath;
}
/** Returns the tag id/display of the AVD. */
@NonNull
public IdDisplay getTag() {
String id = getProperties().get(AvdManager.AVD_INI_TAG_ID);
if (id == null) {
return SystemImageTags.DEFAULT_TAG;
}
String display = getProperties().get(AvdManager.AVD_INI_TAG_DISPLAY);
return IdDisplay.create(id, display == null ? id : display);
}
public ImmutableList<IdDisplay> getTags() {
String ids = getProperties().get(AvdManager.AVD_INI_TAG_IDS);
if (ids == null) {
return ImmutableList.of(getTag());
}
String displays =
Strings.nullToEmpty(getProperties().get(AvdManager.AVD_INI_TAG_DISPLAYNAMES));
return Streams.zip(
Splitter.on(",").splitToStream(ids),
Stream.concat(
Splitter.on(",").splitToStream(displays),
Stream.generate(() -> "")),
(id, display) -> IdDisplay.create(id, display.isEmpty() ? id : display))
.collect(toImmutableList());
}
/** Returns the processor type of the AVD. */
@NonNull
public String getAbiType() {
return getProperties().get(AvdManager.AVD_INI_ABI_TYPE);
}
/** Returns true if this AVD supports Google Play Store */
public boolean hasPlayStore() {
return mHasPlayStore;
}
@NonNull
public AndroidVersion getAndroidVersion() {
Map<String, String> properties = getProperties();
String apiStr = properties.get(AvdManager.AVD_INI_ANDROID_API);
String codename = properties.get(AvdManager.AVD_INI_ANDROID_CODENAME);
int api = 1;
if (!Strings.isNullOrEmpty(apiStr)) {
try {
api = Integer.parseInt(apiStr);
}
catch (NumberFormatException e) {
// continue with the default
}
}
String extStr = properties.get(AvdManager.AVD_INI_ANDROID_EXTENSION);
int extension = 1;
if (!Strings.isNullOrEmpty(extStr)) {
try {
extension = Integer.parseInt(extStr);
} catch (NumberFormatException e) {
// continue with the default
}
}
String isBaseStr = properties.get(AvdManager.AVD_INI_ANDROID_IS_BASE_EXTENSION);
boolean isBase = true;
if (!Strings.isNullOrEmpty(isBaseStr)) {
isBase = Boolean.parseBoolean(isBaseStr);
}
return new AndroidVersion(api, codename, extension, isBase);
}
@NonNull
public String getCpuArch() {
String cpuArch = mProperties.get(AvdManager.AVD_INI_CPU_ARCH);
if (cpuArch != null) {
return cpuArch;
}
// legacy
return SdkConstants.CPU_ARCH_ARM;
}
@NonNull
public String getDeviceManufacturer() {
String deviceManufacturer = mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER);
if (deviceManufacturer != null && !deviceManufacturer.isEmpty()) {
return deviceManufacturer;
}
return ""; // $NON-NLS-1$
}
@NonNull
public String getDeviceName() {
String deviceName = mProperties.get(AvdManager.AVD_INI_DEVICE_NAME);
if (deviceName != null && !deviceName.isEmpty()) {
return deviceName;
}
return ""; // $NON-NLS-1$
}
/**
* Gets the system image for this AVD. Can be null if the system image is not found.
*/
@Nullable
public ISystemImage getSystemImage() {
return mSystemImage;
}
/** Returns the {@link AvdStatus} of the receiver. */
@NonNull
public AvdStatus getStatus() {
return mStatus;
}
/**
* Helper method that returns the default AVD folder that would be used for a given AVD name
* <em>if and only if</em> the AVD was created with the default choice.
*
* <p>Callers must NOT use this to "guess" the actual folder from an actual AVD since the
* purpose of the AVD .ini file is to be able to change this folder. Callers should however use
* this to create a new {@link AvdInfo} to setup its data folder to the default.
*
* <p>The default is {@code getBaseAvdFolder()/avdname.avd/}.
*
* <p>For an actual existing AVD, callers must use {@link #getDataFolderPath()} instead.
*
* @param manager The AVD Manager, used to get the AVD storage path.
* @param avdName The name of the desired AVD.
* @param unique Whether to return the default or a unique variation of the default.
*/
@NonNull
public static Path getDefaultAvdFolder(
@NonNull AvdManager manager, @NonNull String avdName, boolean unique) {
Path base = manager.getBaseAvdFolder();
Path result = base.resolve(avdName + AvdManager.AVD_FOLDER_EXTENSION);
if (unique) {
int suffix = 0;
while (CancellableFileIo.exists(result)) {
result =
base.resolve(
String.format(
"%s_%d%s",
avdName, (++suffix), AvdManager.AVD_FOLDER_EXTENSION));
}
}
return result;
}
/**
* Helper method that returns the .ini {@link File} for a given AVD name.
*
* <p>The default is {@code getBaseAvdFolder()/avdname.ini}.
*
* @param manager The AVD Manager, used to get the AVD storage path.
* @param avdName The name of the desired AVD.
*/
@NonNull
public static Path getDefaultIniFile(@NonNull AvdManager manager, @NonNull String avdName) {
Path avdRoot = manager.getBaseAvdFolder();
return avdRoot.resolve(avdName + AvdManager.INI_EXTENSION);
}
/** Returns the .ini {@link File} for this AVD. */
@NonNull
public Path getIniFile() {
return mIniFile;
}
/** Helper method that returns the Config file for a given AVD name. */
@NonNull
public static Path getConfigFile(@NonNull Path path) {
return path.resolve(AvdManager.CONFIG_INI);
}
/** Helper method that returns the User Settings Path. */
@NonNull
public static Path getUserSettingsPath(@NonNull Path dataFolder) {
return dataFolder.resolve(AvdManager.USER_SETTINGS_INI);
}
static Map<String, String> parseUserSettingsFile(
@NonNull Path dataFolder, @Nullable ILogger logger) {
PathFileWrapper settingsPath = new PathFileWrapper(getUserSettingsPath(dataFolder));
if (settingsPath.exists()) {
Map<String, String> parsedSettings = AvdManager.parseIniFile(settingsPath, logger);
if (parsedSettings != null) {
return parsedSettings;
}
}
return new HashMap<>();
}
@NonNull
public Map<String, String> getUserSettings() {
return mUserSettings;
}
/** Returns the Config file for this AVD. */
@NonNull
public Path getConfigFile() {
return getConfigFile(mFolderPath);
}
/**
* Returns the value of the property with the given name, or null if the AVD doesn't have such
* property.
*/
@Nullable
public String getProperty(@NonNull String propertyName) {
return mProperties.get(propertyName);
}
/**
* Returns an ImmutableMap of the AVD's configuration properties; i.e. the properties stored in
* the <code>config.ini</code> file. Keys are defined in the <code>AVD_INI*</code> fields of
* {@link AvdManager}.
*/
@NonNull
public Map<String, String> getProperties() {
return mProperties;
}
/**
* Returns the error message for the AVD or <code>null</code> if {@link #getStatus()}
* returns {@link AvdStatus#OK}
*/
@Nullable
public String getErrorMessage() {
switch (mStatus) {
case ERROR_PATH:
return String.format("Missing AVD 'path' property in %1$s", getIniFile());
case ERROR_CONFIG:
return String.format("Missing config.ini file in %1$s", mFolderPath);
case ERROR_PROPERTIES:
return String.format("Failed to parse properties from %1$s",
getConfigFile());
case ERROR_IMAGE_DIR:
case ERROR_IMAGE_MISSING:
return String.format(
"Missing system image for %s%s %s.",
SystemImageTags.DEFAULT_TAG.equals(getTag())
? ""
: (getTag().getDisplay() + " "),
getAbiType(),
mName);
case ERROR_DEVICE_CHANGED:
return String.format("%1$s %2$s configuration has changed since AVD creation",
mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER),
mProperties.get(AvdManager.AVD_INI_DEVICE_NAME));
case ERROR_DEVICE_MISSING:
return String.format("%1$s %2$s no longer exists as a device",
mProperties.get(AvdManager.AVD_INI_DEVICE_MANUFACTURER),
mProperties.get(AvdManager.AVD_INI_DEVICE_NAME));
case ERROR_CORRUPTED_INI:
return String.format("Corrupted AVD ini file: %1$s", getIniFile());
case OK:
return null;
}
return null;
}
public boolean isSameMetadata(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AvdInfo avdInfo = (AvdInfo) o;
return mName.equals(avdInfo.mName) && mUserSettings.equals(avdInfo.mUserSettings);
}
@NonNull
public AvdInfo copyMetadata(@NonNull AvdInfo other) {
return new AvdInfo(
other.getName(),
getIniFile(),
getDataFolderPath(),
getSystemImage(),
getProperties(),
other.getUserSettings(),
getStatus());
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AvdInfo avdInfo = (AvdInfo) o;
return mHasPlayStore == avdInfo.mHasPlayStore
&& mName.equals(avdInfo.mName)
&& mIniFile.equals(avdInfo.mIniFile)
&& mFolderPath.equals(avdInfo.mFolderPath)
&& mProperties.equals(avdInfo.mProperties)
&& mUserSettings.equals(avdInfo.mUserSettings)
&& mStatus == avdInfo.mStatus
&& Objects.equals(mSystemImage, avdInfo.mSystemImage);
}
@Override
public int hashCode() {
int hashCode = mName.hashCode();
hashCode = 31 * hashCode + mIniFile.hashCode();
hashCode = 31 * hashCode + mFolderPath.hashCode();
hashCode = 31 * hashCode + mProperties.hashCode();
hashCode = 31 * hashCode + mUserSettings.hashCode();
hashCode = 31 * hashCode + mStatus.hashCode();
hashCode = 31 * hashCode + Objects.hashCode(mSystemImage);
hashCode = 31 * hashCode + Objects.hashCode(mHasPlayStore);
return hashCode;
}
@NonNull
public String toDebugString() {
String separator = System.lineSeparator();
return "mName = "
+ mName
+ separator
+ "mIniFile = "
+ mIniFile
+ separator
+ "mFolderPath = "
+ mFolderPath
+ separator
+ "mProperties = "
+ mProperties
+ separator
+ "mUserSettings = "
+ mUserSettings
+ separator
+ "mStatus = "
+ mStatus
+ separator
+ "mSystemImage = "
+ mSystemImage
+ separator
+ "mHasPlayStore = "
+ mHasPlayStore
+ separator;
}
}