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