| /* |
| * 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.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.prefs.AndroidLocation; |
| import com.android.prefs.AndroidLocation.AndroidLocationException; |
| import com.android.resources.Keyboard; |
| import com.android.resources.KeyboardState; |
| import com.android.resources.Navigation; |
| import com.android.sdklib.internal.avd.AvdManager; |
| import com.android.sdklib.internal.avd.HardwareProperties; |
| import com.android.sdklib.io.FileOp; |
| import com.android.sdklib.repository.PkgProps; |
| import com.android.utils.ILogger; |
| import com.google.common.base.Charsets; |
| import com.google.common.collect.HashBasedTable; |
| import com.google.common.collect.Table; |
| import com.google.common.hash.HashFunction; |
| import com.google.common.hash.Hasher; |
| import com.google.common.hash.Hashing; |
| import com.google.common.io.Closeables; |
| |
| import org.xml.sax.SAXException; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactoryConfigurationError; |
| |
| /** |
| * Manager class for interacting with {@link Device}s within the SDK |
| */ |
| public class DeviceManager { |
| |
| private static final String DEVICE_PROFILES_PROP = "DeviceProfiles"; |
| private static final Pattern PATH_PROPERTY_PATTERN = |
| Pattern.compile('^' + PkgProps.EXTRA_PATH + '=' + DEVICE_PROFILES_PROP + '$'); |
| private ILogger mLog; |
| private Table<String, String, Device> mVendorDevices; |
| private Table<String, String, Device> mSysImgDevices; |
| private Table<String, String, Device> mUserDevices; |
| private Table<String, String, Device> mDefaultDevices; |
| private final Object mLock = new Object(); |
| private final List<DevicesChangedListener> sListeners = new ArrayList<DevicesChangedListener>(); |
| private final String mOsSdkPath; |
| |
| public enum DeviceFilter { |
| /** getDevices() flag to list default devices from the bundled devices.xml definitions. */ |
| DEFAULT, |
| /** getDevices() flag to list user devices saved in the .android home folder. */ |
| USER, |
| /** getDevices() flag to list vendor devices -- the bundled nexus.xml devices |
| * as well as all those coming from extra packages. */ |
| VENDOR, |
| /** getDevices() flag to list devices from system-images/platform-N/tag/abi/devices.xml */ |
| SYSTEM_IMAGES, |
| } |
| |
| /** getDevices() flag to list all devices. */ |
| public static final EnumSet<DeviceFilter> ALL_DEVICES = EnumSet.allOf(DeviceFilter.class); |
| |
| public enum DeviceStatus { |
| /** |
| * The device exists unchanged from the given configuration |
| */ |
| EXISTS, |
| /** |
| * A device exists with the given name and manufacturer, but has a different configuration |
| */ |
| CHANGED, |
| /** |
| * There is no device with the given name and manufacturer |
| */ |
| MISSING |
| } |
| |
| /** |
| * Creates a new instance of DeviceManager. |
| * |
| * @param sdkLocation Path to the current SDK. If null or invalid, vendor and system images |
| * devices are ignored. |
| * @param log SDK logger instance. Should be non-null. |
| */ |
| public static DeviceManager createInstance(@Nullable File sdkLocation, @NonNull ILogger log) { |
| // TODO consider using a cache and reusing the same instance of the device manager |
| // for the same manager/log combo. |
| return new DeviceManager(sdkLocation == null ? null : sdkLocation.getPath(), log); |
| } |
| |
| /** |
| * Creates a new instance of DeviceManager. |
| * |
| * @param osSdkPath Path to the current SDK. If null or invalid, vendor devices are ignored. |
| * @param log SDK logger instance. Should be non-null. |
| */ |
| private DeviceManager(@Nullable String osSdkPath, @NonNull ILogger log) { |
| mOsSdkPath = osSdkPath; |
| mLog = log; |
| } |
| |
| /** |
| * Interface implemented by objects which want to know when changes occur to the {@link Device} |
| * lists. |
| */ |
| public interface DevicesChangedListener { |
| /** |
| * Called after one of the {@link Device} lists has been updated. |
| */ |
| void onDevicesChanged(); |
| } |
| |
| /** |
| * Register a listener to be notified when the device lists are modified. |
| * |
| * @param listener The listener to add. Ignored if already registered. |
| */ |
| public void registerListener(@NonNull DevicesChangedListener listener) { |
| synchronized (sListeners) { |
| if (!sListeners.contains(listener)) { |
| sListeners.add(listener); |
| } |
| } |
| } |
| |
| /** |
| * Removes a listener from the notification list such that it will no longer receive |
| * notifications when modifications to the {@link Device} list occur. |
| * |
| * @param listener The listener to remove. |
| */ |
| public boolean unregisterListener(@NonNull DevicesChangedListener listener) { |
| synchronized (sListeners) { |
| return sListeners.remove(listener); |
| } |
| } |
| |
| @NonNull |
| public DeviceStatus getDeviceStatus(@NonNull String name, @NonNull String manufacturer) { |
| Device d = getDevice(name, manufacturer); |
| if (d == null) { |
| return DeviceStatus.MISSING; |
| } |
| |
| return DeviceStatus.EXISTS; |
| } |
| |
| @Nullable |
| public Device getDevice(@NonNull String id, @NonNull String manufacturer) { |
| initDevicesLists(); |
| Device d = mUserDevices.get(id, manufacturer); |
| if (d != null) { |
| return d; |
| } |
| d = mSysImgDevices.get(id, manufacturer); |
| if (d != null) { |
| return d; |
| } |
| d = mDefaultDevices.get(id, manufacturer); |
| if (d != null) { |
| return d; |
| } |
| d = mVendorDevices.get(id, manufacturer); |
| return d; |
| } |
| |
| @Nullable |
| private Device getDeviceImpl(@NonNull Iterable<Device> devicesList, |
| @NonNull String id, |
| @NonNull String manufacturer) { |
| for (Device d : devicesList) { |
| if (d.getId().equals(id) && d.getManufacturer().equals(manufacturer)) { |
| return d; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the known {@link Device} list. |
| * |
| * @param deviceFilter One of the {@link DeviceFilter} constants. |
| * @return A copy of the list of {@link Device}s. Can be empty but not null. |
| */ |
| @NonNull |
| public Collection<Device> getDevices(@NonNull DeviceFilter deviceFilter) { |
| return getDevices(EnumSet.of(deviceFilter)); |
| } |
| |
| /** |
| * Returns the known {@link Device} list. |
| * |
| * @param deviceFilter A combination of the {@link DeviceFilter} constants |
| * or the constant {@link DeviceManager#ALL_DEVICES}. |
| * @return A copy of the list of {@link Device}s. Can be empty but not null. |
| */ |
| @NonNull |
| public Collection<Device> getDevices(@NonNull EnumSet<DeviceFilter> deviceFilter) { |
| initDevicesLists(); |
| Table<String, String, Device> devices = HashBasedTable.create(); |
| if (mUserDevices != null && (deviceFilter.contains(DeviceFilter.USER))) { |
| devices.putAll(mUserDevices); |
| } |
| if (mDefaultDevices != null && (deviceFilter.contains(DeviceFilter.DEFAULT))) { |
| devices.putAll(mDefaultDevices); |
| } |
| if (mVendorDevices != null && (deviceFilter.contains(DeviceFilter.VENDOR))) { |
| devices.putAll(mVendorDevices); |
| } |
| if (mSysImgDevices != null && (deviceFilter.contains(DeviceFilter.SYSTEM_IMAGES))) { |
| devices.putAll(mSysImgDevices); |
| } |
| return Collections.unmodifiableCollection(devices.values()); |
| } |
| |
| private void initDevicesLists() { |
| boolean changed = initDefaultDevices(); |
| changed |= initVendorDevices(); |
| changed |= initSysImgDevices(); |
| changed |= initUserDevices(); |
| if (changed) { |
| notifyListeners(); |
| } |
| } |
| |
| /** |
| * Initializes the {@link Device}s packaged with the SDK. |
| * @return True if the list has changed. |
| */ |
| private boolean initDefaultDevices() { |
| synchronized (mLock) { |
| if (mDefaultDevices != null) { |
| return false; |
| } |
| InputStream stream = DeviceManager.class |
| .getResourceAsStream(SdkConstants.FN_DEVICES_XML); |
| try { |
| assert stream != null : SdkConstants.FN_DEVICES_XML + " not bundled in sdklib."; |
| mDefaultDevices = DeviceParser.parse(stream); |
| return true; |
| } catch (IllegalStateException e) { |
| // The device builders can throw IllegalStateExceptions if |
| // build gets called before everything is properly setup |
| mLog.error(e, null); |
| mDefaultDevices = HashBasedTable.create(); |
| } catch (Exception e) { |
| mLog.error(e, "Error reading default devices"); |
| mDefaultDevices = HashBasedTable.create(); |
| } finally { |
| Closeables.closeQuietly(stream); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Initializes all vendor-provided {@link Device}s: the bundled nexus.xml devices |
| * as well as all those coming from extra packages. |
| * @return True if the list has changed. |
| */ |
| private boolean initVendorDevices() { |
| synchronized (mLock) { |
| if (mVendorDevices != null) { |
| return false; |
| } |
| |
| mVendorDevices = HashBasedTable.create(); |
| |
| // Load builtin devices |
| InputStream stream = DeviceManager.class.getResourceAsStream("nexus.xml"); |
| try { |
| mVendorDevices.putAll(DeviceParser.parse(stream)); |
| } catch (Exception e) { |
| mLog.error(e, "Could not load nexus devices"); |
| } finally { |
| Closeables.closeQuietly(stream); |
| } |
| |
| stream = DeviceManager.class.getResourceAsStream("wear.xml"); |
| try { |
| mVendorDevices.putAll(DeviceParser.parse(stream)); |
| } catch (Exception e) { |
| mLog.error(e, "Could not load wear devices"); |
| } finally { |
| Closeables.closeQuietly(stream); |
| } |
| |
| stream = DeviceManager.class.getResourceAsStream("tv.xml"); |
| try { |
| mVendorDevices.putAll(DeviceParser.parse(stream)); |
| } catch (Exception e) { |
| mLog.error(e, "Could not load tv devices"); |
| } finally { |
| Closeables.closeQuietly(stream); |
| } |
| |
| if (mOsSdkPath != null) { |
| // Load devices from vendor extras |
| File extrasFolder = new File(mOsSdkPath, SdkConstants.FD_EXTRAS); |
| List<File> deviceDirs = getExtraDirs(extrasFolder); |
| for (File deviceDir : deviceDirs) { |
| File deviceXml = new File(deviceDir, SdkConstants.FN_DEVICES_XML); |
| if (deviceXml.isFile()) { |
| mVendorDevices.putAll(loadDevices(deviceXml)); |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Initializes all system-image provided {@link Device}s. |
| * @return True if the list has changed. |
| */ |
| private boolean initSysImgDevices() { |
| synchronized (mLock) { |
| if (mSysImgDevices != null) { |
| return false; |
| } |
| mSysImgDevices = HashBasedTable.create(); |
| |
| if (mOsSdkPath == null) { |
| return false; |
| } |
| |
| // Load devices from tagged system-images |
| // Path pattern is /sdk/system-images/<platform-N>/<tag>/<abi>/devices.xml |
| |
| FileOp fop = new FileOp(); |
| File sysImgFolder = new File(mOsSdkPath, SdkConstants.FD_SYSTEM_IMAGES); |
| |
| for (File platformFolder : fop.listFiles(sysImgFolder)) { |
| if (!fop.isDirectory(platformFolder)) { |
| continue; |
| } |
| |
| for (File tagFolder : fop.listFiles(platformFolder)) { |
| if (!fop.isDirectory(tagFolder)) { |
| continue; |
| } |
| |
| for (File abiFolder : fop.listFiles(tagFolder)) { |
| if (!fop.isDirectory(abiFolder)) { |
| continue; |
| } |
| |
| File deviceXml = new File(abiFolder, SdkConstants.FN_DEVICES_XML); |
| if (fop.isFile(deviceXml)) { |
| mSysImgDevices.putAll(loadDevices(deviceXml)); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * Initializes all user-created {@link Device}s |
| * @return True if the list has changed. |
| */ |
| private boolean initUserDevices() { |
| synchronized (mLock) { |
| if (mUserDevices != null) { |
| return false; |
| } |
| // User devices should be saved out to |
| // $HOME/.android/devices.xml |
| mUserDevices = HashBasedTable.create(); |
| File userDevicesFile = null; |
| try { |
| userDevicesFile = new File( |
| AndroidLocation.getFolder(), |
| SdkConstants.FN_DEVICES_XML); |
| if (userDevicesFile.exists()) { |
| mUserDevices.putAll(DeviceParser.parse(userDevicesFile)); |
| return true; |
| } |
| } catch (AndroidLocationException e) { |
| mLog.warning("Couldn't load user devices: %1$s", e.getMessage()); |
| } catch (SAXException e) { |
| // Probably an old config file which we don't want to overwrite. |
| if (userDevicesFile != null) { |
| String base = userDevicesFile.getAbsoluteFile() + ".old"; |
| File renamedConfig = new File(base); |
| int i = 0; |
| while (renamedConfig.exists()) { |
| renamedConfig = new File(base + '.' + (i++)); |
| } |
| mLog.error(e, "Error parsing %1$s, backing up to %2$s", |
| userDevicesFile.getAbsolutePath(), |
| renamedConfig.getAbsolutePath()); |
| userDevicesFile.renameTo(renamedConfig); |
| } |
| } catch (ParserConfigurationException e) { |
| mLog.error(e, "Error parsing %1$s", |
| userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath()); |
| } catch (IOException e) { |
| mLog.error(e, "Error parsing %1$s", |
| userDevicesFile == null ? "(null)" : userDevicesFile.getAbsolutePath()); |
| } |
| } |
| return false; |
| } |
| |
| public void addUserDevice(@NonNull Device d) { |
| boolean changed = false; |
| synchronized (mLock) { |
| if (mUserDevices == null) { |
| initUserDevices(); |
| assert mUserDevices != null; |
| } |
| if (mUserDevices != null) { |
| mUserDevices.put(d.getId(), d.getManufacturer(), d); |
| } |
| changed = true; |
| } |
| if (changed) { |
| notifyListeners(); |
| } |
| } |
| |
| public void removeUserDevice(@NonNull Device d) { |
| synchronized (mLock) { |
| if (mUserDevices == null) { |
| initUserDevices(); |
| assert mUserDevices != null; |
| } |
| if (mUserDevices != null) { |
| if (mUserDevices.contains(d.getId(), d.getManufacturer())) { |
| mUserDevices.remove(d.getId(), d.getManufacturer()); |
| notifyListeners(); |
| } |
| } |
| } |
| } |
| |
| public void replaceUserDevice(@NonNull Device d) { |
| synchronized (mLock) { |
| if (mUserDevices == null) { |
| initUserDevices(); |
| } |
| removeUserDevice(d); |
| addUserDevice(d); |
| } |
| } |
| |
| /** |
| * Saves out the user devices to {@link SdkConstants#FN_DEVICES_XML} in |
| * {@link AndroidLocation#getFolder()}. |
| */ |
| public void saveUserDevices() { |
| if (mUserDevices == null) { |
| return; |
| } |
| |
| File userDevicesFile = null; |
| try { |
| userDevicesFile = new File(AndroidLocation.getFolder(), |
| SdkConstants.FN_DEVICES_XML); |
| } catch (AndroidLocationException e) { |
| mLog.warning("Couldn't find user directory: %1$s", e.getMessage()); |
| return; |
| } |
| |
| if (mUserDevices.isEmpty()) { |
| userDevicesFile.delete(); |
| return; |
| } |
| |
| synchronized (mLock) { |
| if (!mUserDevices.isEmpty()) { |
| try { |
| DeviceWriter.writeToXml(new FileOutputStream(userDevicesFile), mUserDevices.values()); |
| } catch (FileNotFoundException e) { |
| mLog.warning("Couldn't open file: %1$s", e.getMessage()); |
| } catch (ParserConfigurationException e) { |
| mLog.warning("Error writing file: %1$s", e.getMessage()); |
| } catch (TransformerFactoryConfigurationError e) { |
| mLog.warning("Error writing file: %1$s", e.getMessage()); |
| } catch (TransformerException e) { |
| mLog.warning("Error writing file: %1$s", e.getMessage()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns hardware properties (defined in hardware.ini) as a {@link Map}. |
| * |
| * @param s The {@link State} from which to derive the hardware properties. |
| * @return A {@link Map} of hardware properties. |
| */ |
| @NonNull |
| public static Map<String, String> getHardwareProperties(@NonNull State s) { |
| Hardware hw = s.getHardware(); |
| Map<String, String> props = new HashMap<String, String>(); |
| props.put(HardwareProperties.HW_MAINKEYS, |
| getBooleanVal(hw.getButtonType().equals(ButtonType.HARD))); |
| props.put(HardwareProperties.HW_TRACKBALL, |
| getBooleanVal(hw.getNav().equals(Navigation.TRACKBALL))); |
| props.put(HardwareProperties.HW_KEYBOARD, |
| getBooleanVal(hw.getKeyboard().equals(Keyboard.QWERTY))); |
| props.put(HardwareProperties.HW_DPAD, |
| getBooleanVal(hw.getNav().equals(Navigation.DPAD))); |
| |
| Set<Sensor> sensors = hw.getSensors(); |
| props.put(HardwareProperties.HW_GPS, getBooleanVal(sensors.contains(Sensor.GPS))); |
| props.put(HardwareProperties.HW_BATTERY, |
| getBooleanVal(hw.getChargeType().equals(PowerType.BATTERY))); |
| props.put(HardwareProperties.HW_ACCELEROMETER, |
| getBooleanVal(sensors.contains(Sensor.ACCELEROMETER))); |
| props.put(HardwareProperties.HW_ORIENTATION_SENSOR, |
| getBooleanVal(sensors.contains(Sensor.GYROSCOPE))); |
| props.put(HardwareProperties.HW_AUDIO_INPUT, getBooleanVal(hw.hasMic())); |
| props.put(HardwareProperties.HW_SDCARD, getBooleanVal(!hw.getRemovableStorage().isEmpty())); |
| props.put(HardwareProperties.HW_LCD_DENSITY, |
| Integer.toString(hw.getScreen().getPixelDensity().getDpiValue())); |
| props.put(HardwareProperties.HW_PROXIMITY_SENSOR, |
| getBooleanVal(sensors.contains(Sensor.PROXIMITY_SENSOR))); |
| return props; |
| } |
| |
| /** |
| * Returns the hardware properties defined in |
| * {@link AvdManager#HARDWARE_INI} as a {@link Map}. |
| * |
| * This is intended to be dumped in the config.ini and already contains |
| * the device name, manufacturer and device hash. |
| * |
| * @param d The {@link Device} from which to derive the hardware properties. |
| * @return A {@link Map} of hardware properties. |
| */ |
| @NonNull |
| public static Map<String, String> getHardwareProperties(@NonNull Device d) { |
| Map<String, String> props = getHardwareProperties(d.getDefaultState()); |
| for (State s : d.getAllStates()) { |
| if (s.getKeyState().equals(KeyboardState.HIDDEN)) { |
| props.put("hw.keyboard.lid", getBooleanVal(true)); |
| } |
| } |
| |
| HashFunction md5 = Hashing.md5(); |
| Hasher hasher = md5.newHasher(); |
| |
| ArrayList<String> keys = new ArrayList<String>(props.keySet()); |
| Collections.sort(keys); |
| for (String key : keys) { |
| if (key != null) { |
| hasher.putString(key, Charsets.UTF_8); |
| String value = props.get(key); |
| hasher.putString(value == null ? "null" : value, Charsets.UTF_8); |
| } |
| } |
| // store the hash method for potential future compatibility |
| String hash = "MD5:" + hasher.hash().toString(); |
| props.put(AvdManager.AVD_INI_DEVICE_HASH_V2, hash); |
| props.remove(AvdManager.AVD_INI_DEVICE_HASH_V1); |
| |
| props.put(AvdManager.AVD_INI_DEVICE_NAME, d.getId()); |
| props.put(AvdManager.AVD_INI_DEVICE_MANUFACTURER, d.getManufacturer()); |
| return props; |
| } |
| |
| /** |
| * Checks whether the the hardware props have changed. |
| * If the hash is the same, returns null for success. |
| * If the hash is not the same or there's not enough information to indicate it's |
| * the same (e.g. if in the future we change the digest method), simply return the |
| * new hash, indicating it would be best to update it. |
| * |
| * @param d The device. |
| * @param hashV2 The previous saved AvdManager.AVD_INI_DEVICE_HASH_V2 property. |
| * @return Null if the same, otherwise returns the new and different hash. |
| */ |
| @Nullable |
| public static String hasHardwarePropHashChanged(@NonNull Device d, @NonNull String hashV2) { |
| Map<String, String> props = getHardwareProperties(d); |
| String newHash = props.get(AvdManager.AVD_INI_DEVICE_HASH_V2); |
| |
| // Implementation detail: don't just return the hash and let the caller decide whether |
| // the hash is the same. That's because the hash contains the digest method so if in |
| // the future we decide to change it, we could potentially recompute the hash here |
| // using an older digest method here and still determine its validity, whereas the |
| // caller cannot determine that. |
| |
| if (newHash != null && newHash.equals(hashV2)) { |
| return null; |
| } |
| return newHash; |
| } |
| |
| |
| /** |
| * Takes a boolean and returns the appropriate value for |
| * {@link HardwareProperties} |
| * |
| * @param bool The boolean value to turn into the appropriate |
| * {@link HardwareProperties} value. |
| * @return {@code HardwareProperties#BOOLEAN_YES} if true, |
| * {@code HardwareProperties#BOOLEAN_NO} otherwise. |
| */ |
| private static String getBooleanVal(boolean bool) { |
| if (bool) { |
| return HardwareProperties.BOOLEAN_YES; |
| } |
| return HardwareProperties.BOOLEAN_NO; |
| } |
| |
| @NonNull |
| private Table<String, String, Device> loadDevices(@NonNull File deviceXml) { |
| try { |
| return DeviceParser.parse(deviceXml); |
| } catch (SAXException e) { |
| mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath()); |
| } catch (ParserConfigurationException e) { |
| mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath()); |
| } catch (IOException e) { |
| mLog.error(e, "Error reading %1$s", deviceXml.getAbsolutePath()); |
| } catch (AssertionError e) { |
| mLog.error(e, "Error parsing %1$s", deviceXml.getAbsolutePath()); |
| } catch (IllegalStateException e) { |
| // The device builders can throw IllegalStateExceptions if |
| // build gets called before everything is properly setup |
| mLog.error(e, null); |
| } |
| return HashBasedTable.create(); |
| } |
| |
| private void notifyListeners() { |
| synchronized (sListeners) { |
| for (DevicesChangedListener listener : sListeners) { |
| listener.onDevicesChanged(); |
| } |
| } |
| } |
| |
| /* Returns all of DeviceProfiles in the extras/ folder */ |
| @NonNull |
| private List<File> getExtraDirs(@NonNull File extrasFolder) { |
| List<File> extraDirs = new ArrayList<File>(); |
| // All OEM provided device profiles are in |
| // $SDK/extras/$VENDOR/$ITEM/devices.xml |
| if (extrasFolder != null && extrasFolder.isDirectory()) { |
| for (File vendor : extrasFolder.listFiles()) { |
| if (vendor.isDirectory()) { |
| for (File item : vendor.listFiles()) { |
| if (item.isDirectory() && isDevicesExtra(item)) { |
| extraDirs.add(item); |
| } |
| } |
| } |
| } |
| } |
| |
| return extraDirs; |
| } |
| |
| /* |
| * Returns whether a specific folder for a specific vendor is a |
| * DeviceProfiles folder |
| */ |
| private boolean isDevicesExtra(@NonNull File item) { |
| File properties = new File(item, SdkConstants.FN_SOURCE_PROP); |
| try { |
| BufferedReader propertiesReader = new BufferedReader(new FileReader(properties)); |
| try { |
| String line; |
| while ((line = propertiesReader.readLine()) != null) { |
| Matcher m = PATH_PROPERTY_PATTERN.matcher(line); |
| if (m.matches()) { |
| return true; |
| } |
| } |
| } finally { |
| propertiesReader.close(); |
| } |
| } catch (IOException ignore) { |
| } |
| return false; |
| } |
| } |