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