blob: 72de7b58b0a74dee1263353cfa5c220ab03c41d6 [file] [log] [blame]
Andreas Gampe554d7ee2015-09-15 08:57:12 -07001/*
2 * Copyright (C) 2015 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
17package com.android.preload;
18
19import com.android.ddmlib.AndroidDebugBridge;
20import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
21import com.android.preload.classdataretrieval.hprof.Hprof;
22import com.android.ddmlib.DdmPreferences;
23import com.android.ddmlib.IDevice;
24import com.android.ddmlib.IShellOutputReceiver;
25
26import java.util.Date;
27import java.util.concurrent.Future;
28import java.util.concurrent.TimeUnit;
29
30/**
31 * Helper class for some device routines.
32 */
33public class DeviceUtils {
34
35 public static void init(int debugPort) {
36 DdmPreferences.setSelectedDebugPort(debugPort);
37
38 Hprof.init();
39
40 AndroidDebugBridge.init(true);
41
42 AndroidDebugBridge.createBridge();
43 }
44
45 /**
46 * Run a command in the shell on the device.
47 */
48 public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) {
49 doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit);
50 }
51
52 /**
53 * Run a command in the shell on the device. Collects and returns the console output.
54 */
55 public static String doShellReturnString(IDevice device, String cmdline, long timeout,
56 TimeUnit unit) {
57 CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver();
58 doShell(device, cmdline, rec, timeout, unit);
59 return rec.toString();
60 }
61
62 /**
63 * Run a command in the shell on the device, directing all output to the given receiver.
64 */
65 public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver,
66 long timeout, TimeUnit unit) {
67 try {
68 device.executeShellCommand(cmdline, receiver, timeout, unit);
69 } catch (Exception e) {
70 e.printStackTrace();
71 }
72 }
73
74 /**
75 * Run am start on the device.
76 */
77 public static void doAMStart(IDevice device, String name, String activity) {
78 doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS);
79 }
80
81 /**
82 * Find the device with the given serial. Give up after the given timeout (in milliseconds).
83 */
84 public static IDevice findDevice(String serial, int timeout) {
85 WaitForDevice wfd = new WaitForDevice(serial, timeout);
86 return wfd.get();
87 }
88
89 /**
90 * Get all devices ddms knows about. Wait at most for the given timeout.
91 */
92 public static IDevice[] findDevices(int timeout) {
93 WaitForDevice wfd = new WaitForDevice(null, timeout);
94 wfd.get();
95 return AndroidDebugBridge.getBridge().getDevices();
96 }
97
98 /**
99 * Return the build type of the given device. This is the value of the "ro.build.type"
100 * system property.
101 */
102 public static String getBuildType(IDevice device) {
103 try {
104 Future<String> buildType = device.getSystemProperty("ro.build.type");
105 return buildType.get(500, TimeUnit.MILLISECONDS);
106 } catch (Exception e) {
107 }
108 return null;
109 }
110
111 /**
112 * Check whether the given device has a pre-optimized boot image. More precisely, checks
113 * whether /system/framework/ * /boot.art exists.
114 */
115 public static boolean hasPrebuiltBootImage(IDevice device) {
116 String ret =
117 doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS);
118
119 return !ret.contains("No such file or directory");
120 }
121
122 /**
123 * Remove files involved in a standard build that interfere with collecting data. This will
124 * remove /etc/preloaded-classes, which determines which classes are allocated already in the
125 * boot image. It also deletes any compiled boot image on the device. Then it restarts the
126 * device.
127 *
128 * This is a potentially long-running operation, as the boot after the deletion may take a while.
129 * The method will abort after the given timeout.
130 */
131 public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) {
132 String oldContent =
133 DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS);
134 if (oldContent.trim().equals("")) {
135 System.out.println("Preloaded-classes already empty.");
136 return true;
137 }
138
139 // Stop the system server etc.
140 doShell(device, "stop", 100, TimeUnit.MILLISECONDS);
141
142 // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount,"
143 // but AndroidDebugBridge doesn't expose it.
144 doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS);
145 doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
146 // We do need an empty file.
147 doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
148
149 // Delete the files in the dalvik cache.
150 doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS);
151
152 // We'll try to use dev.bootcomplete to know when the system server is back up. But stop
153 // doesn't reset it, so do it manually.
154 doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS);
155
156 // Start the system server.
157 doShell(device, "start", 100, TimeUnit.MILLISECONDS);
158
159 // Do a loop checking each second whether bootcomplete. Wait for at most the given
160 // threshold.
161 Date startDate = new Date();
162 for (;;) {
163 try {
164 Thread.sleep(1000);
165 } catch (InterruptedException e) {
166 // Ignore spurious wakeup.
167 }
168 // Check whether bootcomplete.
169 String ret =
170 doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS);
171 if (ret.trim().equals("1")) {
172 break;
173 }
174 System.out.println("Still not booted: " + ret);
175
176 // Check whether we timed out. This is a simplistic check that doesn't take into account
177 // things like switches in time.
178 Date endDate = new Date();
179 long seconds =
180 TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS);
181 if (seconds > preloadedWaitTimeInSeconds) {
182 return false;
183 }
184 }
185
186 return true;
187 }
188
189 /**
190 * Enable method-tracing on device. The system should be restarted after this.
191 */
192 public static void enableTracing(IDevice device) {
193 // Disable selinux.
194 doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS);
195
196 // Make the profile directory world-writable.
197 doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS);
198
199 // Enable streaming method tracing with a small 1K buffer.
200 doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS);
201 doShell(device, "setprop dalvik.vm.method-trace-file "
202 + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS);
203 doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS);
204 doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS);
205 }
206
207 private static class NullShellOutputReceiver implements IShellOutputReceiver {
208 @Override
209 public boolean isCancelled() {
210 return false;
211 }
212
213 @Override
214 public void flush() {}
215
216 @Override
217 public void addOutput(byte[] arg0, int arg1, int arg2) {}
218 }
219
220 private static class CollectStringShellOutputReceiver implements IShellOutputReceiver {
221
222 private StringBuilder builder = new StringBuilder();
223
224 @Override
225 public String toString() {
226 String ret = builder.toString();
227 // Strip trailing newlines. They are especially ugly because adb uses DOS line endings.
228 while (ret.endsWith("\r") || ret.endsWith("\n")) {
229 ret = ret.substring(0, ret.length() - 1);
230 }
231 return ret;
232 }
233
234 @Override
235 public void addOutput(byte[] arg0, int arg1, int arg2) {
236 builder.append(new String(arg0, arg1, arg2));
237 }
238
239 @Override
240 public void flush() {}
241
242 @Override
243 public boolean isCancelled() {
244 return false;
245 }
246 }
247
248 private static class WaitForDevice {
249
250 private String serial;
251 private long timeout;
252 private IDevice device;
253
254 public WaitForDevice(String serial, long timeout) {
255 this.serial = serial;
256 this.timeout = timeout;
257 device = null;
258 }
259
260 public IDevice get() {
261 if (device == null) {
262 WaitForDeviceListener wfdl = new WaitForDeviceListener(serial);
263 synchronized (wfdl) {
264 AndroidDebugBridge.addDeviceChangeListener(wfdl);
265
266 // Check whether we already know about this device.
267 IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
268 if (serial != null) {
269 for (IDevice d : devices) {
270 if (serial.equals(d.getSerialNumber())) {
271 // Only accept if there are clients already. Else wait for the callback informing
272 // us that we now have clients.
273 if (d.hasClients()) {
274 device = d;
275 }
276
277 break;
278 }
279 }
280 } else {
281 if (devices.length > 0) {
282 device = devices[0];
283 }
284 }
285
286 if (device == null) {
287 try {
288 wait(timeout);
289 } catch (InterruptedException e) {
290 // Ignore spurious wakeups.
291 }
292 device = wfdl.getDevice();
293 }
294
295 AndroidDebugBridge.removeDeviceChangeListener(wfdl);
296 }
297 }
298
299 if (device != null) {
300 // Wait for clients.
301 WaitForClientsListener wfcl = new WaitForClientsListener(device);
302 synchronized (wfcl) {
303 AndroidDebugBridge.addDeviceChangeListener(wfcl);
304
305 if (!device.hasClients()) {
306 try {
307 wait(timeout);
308 } catch (InterruptedException e) {
309 // Ignore spurious wakeups.
310 }
311 }
312
313 AndroidDebugBridge.removeDeviceChangeListener(wfcl);
314 }
315 }
316
317 return device;
318 }
319
320 private static class WaitForDeviceListener implements IDeviceChangeListener {
321
322 private String serial;
323 private IDevice device;
324
325 public WaitForDeviceListener(String serial) {
326 this.serial = serial;
327 }
328
329 public IDevice getDevice() {
330 return device;
331 }
332
333 @Override
334 public void deviceChanged(IDevice arg0, int arg1) {
335 // We may get a device changed instead of connected. Handle like a connection.
336 deviceConnected(arg0);
337 }
338
339 @Override
340 public void deviceConnected(IDevice arg0) {
341 if (device != null) {
342 // Ignore updates.
343 return;
344 }
345
346 if (serial == null || serial.equals(arg0.getSerialNumber())) {
347 device = arg0;
348 synchronized (this) {
349 notifyAll();
350 }
351 }
352 }
353
354 @Override
355 public void deviceDisconnected(IDevice arg0) {
356 // Ignore disconnects.
357 }
358
359 }
360
361 private static class WaitForClientsListener implements IDeviceChangeListener {
362
363 private IDevice myDevice;
364
365 public WaitForClientsListener(IDevice myDevice) {
366 this.myDevice = myDevice;
367 }
368
369 @Override
370 public void deviceChanged(IDevice arg0, int arg1) {
371 if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) {
372 // Got a client list, done here.
373 synchronized (this) {
374 notifyAll();
375 }
376 }
377 }
378
379 @Override
380 public void deviceConnected(IDevice arg0) {
381 }
382
383 @Override
384 public void deviceDisconnected(IDevice arg0) {
385 }
386
387 }
388 }
389
390}