| /* |
| * Copyright (C) 2012 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.devices; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.dvlib.DeviceSchema; |
| import com.android.resources.ScreenOrientation; |
| import com.android.resources.ScreenRound; |
| |
| import java.awt.Dimension; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Instances of this class contain the specifications for a device. Use the |
| * {@link Builder} class to construct a Device object, or the |
| * {@link DeviceParser} if constructing device objects from XML conforming to |
| * the {@link DeviceSchema} standards. |
| */ |
| public final class Device { |
| |
| /** Name of the device */ |
| @NonNull |
| private final String mName; |
| |
| /** ID of the device */ |
| @NonNull |
| private final String mId; |
| |
| /** Manufacturer of the device */ |
| @NonNull |
| private final String mManufacturer; |
| |
| /** A list of software capabilities, one for each API level range */ |
| @NonNull |
| private final List<Software> mSoftware; |
| |
| /** A list of phone states (landscape, portrait with keyboard out, etc.) */ |
| @NonNull |
| private final List<State> mState; |
| |
| /** Meta information such as icon files and device frames */ |
| @NonNull |
| private final Meta mMeta; |
| |
| /** Default state of the device */ |
| @NonNull |
| private final State mDefaultState; |
| |
| /** Optional tag-id of the device. */ |
| @Nullable |
| private String mTagId; |
| |
| /** Optional boot.props of the device. */ |
| @NonNull |
| private Map<String, String> mBootProps; |
| |
| /** |
| * Returns the name of the {@link Device}. This is intended to be displayed by the user and |
| * can vary over time. For a stable internal name of the device, use {@link #getId} instead. |
| * |
| * @deprecated Use {@link #getId()} or {@link #getDisplayName()} instead based on whether |
| * a stable identifier or a user visible name is needed |
| * @return The name of the {@link Device}. |
| */ |
| @NonNull |
| @Deprecated |
| public String getName() { |
| return mName; |
| } |
| |
| /** |
| * Returns the user visible name of the {@link Device}. This is intended to be displayed by the |
| * user and can vary over time. For a stable internal name of the device, use {@link #getId} |
| * instead. |
| * |
| * @return The name of the {@link Device}. |
| */ |
| @NonNull |
| public String getDisplayName() { |
| return mName; |
| } |
| |
| /** |
| * Returns the id of the {@link Device}. |
| * |
| * @return The id of the {@link Device}. |
| */ |
| @NonNull |
| public String getId() { |
| return mId; |
| } |
| |
| /** |
| * Returns the manufacturer of the {@link Device}. |
| * |
| * @return The name of the manufacturer of the {@link Device}. |
| */ |
| @NonNull |
| public String getManufacturer() { |
| return mManufacturer; |
| } |
| |
| /** |
| * Returns all of the {@link Software} configurations of the {@link Device}. |
| * |
| * @return A list of all the {@link Software} configurations. |
| */ |
| @NonNull |
| public List<Software> getAllSoftware() { |
| return mSoftware; |
| } |
| |
| /** |
| * Returns all of the {@link State}s the {@link Device} can be in. |
| * |
| * @return A list of all the {@link State}s. |
| */ |
| @NonNull |
| public List<State> getAllStates() { |
| return mState; |
| } |
| |
| /** |
| * Returns the default {@link Hardware} configuration for the device. This |
| * is really just a shortcut for getting the {@link Hardware} on the default |
| * {@link State} |
| * |
| * @return The default {@link Hardware} for the device. |
| */ |
| @NonNull |
| public Hardware getDefaultHardware() { |
| return mDefaultState.getHardware(); |
| } |
| |
| /** |
| * Returns the {@link Meta} object for the device, which contains meta |
| * information about the device, such as the location of icons. |
| * |
| * @return The {@link Meta} object for the {@link Device}. |
| */ |
| @NonNull |
| public Meta getMeta() { |
| return mMeta; |
| } |
| |
| /** |
| * Returns the default {@link State} of the {@link Device}. |
| * |
| * @return The default {@link State} of the {@link Device}. |
| */ |
| @NonNull |
| public State getDefaultState() { |
| return mDefaultState; |
| } |
| |
| /** |
| * Returns the software configuration for the given API version. |
| * |
| * @param apiVersion |
| * The API version requested. |
| * @return The Software instance for the requested API version or null if |
| * the API version is unsupported for this device. |
| */ |
| @Nullable |
| public Software getSoftware(int apiVersion) { |
| for (Software s : mSoftware) { |
| if (apiVersion >= s.getMinSdkLevel() && apiVersion <= s.getMaxSdkLevel()) { |
| return s; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the state of the device with the given name. |
| * |
| * @param name |
| * The name of the state requested. |
| * @return The State object requested or null if there's no state with the |
| * given name. |
| */ |
| @Nullable |
| public State getState(String name) { |
| for (State s : getAllStates()) { |
| if (s.getName().equals(name)) { |
| return s; |
| } |
| } |
| return null; |
| } |
| |
| @SuppressWarnings("SuspiciousNameCombination") // Deliberately swapping orientations |
| @Nullable |
| public Dimension getScreenSize(@NonNull ScreenOrientation orientation) { |
| Screen screen = getDefaultHardware().getScreen(); |
| if (screen == null) { |
| return null; |
| } |
| |
| // compute width and height to take orientation into account. |
| int x = screen.getXDimension(); |
| int y = screen.getYDimension(); |
| int screenWidth, screenHeight; |
| |
| if (x > y) { |
| if (orientation == ScreenOrientation.LANDSCAPE) { |
| screenWidth = x; |
| screenHeight = y; |
| } |
| else { |
| screenWidth = y; |
| screenHeight = x; |
| } |
| } |
| else { |
| if (orientation == ScreenOrientation.LANDSCAPE) { |
| screenWidth = y; |
| screenHeight = x; |
| } |
| else { |
| screenWidth = x; |
| screenHeight = y; |
| } |
| } |
| |
| return new Dimension(screenWidth, screenHeight); |
| } |
| |
| /** |
| * Returns the optional tag-id of the device. |
| * |
| * @return the optional tag-id of the device. Can be null. |
| */ |
| @Nullable |
| public String getTagId() { |
| return mTagId; |
| } |
| |
| /** |
| * Returns the optional boot.props of the device. |
| * |
| * @return the optional boot.props of the device. Can be null or empty. |
| */ |
| @NonNull |
| public Map<String, String> getBootProps() { |
| return mBootProps; |
| } |
| |
| /** |
| * A convenience method to get if the screen for this device is round. |
| */ |
| public boolean isScreenRound() { |
| return getDefaultHardware().getScreen().getScreenRound() == ScreenRound.ROUND; |
| } |
| |
| /** |
| * A convenience method to get the chin size for this device. |
| */ |
| public int getChinSize() { |
| return getDefaultHardware().getScreen().getChin(); |
| } |
| |
| public static class Builder { |
| private String mName; |
| private String mId; |
| private String mManufacturer; |
| private final List<Software> mSoftware = new ArrayList<Software>(); |
| private final List<State> mState = new ArrayList<State>(); |
| private Meta mMeta; |
| private State mDefaultState; |
| private String mTagId; |
| private final Map<String, String> mBootProps = new TreeMap<String, String>(); |
| |
| public Builder() { } |
| |
| public Builder(Device d) { |
| mTagId = null; |
| mName = d.getDisplayName(); |
| mId = d.getId(); |
| mManufacturer = d.getManufacturer(); |
| for (Software s : d.getAllSoftware()) { |
| mSoftware.add(s.deepCopy()); |
| } |
| for (State s : d.getAllStates()) { |
| mState.add(s.deepCopy()); |
| } |
| mSoftware.addAll(d.getAllSoftware()); |
| mState.addAll(d.getAllStates()); |
| mMeta = d.getMeta(); |
| mDefaultState = d.getDefaultState(); |
| } |
| |
| public void setName(@NonNull String name) { |
| mName = name; |
| } |
| |
| public void setId(@NonNull String id) { |
| mId = id; |
| } |
| |
| public void setTagId(@Nullable String tagId) { |
| mTagId = tagId; |
| } |
| |
| public void addBootProp(@NonNull String propName, @NonNull String propValue) { |
| mBootProps.put(propName, propValue); |
| } |
| |
| public void setManufacturer(@NonNull String manufacturer) { |
| mManufacturer = manufacturer; |
| } |
| |
| public void addSoftware(@NonNull Software sw) { |
| mSoftware.add(sw); |
| } |
| |
| public void addAllSoftware(@NonNull Collection<? extends Software> sw) { |
| mSoftware.addAll(sw); |
| } |
| |
| public void addState(State state) { |
| mState.add(state); |
| } |
| |
| public void addAllState(@NonNull Collection<? extends State> states) { |
| mState.addAll(states); |
| } |
| |
| /** |
| * Removes the first {@link State} with the given name |
| * @param stateName The name of the {@link State} to remove. |
| * @return Whether a {@link State} was removed or not. |
| */ |
| public boolean removeState(@NonNull String stateName) { |
| for (int i = 0; i < mState.size(); i++) { |
| if (stateName != null && stateName.equals(mState.get(i).getName())) { |
| mState.remove(i); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Only for use by the {@link DeviceParser}, so that it can modify the states after they've |
| * been added. |
| */ |
| List<State> getAllStates() { |
| return mState; |
| } |
| |
| public void setMeta(@NonNull Meta meta) { |
| mMeta = meta; |
| } |
| |
| public Device build() { |
| if (mName == null) { |
| throw generateBuildException("Device missing name"); |
| } else if (mManufacturer == null) { |
| throw generateBuildException("Device missing manufacturer"); |
| } else if (mSoftware.size() <= 0) { |
| throw generateBuildException("Device software not configured"); |
| } else if (mState.size() <= 0) { |
| throw generateBuildException("Device states not configured"); |
| } |
| |
| if (mId == null) { |
| mId = mName; |
| } |
| |
| if (mMeta == null) { |
| mMeta = new Meta(); |
| } |
| for (State s : mState) { |
| if (s.isDefaultState()) { |
| mDefaultState = s; |
| break; |
| } |
| } |
| if (mDefaultState == null) { |
| throw generateBuildException("Device missing default state"); |
| } |
| return new Device(this); |
| } |
| |
| private IllegalStateException generateBuildException(String err) { |
| String device = ""; |
| if (mManufacturer != null) { |
| device = mManufacturer + ' '; |
| } |
| if (mName != null) { |
| device += mName; |
| } else { |
| device = "Unknown " + device +"Device"; |
| } |
| |
| return new IllegalStateException("Error building " + device + ": " +err); |
| } |
| } |
| |
| private Device(Builder b) { |
| mName = b.mName; |
| mId = b.mId; |
| mManufacturer = b.mManufacturer; |
| mSoftware = Collections.unmodifiableList(b.mSoftware); |
| mState = Collections.unmodifiableList(b.mState); |
| mMeta = b.mMeta; |
| mDefaultState = b.mDefaultState; |
| mTagId = b.mTagId; |
| mBootProps = Collections.unmodifiableMap(b.mBootProps); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o == this) { |
| return true; |
| } |
| if (!(o instanceof Device)) { |
| return false; |
| } |
| Device d = (Device) o; |
| boolean ok = mName.equals(d.getDisplayName()) |
| && mManufacturer.equals(d.getManufacturer()) |
| && mSoftware.equals(d.getAllSoftware()) |
| && mState.equals(d.getAllStates()) |
| && mMeta.equals(d.getMeta()) |
| && mDefaultState.equals(d.getDefaultState()); |
| if (!ok) { |
| return false; |
| } |
| |
| ok = (mTagId == null && d.mTagId == null) || |
| (mTagId != null && mTagId.equals(d.mTagId)); |
| if (!ok) { |
| return false; |
| } |
| |
| ok = (mBootProps == null && d.mBootProps == null) || |
| (mBootProps != null && mBootProps.equals(d.mBootProps)); |
| return ok; |
| } |
| |
| /** |
| * For *internal* usage only. Must not be serialized to disk. |
| */ |
| @Override |
| public int hashCode() { |
| int hash = 17; |
| hash = 31 * hash + mName.hashCode(); |
| hash = 31 * hash + mManufacturer.hashCode(); |
| hash = 31 * hash + mSoftware.hashCode(); |
| hash = 31 * hash + mState.hashCode(); |
| hash = 31 * hash + mMeta.hashCode(); |
| hash = 31 * hash + mDefaultState.hashCode(); |
| |
| // tag-id and boot-props are optional and should not change a device's hashcode |
| // which did not have them before. |
| if (mTagId != null) { |
| hash = 31 * hash + mTagId.hashCode(); |
| } |
| if (mBootProps != null && !mBootProps.isEmpty()) { |
| hash = 31 * hash + mBootProps.hashCode(); |
| } |
| return hash; |
| } |
| |
| /** toString value suitable for debugging only. */ |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Device [mName="); |
| sb.append(mName); |
| sb.append(", mId="); |
| sb.append(mId); |
| sb.append(", mManufacturer="); |
| sb.append(mManufacturer); |
| sb.append(", mSoftware="); |
| sb.append(mSoftware); |
| sb.append(", mState="); |
| sb.append(mState); |
| sb.append(", mMeta="); |
| sb.append(mMeta); |
| sb.append(", mDefaultState="); |
| sb.append(mDefaultState); |
| sb.append(", mTagId="); |
| sb.append(mTagId); |
| sb.append(", mBootProps="); |
| sb.append(mBootProps); |
| sb.append("]"); |
| return sb.toString(); |
| } |
| |
| private static Pattern PATTERN = Pattern.compile( |
| "(\\d+\\.?\\d*)(?:in|\") (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$ |
| |
| /** |
| * Returns a "sortable" name for the device -- if a device list is sorted |
| * using this sort-aware display name, it will be displayed in an order that |
| * is user friendly with devices using names first sorted alphabetically |
| * followed by all devices that use a numeric screen size sorted by actual |
| * size. |
| * <p/> |
| * Note that although the name results in a proper sort, it is not a name |
| * that you actually want to display to the user. |
| * <p/> |
| * Extracted from DeviceMenuListener. Modified to remove the leading space |
| * insertion as it doesn't render neatly in the avd manager. Instead added |
| * the option to add leading zeroes to make the string names sort properly. |
| * |
| * Replace "'in'" with '"' (e.g. 2.7" QVGA instead of 2.7in QVGA). |
| * Use the same precision for all devices (all but one specify decimals). |
| */ |
| private String getSortableName() { |
| String sortableName = mName; |
| Matcher matcher = PATTERN.matcher(sortableName); |
| if (matcher.matches()) { |
| String size = matcher.group(1); |
| String n = matcher.group(2); |
| int dot = size.indexOf('.'); |
| if (dot == -1) { |
| size = size + ".0"; |
| dot = size.length() - 2; |
| } |
| if (dot < 3) { |
| // Pad to have at least 3 digits before the dot, for sorting |
| // purposes. |
| // We can revisit this once we get devices that are more than |
| // 999 inches wide. |
| size = "000".substring(dot) + size; |
| } |
| sortableName = size + "\" " + n; |
| } |
| |
| return sortableName; |
| } |
| |
| /** |
| * Returns a comparator suitable to sort a device list using a sort-aware display name. |
| * The list is displayed in an order that is user friendly with devices using names |
| * first sorted alphabetically followed by all devices that use a numeric screen size |
| * sorted by actual size. |
| */ |
| public static Comparator<Device> getDisplayComparator() { |
| return new Comparator<Device>() { |
| @Override |
| public int compare(Device d1, Device d2) { |
| String s1 = d1.getSortableName(); |
| String s2 = d2.getSortableName(); |
| if (s1.length() > 1 && s2.length() > 1) { |
| int i1 = Character.isDigit(s1.charAt(0)) ? 1 : 0; |
| int i2 = Character.isDigit(s2.charAt(0)) ? 1 : 0; |
| if (i1 != i2) { |
| return i1 - i2; |
| } |
| } |
| return s1.compareTo(s2); |
| }}; |
| } |
| |
| } |