1 /* 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 17 package com.android.preload; 18 19 import com.android.ddmlib.AndroidDebugBridge; 20 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; 21 import com.android.preload.classdataretrieval.hprof.Hprof; 22 import com.android.ddmlib.DdmPreferences; 23 import com.android.ddmlib.IDevice; 24 import com.android.ddmlib.IShellOutputReceiver; 25 26 import java.util.Date; 27 import java.util.concurrent.Future; 28 import java.util.concurrent.TimeUnit; 29 30 /** 31 * Helper class for some device routines. 32 */ 33 public class DeviceUtils { 34 init(int debugPort)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 */ doShell(IDevice device, String cmdline, long timeout, TimeUnit unit)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 */ doShellReturnString(IDevice device, String cmdline, long timeout, TimeUnit unit)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 */ doShell(IDevice device, String cmdline, IShellOutputReceiver receiver, long timeout, TimeUnit unit)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 */ doAMStart(IDevice device, String name, String activity)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 */ findDevice(String serial, int timeout)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 */ findDevices(int timeout)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 */ getBuildType(IDevice device)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 */ hasPrebuiltBootImage(IDevice device)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 */ removePreloaded(IDevice device, long preloadedWaitTimeInSeconds)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 */ enableTracing(IDevice device)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 isCancelled()209 public boolean isCancelled() { 210 return false; 211 } 212 213 @Override flush()214 public void flush() {} 215 216 @Override addOutput(byte[] arg0, int arg1, int arg2)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 toString()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 addOutput(byte[] arg0, int arg1, int arg2)235 public void addOutput(byte[] arg0, int arg1, int arg2) { 236 builder.append(new String(arg0, arg1, arg2)); 237 } 238 239 @Override flush()240 public void flush() {} 241 242 @Override isCancelled()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 WaitForDevice(String serial, long timeout)254 public WaitForDevice(String serial, long timeout) { 255 this.serial = serial; 256 this.timeout = timeout; 257 device = null; 258 } 259 get()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 WaitForDeviceListener(String serial)325 public WaitForDeviceListener(String serial) { 326 this.serial = serial; 327 } 328 getDevice()329 public IDevice getDevice() { 330 return device; 331 } 332 333 @Override deviceChanged(IDevice arg0, int arg1)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 deviceConnected(IDevice arg0)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 deviceDisconnected(IDevice arg0)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 WaitForClientsListener(IDevice myDevice)365 public WaitForClientsListener(IDevice myDevice) { 366 this.myDevice = myDevice; 367 } 368 369 @Override deviceChanged(IDevice arg0, int arg1)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 deviceConnected(IDevice arg0)380 public void deviceConnected(IDevice arg0) { 381 } 382 383 @Override deviceDisconnected(IDevice arg0)384 public void deviceDisconnected(IDevice arg0) { 385 } 386 387 } 388 } 389 390 } 391