1/*
2 * Copyright (C) 2007 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
17package com.android.ddmlib;
18
19import com.android.ddmlib.log.LogReceiver;
20
21import java.io.File;
22import java.io.IOException;
23import java.nio.channels.SocketChannel;
24import java.util.ArrayList;
25import java.util.Collections;
26import java.util.HashMap;
27import java.util.List;
28import java.util.Map;
29import java.util.regex.Matcher;
30import java.util.regex.Pattern;
31
32
33/**
34 * A Device. It can be a physical device or an emulator.
35 */
36final class Device implements IDevice {
37    private static final String DEVICE_MODEL_PROPERTY = "ro.product.model"; //$NON-NLS-1$
38    private static final String DEVICE_MANUFACTURER_PROPERTY = "ro.product.manufacturer"; //$NON-NLS-1$
39
40    private final static int INSTALL_TIMEOUT = 2*60*1000; //2min
41    private static final int BATTERY_TIMEOUT = 2*1000; //2 seconds
42    private static final int GETPROP_TIMEOUT = 2*1000; //2 seconds
43
44    /** Emulator Serial Number regexp. */
45    final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
46
47    /** Serial number of the device */
48    private String mSerialNumber = null;
49
50    /** Name of the AVD */
51    private String mAvdName = null;
52
53    /** State of the device. */
54    private DeviceState mState = null;
55
56    /** Device properties. */
57    private final Map<String, String> mProperties = new HashMap<String, String>();
58    private final Map<String, String> mMountPoints = new HashMap<String, String>();
59
60    private final ArrayList<Client> mClients = new ArrayList<Client>();
61    private DeviceMonitor mMonitor;
62
63    private static final String LOG_TAG = "Device";
64    private static final char SEPARATOR = '-';
65
66    /**
67     * Socket for the connection monitoring client connection/disconnection.
68     */
69    private SocketChannel mSocketChannel;
70
71    private boolean mArePropertiesSet = false;
72
73    private Integer mLastBatteryLevel = null;
74    private long mLastBatteryCheckTime = 0;
75
76    private String mName;
77
78    /**
79     * Output receiver for "pm install package.apk" command line.
80     */
81    private static final class InstallReceiver extends MultiLineReceiver {
82
83        private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
84        private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
85
86        private String mErrorMessage = null;
87
88        public InstallReceiver() {
89        }
90
91        @Override
92        public void processNewLines(String[] lines) {
93            for (String line : lines) {
94                if (line.length() > 0) {
95                    if (line.startsWith(SUCCESS_OUTPUT)) {
96                        mErrorMessage = null;
97                    } else {
98                        Matcher m = FAILURE_PATTERN.matcher(line);
99                        if (m.matches()) {
100                            mErrorMessage = m.group(1);
101                        }
102                    }
103                }
104            }
105        }
106
107        @Override
108        public boolean isCancelled() {
109            return false;
110        }
111
112        public String getErrorMessage() {
113            return mErrorMessage;
114        }
115    }
116
117    /**
118     * Output receiver for "dumpsys battery" command line.
119     */
120    private static final class BatteryReceiver extends MultiLineReceiver {
121        private static final Pattern BATTERY_LEVEL = Pattern.compile("\\s*level: (\\d+)");
122        private static final Pattern SCALE = Pattern.compile("\\s*scale: (\\d+)");
123
124        private Integer mBatteryLevel = null;
125        private Integer mBatteryScale = null;
126
127        /**
128         * Get the parsed percent battery level.
129         * @return
130         */
131        public Integer getBatteryLevel() {
132            if (mBatteryLevel != null && mBatteryScale != null) {
133                return (mBatteryLevel * 100) / mBatteryScale;
134            }
135            return null;
136        }
137
138        @Override
139        public void processNewLines(String[] lines) {
140            for (String line : lines) {
141                Matcher batteryMatch = BATTERY_LEVEL.matcher(line);
142                if (batteryMatch.matches()) {
143                    try {
144                        mBatteryLevel = Integer.parseInt(batteryMatch.group(1));
145                    } catch (NumberFormatException e) {
146                        Log.w(LOG_TAG, String.format("Failed to parse %s as an integer",
147                                batteryMatch.group(1)));
148                    }
149                }
150                Matcher scaleMatch = SCALE.matcher(line);
151                if (scaleMatch.matches()) {
152                    try {
153                        mBatteryScale = Integer.parseInt(scaleMatch.group(1));
154                    } catch (NumberFormatException e) {
155                        Log.w(LOG_TAG, String.format("Failed to parse %s as an integer",
156                                batteryMatch.group(1)));
157                    }
158                }
159            }
160        }
161
162        @Override
163        public boolean isCancelled() {
164            return false;
165        }
166    }
167
168    /*
169     * (non-Javadoc)
170     * @see com.android.ddmlib.IDevice#getSerialNumber()
171     */
172    @Override
173    public String getSerialNumber() {
174        return mSerialNumber;
175    }
176
177    /** {@inheritDoc} */
178    @Override
179    public String getAvdName() {
180        return mAvdName;
181    }
182
183    /**
184     * Sets the name of the AVD
185     */
186    void setAvdName(String avdName) {
187        if (isEmulator() == false) {
188            throw new IllegalArgumentException(
189                    "Cannot set the AVD name of the device is not an emulator");
190        }
191
192        mAvdName = avdName;
193    }
194
195    @Override
196    public String getName() {
197        if (mName == null) {
198            mName = constructName();
199        }
200
201        return mName;
202    }
203
204    private String constructName() {
205        if (isEmulator()) {
206            String avdName = getAvdName();
207            if (avdName != null) {
208                return String.format("%s [%s]", avdName, getSerialNumber());
209            } else {
210                return getSerialNumber();
211            }
212        } else {
213            String manufacturer = cleanupStringForDisplay(
214                    getProperty(DEVICE_MANUFACTURER_PROPERTY));
215            String model = cleanupStringForDisplay(
216                    getProperty(DEVICE_MODEL_PROPERTY));
217
218            StringBuilder sb = new StringBuilder(20);
219
220            if (manufacturer != null) {
221                sb.append(manufacturer);
222                sb.append(SEPARATOR);
223            }
224
225            if (model != null) {
226                sb.append(model);
227                sb.append(SEPARATOR);
228            }
229
230            sb.append(getSerialNumber());
231            return sb.toString();
232        }
233    }
234
235    private String cleanupStringForDisplay(String s) {
236        if (s == null) {
237            return null;
238        }
239
240        StringBuilder sb = new StringBuilder(s.length());
241        for (int i = 0; i < s.length(); i++) {
242            char c = s.charAt(i);
243
244            if (Character.isLetterOrDigit(c)) {
245                sb.append(Character.toLowerCase(c));
246            } else {
247                sb.append('_');
248            }
249        }
250
251        return sb.toString();
252    }
253
254    /*
255     * (non-Javadoc)
256     * @see com.android.ddmlib.IDevice#getState()
257     */
258    @Override
259    public DeviceState getState() {
260        return mState;
261    }
262
263    /**
264     * Changes the state of the device.
265     */
266    void setState(DeviceState state) {
267        mState = state;
268    }
269
270
271    /*
272     * (non-Javadoc)
273     * @see com.android.ddmlib.IDevice#getProperties()
274     */
275    @Override
276    public Map<String, String> getProperties() {
277        return Collections.unmodifiableMap(mProperties);
278    }
279
280    /*
281     * (non-Javadoc)
282     * @see com.android.ddmlib.IDevice#getPropertyCount()
283     */
284    @Override
285    public int getPropertyCount() {
286        return mProperties.size();
287    }
288
289    /*
290     * (non-Javadoc)
291     * @see com.android.ddmlib.IDevice#getProperty(java.lang.String)
292     */
293    @Override
294    public String getProperty(String name) {
295        return mProperties.get(name);
296    }
297
298    /**
299     * {@inheritDoc}
300     */
301    @Override
302    public boolean arePropertiesSet() {
303        return mArePropertiesSet;
304    }
305
306    /**
307     * {@inheritDoc}
308     */
309    @Override
310    public String getPropertyCacheOrSync(String name) throws TimeoutException,
311            AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
312        if (mArePropertiesSet) {
313            return getProperty(name);
314        } else {
315            return getPropertySync(name);
316        }
317    }
318
319    /**
320     * {@inheritDoc}
321     */
322    @Override
323    public String getPropertySync(String name) throws TimeoutException,
324            AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
325        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
326        executeShellCommand(String.format("getprop '%s'", name), receiver, GETPROP_TIMEOUT);
327        String value = receiver.getOutput().trim();
328        if (value.isEmpty()) {
329            return null;
330        }
331        return value;
332    }
333
334    @Override
335    public String getMountPoint(String name) {
336        return mMountPoints.get(name);
337    }
338
339
340    @Override
341    public String toString() {
342        return mSerialNumber;
343    }
344
345    /*
346     * (non-Javadoc)
347     * @see com.android.ddmlib.IDevice#isOnline()
348     */
349    @Override
350    public boolean isOnline() {
351        return mState == DeviceState.ONLINE;
352    }
353
354    /*
355     * (non-Javadoc)
356     * @see com.android.ddmlib.IDevice#isEmulator()
357     */
358    @Override
359    public boolean isEmulator() {
360        return mSerialNumber.matches(RE_EMULATOR_SN);
361    }
362
363    /*
364     * (non-Javadoc)
365     * @see com.android.ddmlib.IDevice#isOffline()
366     */
367    @Override
368    public boolean isOffline() {
369        return mState == DeviceState.OFFLINE;
370    }
371
372    /*
373     * (non-Javadoc)
374     * @see com.android.ddmlib.IDevice#isBootLoader()
375     */
376    @Override
377    public boolean isBootLoader() {
378        return mState == DeviceState.BOOTLOADER;
379    }
380
381    /*
382     * (non-Javadoc)
383     * @see com.android.ddmlib.IDevice#hasClients()
384     */
385    @Override
386    public boolean hasClients() {
387        return mClients.size() > 0;
388    }
389
390    /*
391     * (non-Javadoc)
392     * @see com.android.ddmlib.IDevice#getClients()
393     */
394    @Override
395    public Client[] getClients() {
396        synchronized (mClients) {
397            return mClients.toArray(new Client[mClients.size()]);
398        }
399    }
400
401    /*
402     * (non-Javadoc)
403     * @see com.android.ddmlib.IDevice#getClient(java.lang.String)
404     */
405    @Override
406    public Client getClient(String applicationName) {
407        synchronized (mClients) {
408            for (Client c : mClients) {
409                if (applicationName.equals(c.getClientData().getClientDescription())) {
410                    return c;
411                }
412            }
413
414        }
415
416        return null;
417    }
418
419    /*
420     * (non-Javadoc)
421     * @see com.android.ddmlib.IDevice#getSyncService()
422     */
423    @Override
424    public SyncService getSyncService()
425            throws TimeoutException, AdbCommandRejectedException, IOException {
426        SyncService syncService = new SyncService(AndroidDebugBridge.getSocketAddress(), this);
427        if (syncService.openSync()) {
428            return syncService;
429         }
430
431        return null;
432    }
433
434    /*
435     * (non-Javadoc)
436     * @see com.android.ddmlib.IDevice#getFileListingService()
437     */
438    @Override
439    public FileListingService getFileListingService() {
440        return new FileListingService(this);
441    }
442
443    @Override
444    public RawImage getScreenshot()
445            throws TimeoutException, AdbCommandRejectedException, IOException {
446        return AdbHelper.getFrameBuffer(AndroidDebugBridge.getSocketAddress(), this);
447    }
448
449    @Override
450    public void executeShellCommand(String command, IShellOutputReceiver receiver)
451            throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
452            IOException {
453        AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this,
454                receiver, DdmPreferences.getTimeOut());
455    }
456
457    @Override
458    public void executeShellCommand(String command, IShellOutputReceiver receiver,
459            int maxTimeToOutputResponse)
460            throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
461            IOException {
462        AdbHelper.executeRemoteCommand(AndroidDebugBridge.getSocketAddress(), command, this,
463                receiver, maxTimeToOutputResponse);
464    }
465
466    @Override
467    public void runEventLogService(LogReceiver receiver)
468            throws TimeoutException, AdbCommandRejectedException, IOException {
469        AdbHelper.runEventLogService(AndroidDebugBridge.getSocketAddress(), this, receiver);
470    }
471
472    @Override
473    public void runLogService(String logname, LogReceiver receiver)
474            throws TimeoutException, AdbCommandRejectedException, IOException {
475        AdbHelper.runLogService(AndroidDebugBridge.getSocketAddress(), this, logname, receiver);
476    }
477
478    @Override
479    public void createForward(int localPort, int remotePort)
480            throws TimeoutException, AdbCommandRejectedException, IOException {
481        AdbHelper.createForward(AndroidDebugBridge.getSocketAddress(), this,
482                String.format("tcp:%d", localPort),     //$NON-NLS-1$
483                String.format("tcp:%d", remotePort));   //$NON-NLS-1$
484    }
485
486    @Override
487    public void createForward(int localPort, String remoteSocketName,
488            DeviceUnixSocketNamespace namespace) throws TimeoutException,
489            AdbCommandRejectedException, IOException {
490        AdbHelper.createForward(AndroidDebugBridge.getSocketAddress(), this,
491                String.format("tcp:%d", localPort),     //$NON-NLS-1$
492                String.format("%s:%s", namespace.getType(), remoteSocketName));   //$NON-NLS-1$
493    }
494
495    @Override
496    public void removeForward(int localPort, int remotePort)
497            throws TimeoutException, AdbCommandRejectedException, IOException {
498        AdbHelper.removeForward(AndroidDebugBridge.getSocketAddress(), this,
499                String.format("tcp:%d", localPort),     //$NON-NLS-1$
500                String.format("tcp:%d", remotePort));   //$NON-NLS-1$
501    }
502
503    @Override
504    public void removeForward(int localPort, String remoteSocketName,
505            DeviceUnixSocketNamespace namespace) throws TimeoutException,
506            AdbCommandRejectedException, IOException {
507        AdbHelper.removeForward(AndroidDebugBridge.getSocketAddress(), this,
508                String.format("tcp:%d", localPort),     //$NON-NLS-1$
509                String.format("%s:%s", namespace.getType(), remoteSocketName));   //$NON-NLS-1$
510    }
511
512    /*
513     * (non-Javadoc)
514     * @see com.android.ddmlib.IDevice#getClientName(int)
515     */
516    @Override
517    public String getClientName(int pid) {
518        synchronized (mClients) {
519            for (Client c : mClients) {
520                if (c.getClientData().getPid() == pid) {
521                    return c.getClientData().getClientDescription();
522                }
523            }
524        }
525
526        return null;
527    }
528
529
530    Device(DeviceMonitor monitor, String serialNumber, DeviceState deviceState) {
531        mMonitor = monitor;
532        mSerialNumber = serialNumber;
533        mState = deviceState;
534    }
535
536    DeviceMonitor getMonitor() {
537        return mMonitor;
538    }
539
540    void addClient(Client client) {
541        synchronized (mClients) {
542            mClients.add(client);
543        }
544    }
545
546    List<Client> getClientList() {
547        return mClients;
548    }
549
550    boolean hasClient(int pid) {
551        synchronized (mClients) {
552            for (Client client : mClients) {
553                if (client.getClientData().getPid() == pid) {
554                    return true;
555                }
556            }
557        }
558
559        return false;
560    }
561
562    void clearClientList() {
563        synchronized (mClients) {
564            mClients.clear();
565        }
566    }
567
568    /**
569     * Sets the client monitoring socket.
570     * @param socketChannel the sockets
571     */
572    void setClientMonitoringSocket(SocketChannel socketChannel) {
573        mSocketChannel = socketChannel;
574    }
575
576    /**
577     * Returns the client monitoring socket.
578     */
579    SocketChannel getClientMonitoringSocket() {
580        return mSocketChannel;
581    }
582
583    /**
584     * Removes a {@link Client} from the list.
585     * @param client the client to remove.
586     * @param notify Whether or not to notify the listeners of a change.
587     */
588    void removeClient(Client client, boolean notify) {
589        mMonitor.addPortToAvailableList(client.getDebuggerListenPort());
590        synchronized (mClients) {
591            mClients.remove(client);
592        }
593        if (notify) {
594            mMonitor.getServer().deviceChanged(this, CHANGE_CLIENT_LIST);
595        }
596    }
597
598    void update(int changeMask) {
599        if ((changeMask & CHANGE_BUILD_INFO) != 0) {
600            mArePropertiesSet = true;
601        }
602        mMonitor.getServer().deviceChanged(this, changeMask);
603    }
604
605    void update(Client client, int changeMask) {
606        mMonitor.getServer().clientChanged(client, changeMask);
607    }
608
609    void addProperty(String label, String value) {
610        mProperties.put(label, value);
611    }
612
613    void setMountingPoint(String name, String value) {
614        mMountPoints.put(name, value);
615    }
616
617    @Override
618    public void pushFile(String local, String remote)
619            throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
620        SyncService sync = null;
621        try {
622            String targetFileName = getFileName(local);
623
624            Log.d(targetFileName, String.format("Uploading %1$s onto device '%2$s'",
625                    targetFileName, getSerialNumber()));
626
627            sync = getSyncService();
628            if (sync != null) {
629                String message = String.format("Uploading file onto device '%1$s'",
630                        getSerialNumber());
631                Log.d(LOG_TAG, message);
632                sync.pushFile(local, remote, SyncService.getNullProgressMonitor());
633            } else {
634                throw new IOException("Unable to open sync connection!");
635            }
636        } catch (TimeoutException e) {
637            Log.e(LOG_TAG, "Error during Sync: timeout.");
638            throw e;
639
640        } catch (SyncException e) {
641            Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
642            throw e;
643
644        } catch (IOException e) {
645            Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
646            throw e;
647
648        } finally {
649            if (sync != null) {
650                sync.close();
651            }
652        }
653    }
654
655    @Override
656    public void pullFile(String remote, String local)
657            throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
658        SyncService sync = null;
659        try {
660            String targetFileName = getFileName(remote);
661
662            Log.d(targetFileName, String.format("Downloading %1$s from device '%2$s'",
663                    targetFileName, getSerialNumber()));
664
665            sync = getSyncService();
666            if (sync != null) {
667                String message = String.format("Downloding file from device '%1$s'",
668                        getSerialNumber());
669                Log.d(LOG_TAG, message);
670                sync.pullFile(remote, local, SyncService.getNullProgressMonitor());
671            } else {
672                throw new IOException("Unable to open sync connection!");
673            }
674        } catch (TimeoutException e) {
675            Log.e(LOG_TAG, "Error during Sync: timeout.");
676            throw e;
677
678        } catch (SyncException e) {
679            Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
680            throw e;
681
682        } catch (IOException e) {
683            Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
684            throw e;
685
686        } finally {
687            if (sync != null) {
688                sync.close();
689            }
690        }
691    }
692
693    @Override
694    public String installPackage(String packageFilePath, boolean reinstall, String... extraArgs)
695            throws InstallException {
696        try {
697            String remoteFilePath = syncPackageToDevice(packageFilePath);
698            String result = installRemotePackage(remoteFilePath, reinstall, extraArgs);
699            removeRemotePackage(remoteFilePath);
700            return result;
701        } catch (IOException e) {
702            throw new InstallException(e);
703        } catch (AdbCommandRejectedException e) {
704            throw new InstallException(e);
705        } catch (TimeoutException e) {
706            throw new InstallException(e);
707        } catch (SyncException e) {
708            throw new InstallException(e);
709        }
710    }
711
712    @Override
713    public String syncPackageToDevice(String localFilePath)
714            throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
715        SyncService sync = null;
716        try {
717            String packageFileName = getFileName(localFilePath);
718            String remoteFilePath = String.format("/data/local/tmp/%1$s", packageFileName); //$NON-NLS-1$
719
720            Log.d(packageFileName, String.format("Uploading %1$s onto device '%2$s'",
721                    packageFileName, getSerialNumber()));
722
723            sync = getSyncService();
724            if (sync != null) {
725                String message = String.format("Uploading file onto device '%1$s'",
726                        getSerialNumber());
727                Log.d(LOG_TAG, message);
728                sync.pushFile(localFilePath, remoteFilePath, SyncService.getNullProgressMonitor());
729            } else {
730                throw new IOException("Unable to open sync connection!");
731            }
732            return remoteFilePath;
733        } catch (TimeoutException e) {
734            Log.e(LOG_TAG, "Error during Sync: timeout.");
735            throw e;
736
737        } catch (SyncException e) {
738            Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
739            throw e;
740
741        } catch (IOException e) {
742            Log.e(LOG_TAG, String.format("Error during Sync: %1$s", e.getMessage()));
743            throw e;
744
745        } finally {
746            if (sync != null) {
747                sync.close();
748            }
749        }
750    }
751
752    /**
753     * Helper method to retrieve the file name given a local file path
754     * @param filePath full directory path to file
755     * @return {@link String} file name
756     */
757    private String getFileName(String filePath) {
758        return new File(filePath).getName();
759    }
760
761    @Override
762    public String installRemotePackage(String remoteFilePath, boolean reinstall,
763            String... extraArgs) throws InstallException {
764        try {
765            InstallReceiver receiver = new InstallReceiver();
766            StringBuilder optionString = new StringBuilder();
767            if (reinstall) {
768                optionString.append("-r ");
769            }
770            for (String arg : extraArgs) {
771                optionString.append(arg);
772                optionString.append(' ');
773            }
774            String cmd = String.format("pm install %1$s \"%2$s\"", optionString.toString(),
775                    remoteFilePath);
776            executeShellCommand(cmd, receiver, INSTALL_TIMEOUT);
777            return receiver.getErrorMessage();
778        } catch (TimeoutException e) {
779            throw new InstallException(e);
780        } catch (AdbCommandRejectedException e) {
781            throw new InstallException(e);
782        } catch (ShellCommandUnresponsiveException e) {
783            throw new InstallException(e);
784        } catch (IOException e) {
785            throw new InstallException(e);
786        }
787    }
788
789    @Override
790    public void removeRemotePackage(String remoteFilePath) throws InstallException {
791        try {
792            executeShellCommand("rm " + remoteFilePath, new NullOutputReceiver(), INSTALL_TIMEOUT);
793        } catch (IOException e) {
794            throw new InstallException(e);
795        } catch (TimeoutException e) {
796            throw new InstallException(e);
797        } catch (AdbCommandRejectedException e) {
798            throw new InstallException(e);
799        } catch (ShellCommandUnresponsiveException e) {
800            throw new InstallException(e);
801        }
802    }
803
804    @Override
805    public String uninstallPackage(String packageName) throws InstallException {
806        try {
807            InstallReceiver receiver = new InstallReceiver();
808            executeShellCommand("pm uninstall " + packageName, receiver, INSTALL_TIMEOUT);
809            return receiver.getErrorMessage();
810        } catch (TimeoutException e) {
811            throw new InstallException(e);
812        } catch (AdbCommandRejectedException e) {
813            throw new InstallException(e);
814        } catch (ShellCommandUnresponsiveException e) {
815            throw new InstallException(e);
816        } catch (IOException e) {
817            throw new InstallException(e);
818        }
819    }
820
821    /*
822     * (non-Javadoc)
823     * @see com.android.ddmlib.IDevice#reboot()
824     */
825    @Override
826    public void reboot(String into)
827            throws TimeoutException, AdbCommandRejectedException, IOException {
828        AdbHelper.reboot(into, AndroidDebugBridge.getSocketAddress(), this);
829    }
830
831    @Override
832    public Integer getBatteryLevel() throws TimeoutException, AdbCommandRejectedException,
833            IOException, ShellCommandUnresponsiveException {
834        // use default of 5 minutes
835        return getBatteryLevel(5 * 60 * 1000);
836    }
837
838    @Override
839    public Integer getBatteryLevel(long freshnessMs) throws TimeoutException,
840            AdbCommandRejectedException, IOException, ShellCommandUnresponsiveException {
841        if (mLastBatteryLevel != null
842                && mLastBatteryCheckTime > (System.currentTimeMillis() - freshnessMs)) {
843            return mLastBatteryLevel;
844        }
845        BatteryReceiver receiver = new BatteryReceiver();
846        executeShellCommand("dumpsys battery", receiver, BATTERY_TIMEOUT);
847        mLastBatteryLevel = receiver.getBatteryLevel();
848        mLastBatteryCheckTime = System.currentTimeMillis();
849        return mLastBatteryLevel;
850    }
851}
852