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