1 /* 2 * Copyright (C) 2014 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.tradefed.device; 18 19 import com.android.ddmlib.IDevice; 20 import com.android.tradefed.device.IManagedTestDevice.DeviceEventResponse; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.util.ConditionPriorityBlockingQueue.IMatcher; 23 24 import java.util.ArrayList; 25 import java.util.ConcurrentModificationException; 26 import java.util.Iterator; 27 import java.util.LinkedList; 28 import java.util.List; 29 import java.util.Set; 30 import java.util.concurrent.locks.ReentrantLock; 31 32 import javax.annotation.concurrent.GuardedBy; 33 34 /** 35 * A thread-safe data structure that holds all devices known to {@link DeviceManager}. 36 * <p/> 37 * Iteration is also thread-safe, but not consistent. A copy of the list is made at iterator 38 * creation time, and that copy is used as the iteration target. If queue is modified during 39 * iteration, a {@link ConcurrentModificationException} will not be thrown, but the iterator 40 * will also not reflect the modified contents. 41 */ 42 class ManagedDeviceList implements Iterable<IManagedTestDevice> { 43 44 /** 45 * A {@link IMatcher} for finding a {@link IManagedTestDevice} that can be allocated. 46 * Will change the device state to ALLOCATED upon finding a successful match. 47 */ 48 private static class AllocationMatcher implements IMatcher<IManagedTestDevice> { 49 private IDeviceSelection mDeviceSelectionMatcher; 50 AllocationMatcher(IDeviceSelection options)51 AllocationMatcher(IDeviceSelection options) { 52 mDeviceSelectionMatcher = options; 53 } 54 55 @Override matches(IManagedTestDevice element)56 public boolean matches(IManagedTestDevice element) { 57 if (mDeviceSelectionMatcher.matches(element.getIDevice())) { 58 DeviceEventResponse r = element.handleAllocationEvent(DeviceEvent.ALLOCATE_REQUEST); 59 return r.stateChanged && r.allocationState == DeviceAllocationState.Allocated; 60 } 61 return false; 62 } 63 } 64 65 private final ReentrantLock mListLock = new ReentrantLock(true); 66 @GuardedBy("mListLock") 67 private List<IManagedTestDevice> mList = new LinkedList<IManagedTestDevice>(); 68 private final IManagedTestDeviceFactory mDeviceFactory; 69 ManagedDeviceList(IManagedTestDeviceFactory d)70 public ManagedDeviceList(IManagedTestDeviceFactory d) { 71 mDeviceFactory = d; 72 } 73 74 /** 75 * {@inheritDoc} 76 */ 77 @Override iterator()78 public Iterator<IManagedTestDevice> iterator() { 79 return getCopy().iterator(); 80 } 81 82 /** 83 * Get a copy of the contents of the queue. 84 */ getCopy()85 List<IManagedTestDevice> getCopy() { 86 mListLock.lock(); 87 try { 88 List<IManagedTestDevice> l = new ArrayList<IManagedTestDevice>(size()); 89 l.addAll(mList); 90 return l; 91 } finally { 92 mListLock.unlock(); 93 } 94 } 95 96 /** 97 * Return the number of elements in the list 98 */ size()99 public int size() { 100 mListLock.lock(); 101 try { 102 return mList.size(); 103 } finally { 104 mListLock.unlock(); 105 } 106 } 107 108 /** 109 * Finds the device with the given serial 110 * 111 * @param serialNumber 112 * @return the {@link IManagedTestDevice} or <code>null</code> if not found 113 */ find(final String serialNumber)114 public IManagedTestDevice find(final String serialNumber) { 115 return find(new IMatcher<IManagedTestDevice>() { 116 @Override 117 public boolean matches(IManagedTestDevice element) { 118 return serialNumber.equals(element.getSerialNumber()); 119 } 120 }); 121 } 122 123 private boolean isValidDeviceSerial(String serial) { 124 return serial.length() > 1 && !serial.contains("?"); 125 } 126 127 /** 128 * Update the {@link TestDevice#getDeviceState()} of devices as appropriate. 129 * 130 * @param serials the devices currently on fastboot 131 */ 132 public void updateFastbootStates(Set<String> serials) { 133 List<IManagedTestDevice> toRemove = new ArrayList<>(); 134 mListLock.lock(); 135 try { 136 for (IManagedTestDevice d : mList) { 137 if (serials.contains(d.getSerialNumber())) { 138 d.setDeviceState(TestDeviceState.FASTBOOT); 139 } else if (d.getDeviceState() == TestDeviceState.FASTBOOT) { 140 // device was previously on fastboot, assume its gone now 141 d.setDeviceState(TestDeviceState.NOT_AVAILABLE); 142 CLog.d("Device %s was in fastboot and not found anymore", d.getSerialNumber()); 143 toRemove.add(d); 144 } 145 } 146 } finally { 147 mListLock.unlock(); 148 } 149 for (IManagedTestDevice d : toRemove) { 150 handleDeviceEvent(d, DeviceEvent.DISCONNECTED); 151 } 152 } 153 154 /** 155 * Attempt to allocate a device from the list 156 * @param options 157 * @return the {@link IManagedTestDevice} that was successfully allocated, null otherwise 158 */ 159 public IManagedTestDevice allocate(IDeviceSelection options) { 160 AllocationMatcher m = new AllocationMatcher(options); 161 // this method is a variant of find, that attempts to find a device matching options 162 // and that can be transitioned to allocated state. 163 // if found, the device will be moved to the back of the list to try to even out 164 // allocations among devices 165 mListLock.lock(); 166 try { 167 Iterator<IManagedTestDevice> iterator = mList.iterator(); 168 while (iterator.hasNext()) { 169 IManagedTestDevice d = iterator.next(); 170 if (m.matches(d)) { 171 iterator.remove(); 172 mList.add(d); 173 return d; 174 } 175 } 176 } finally { 177 mListLock.unlock(); 178 } 179 return null; 180 } 181 182 private IManagedTestDevice find(IMatcher<IManagedTestDevice> m) { 183 mListLock.lock(); 184 try { 185 for (IManagedTestDevice d : mList) { 186 if (m.matches(d)) { 187 return d; 188 } 189 } 190 } finally { 191 mListLock.unlock(); 192 } 193 return null; 194 } 195 196 /** 197 * Remove the contents of this list. 198 * <p/> 199 * Exposed for unit testing 200 */ 201 void clear() { 202 mListLock.lock(); 203 try { 204 mList.clear(); 205 } finally { 206 mListLock.unlock(); 207 } 208 } 209 210 /** 211 * Find the {@link IManagedTestDevice} corresponding to the {@link IDevice}. If it does not 212 * exist, create a new one. 213 * 214 * @param idevice 215 * @return the {@link IManagedTestDevice}. 216 */ 217 public IManagedTestDevice findOrCreate(IDevice idevice) { 218 if (!isValidDeviceSerial(idevice.getSerialNumber())) { 219 return null; 220 } 221 mListLock.lock(); 222 try { 223 IManagedTestDevice d = find(idevice.getSerialNumber()); 224 if (d == null || DeviceAllocationState.Unavailable.equals(d.getAllocationState())) { 225 mList.remove(d); 226 d = mDeviceFactory.createDevice(idevice); 227 mList.add(d); 228 } 229 return d; 230 } finally { 231 mListLock.unlock(); 232 } 233 } 234 235 /** 236 * Directly add a device to the list. Should not be used outside of unit testing. 237 * @param device 238 */ 239 void add(IManagedTestDevice device) { 240 mListLock.lock(); 241 try { 242 mList.add(device); 243 } finally { 244 mListLock.unlock(); 245 } 246 } 247 248 /** 249 * Handle a device event for given device. Will remove device from list if state transitions 250 * to unknown. 251 * <p/> 252 * {@link DeviceManager} should always make calls through this method as opposed to calling 253 * {@link IManagedTestDevice#handleAllocationEvent(DeviceEvent)} directly, to ensure list stays 254 * valid. 255 */ 256 public DeviceEventResponse handleDeviceEvent(IManagedTestDevice d, DeviceEvent event) { 257 DeviceEventResponse r = d.handleAllocationEvent(event); 258 if (r != null && r.allocationState == DeviceAllocationState.Unknown) { 259 remove(d); 260 } 261 return r; 262 } 263 264 private void remove(IManagedTestDevice d) { 265 mListLock.lock(); 266 try { 267 mList.remove(d); 268 } finally { 269 mListLock.unlock(); 270 } 271 } 272 } 273