1 /*
2  * Copyright (C) 2016 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 package com.android.tradefed.device;
17 
18 import com.android.annotations.VisibleForTesting;
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.IDevice;
21 import com.android.ddmlib.IDevice.DeviceState;
22 import com.android.ddmlib.ShellCommandUnresponsiveException;
23 import com.android.ddmlib.TimeoutException;
24 import com.android.tradefed.device.DeviceManager.FastbootDevice;
25 import com.android.tradefed.device.cloud.ManagedRemoteDevice;
26 import com.android.tradefed.device.cloud.NestedDeviceStateMonitor;
27 import com.android.tradefed.device.cloud.NestedRemoteDevice;
28 import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
29 import com.android.tradefed.device.cloud.VmRemoteDevice;
30 import com.android.tradefed.log.LogUtil.CLog;
31 import com.android.tradefed.util.IRunUtil;
32 import com.android.tradefed.util.RunUtil;
33 import com.android.tradefed.util.SystemUtil;
34 
35 import java.io.IOException;
36 import java.util.concurrent.TimeUnit;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 
40 /**
41  * Factory to create the different kind of devices that can be monitored by Tf
42  */
43 public class ManagedTestDeviceFactory implements IManagedTestDeviceFactory {
44 
45     public static final String IPADDRESS_PATTERN =
46             "((^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
47                     + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
48                     + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
49                     + "([01]?\\d\\d?|2[0-4]\\d|25[0-5]))|(localhost)){1}";
50 
51     protected boolean mFastbootEnabled;
52     protected IDeviceManager mDeviceManager;
53     protected IDeviceMonitor mAllocationMonitor;
54     protected static final String CHECK_PM_CMD = "ls %s";
55     protected static final String EXPECTED_RES = "/system/bin/pm";
56     protected static final String EXPECTED_ERROR = "No such file or directory";
57     protected static final long FRAMEWORK_CHECK_SLEEP_MS = 500;
58     protected static final int FRAMEWORK_CHECK_MAX_RETRY = 3;
59 
ManagedTestDeviceFactory(boolean fastbootEnabled, IDeviceManager deviceManager, IDeviceMonitor allocationMonitor)60     public ManagedTestDeviceFactory(boolean fastbootEnabled, IDeviceManager deviceManager,
61             IDeviceMonitor allocationMonitor) {
62         mFastbootEnabled = fastbootEnabled;
63         mDeviceManager = deviceManager;
64         mAllocationMonitor = allocationMonitor;
65     }
66 
67     /**
68      * {@inheritDoc}
69      */
70     @Override
createDevice(IDevice idevice)71     public IManagedTestDevice createDevice(IDevice idevice) {
72         IManagedTestDevice testDevice = null;
73         if (idevice instanceof VmRemoteDevice) {
74             testDevice =
75                     new ManagedRemoteDevice(
76                             idevice,
77                             new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
78                             mAllocationMonitor);
79             testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
80         } else if (idevice instanceof RemoteAvdIDevice) {
81             testDevice =
82                     new RemoteAndroidVirtualDevice(
83                             idevice,
84                             new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
85                             mAllocationMonitor);
86             testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
87         } else if (idevice instanceof StubLocalAndroidVirtualDevice) {
88             // Virtual device to be launched by TradeFed locally.
89             testDevice =
90                     new LocalAndroidVirtualDevice(
91                             idevice,
92                             new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
93                             mAllocationMonitor);
94         } else if (idevice instanceof TcpDevice) {
95             // Special device for Tcp device for custom handling.
96             testDevice = new RemoteAndroidDevice(idevice,
97                     new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
98                     mAllocationMonitor);
99             testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
100         } else if (isTcpDeviceSerial(idevice.getSerialNumber())) {
101             if (isRemoteEnvironment()) {
102                 // If we are in a remote environment, treat the device as such
103                 testDevice =
104                         new NestedRemoteDevice(
105                                 idevice,
106                                 new NestedDeviceStateMonitor(
107                                         mDeviceManager, idevice, mFastbootEnabled),
108                                 mAllocationMonitor);
109             } else {
110                 // Handle device connected via 'adb connect'
111                 testDevice =
112                         new RemoteAndroidDevice(
113                                 idevice,
114                                 new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
115                                 mAllocationMonitor);
116                 testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
117             }
118         } else if (!checkFrameworkSupport(idevice)) {
119             // Iot device instance tier 1 (no framework support)
120             testDevice =
121                     new NativeDevice(
122                             idevice,
123                             new NativeDeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
124                             mAllocationMonitor);
125         } else {
126             // Default to-go device is Android full stack device.
127             testDevice = new TestDevice(idevice,
128                     new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
129                     mAllocationMonitor);
130         }
131 
132         if (idevice instanceof FastbootDevice) {
133             if (((FastbootDevice) idevice).isFastbootD()) {
134                 testDevice.setDeviceState(TestDeviceState.FASTBOOTD);
135             } else {
136                 testDevice.setDeviceState(TestDeviceState.FASTBOOT);
137             }
138         } else if (idevice instanceof StubDevice) {
139             testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
140         }
141         testDevice.setFastbootEnabled(mFastbootEnabled);
142         testDevice.setFastbootPath(mDeviceManager.getFastbootPath());
143         return testDevice;
144     }
145 
146     /**
147      * Helper that return true if device has framework support.
148      */
checkFrameworkSupport(IDevice idevice)149     protected boolean checkFrameworkSupport(IDevice idevice) {
150         if (idevice instanceof StubDevice) {
151             // Assume stub device should go to the default full framework support for
152             // backward compatibility
153             return true;
154         }
155         final long timeout = 60 * 1000;
156         try {
157             for (int i = 0; i < FRAMEWORK_CHECK_MAX_RETRY; i++) {
158                 CollectingOutputReceiver receiver = createOutputReceiver();
159                 if (!DeviceState.ONLINE.equals(idevice.getState())) {
160                     // Device will be 'unavailable' and recreated in DeviceManager so no need to
161                     // check.
162                     CLog.w("Device state is not Online, assuming Framework support for now.");
163                     return true;
164                 }
165                 String cmd = String.format(CHECK_PM_CMD, EXPECTED_RES);
166                 idevice.executeShellCommand(cmd, receiver, timeout, TimeUnit.MILLISECONDS);
167                 String output = receiver.getOutput().trim();
168                 // It can only be one of the expected output or an exception if device offline
169                 // otherwise we retry
170                 if (EXPECTED_RES.equals(output)) {
171                     return true;
172                 }
173                 if (output.contains(EXPECTED_ERROR)) {
174                     CLog.i("No support for Framework, creating a native device. "
175                             + "output: %s", receiver.getOutput());
176                     return false;
177                 }
178                 getRunUtil().sleep(FRAMEWORK_CHECK_SLEEP_MS);
179             }
180             CLog.w("Could not determine the framework support for '%s' after retries, assuming "
181                     + "framework support.", idevice.getSerialNumber());
182         } catch (TimeoutException | AdbCommandRejectedException | ShellCommandUnresponsiveException
183                 | IOException e) {
184             CLog.w("Exception during checkFrameworkSupport, assuming True: '%s' with device: %s",
185                     e.getMessage(), idevice.getSerialNumber());
186             CLog.e(e);
187         }
188         // We default to support for framework to get same behavior as before.
189         return true;
190     }
191 
192     /** Return the default {@link IRunUtil} instance. */
193     @VisibleForTesting
getRunUtil()194     protected IRunUtil getRunUtil() {
195         return RunUtil.getDefault();
196     }
197 
198     /**
199      * Return true if we are currently running in a remote environment. This will alter the device
200      * behavior.
201      */
202     @VisibleForTesting
isRemoteEnvironment()203     protected boolean isRemoteEnvironment() {
204         return SystemUtil.isRemoteEnvironment();
205     }
206 
207     /** Create a {@link CollectingOutputReceiver}. */
208     @VisibleForTesting
createOutputReceiver()209     protected CollectingOutputReceiver createOutputReceiver() {
210         return new CollectingOutputReceiver();
211     }
212 
213     /**
214      * {@inheritDoc}
215      */
216     @Override
setFastbootEnabled(boolean enable)217     public void setFastbootEnabled(boolean enable) {
218         mFastbootEnabled = enable;
219     }
220 
221     /**
222      * Helper to device if it's a serial from a remotely connected device.
223      * serial format of tcp device is <ip or locahost>:<port>
224      */
isTcpDeviceSerial(String serial)225     protected boolean isTcpDeviceSerial(String serial) {
226         final String remotePattern = IPADDRESS_PATTERN + "(:)([0-9]{2,5})(\\b)";
227         Pattern pattern = Pattern.compile(remotePattern);
228         Matcher match = pattern.matcher(serial.trim());
229         if (match.find()) {
230             return true;
231         }
232         return false;
233     }
234 }
235