Home | History | Annotate | Download | only in preload
      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 
     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 }
    391