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.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 
     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    */
     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    */
     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    */
     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    */
     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    */
    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    */
    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    */
    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    */
    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      */
    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 
    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    */
    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
    239     public boolean isCancelled() {
    240       return false;
    241     }
    242 
    243     @Override
    244     public void flush() {}
    245 
    246     @Override
    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
    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
    265     public void addOutput(byte[] arg0, int arg1, int arg2) {
    266       builder.append(new String(arg0, arg1, arg2));
    267     }
    268 
    269     @Override
    270     public void flush() {}
    271 
    272     @Override
    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 
    284     public WaitForDevice(String serial, long timeout) {
    285       this.serial = serial;
    286       this.timeout = timeout;
    287       device = null;
    288     }
    289 
    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 
    355         public WaitForDeviceListener(String serial) {
    356             this.serial = serial;
    357         }
    358 
    359         public IDevice getDevice() {
    360             return device;
    361         }
    362 
    363         @Override
    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
    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
    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 
    395         public WaitForClientsListener(IDevice myDevice) {
    396             this.myDevice = myDevice;
    397         }
    398 
    399         @Override
    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
    410         public void deviceConnected(IDevice arg0) {
    411         }
    412 
    413         @Override
    414         public void deviceDisconnected(IDevice arg0) {
    415         }
    416 
    417     }
    418   }
    419 
    420 }
    421