• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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