1 /*
2  * Copyright (C) 2014 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.tradefed.util;
18 
19 import com.android.tradefed.device.CollectingOutputReceiver;
20 import com.android.tradefed.device.DeviceNotAvailableException;
21 import com.android.tradefed.device.ITestDevice;
22 import com.android.tradefed.log.LogUtil.CLog;
23 
24 import java.util.concurrent.Callable;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.ExecutorService;
27 import java.util.concurrent.Future;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.TimeoutException;
30 
31 /**
32  * Contains utility methods and classes for concurrent device side command execution
33  * <p>
34  * Use {@link ExecutorService} to run commands implemented as {@link ShellCommandCallable}, and use
35  * {@link #joinFuture(String, Future, long)} for synchronization against the {@link Future} as
36  * returned by {@link ExecutorService} for the command execution.
37  */
38 public class DeviceConcurrentUtil {
39 
40     /* private constructor for singleton */
DeviceConcurrentUtil()41     private DeviceConcurrentUtil() {}
42 
43     /**
44      * Convenience method to join current thread on the <code>task</code>
45      * <p>
46      * {@link DeviceNotAvailableException} and {@link TimeoutException} occurred during execution
47      * are passed through transparently, others are logged as error but not otherwise handled.
48      * @param taskDesc description of task for logging purpose
49      * @param task {@link Future} representing the task to join
50      * @param timeout timeout for waiting on the task
51      * @return The result of the task with the template type.
52      * @throws DeviceNotAvailableException
53      * @throws TimeoutException
54      */
joinFuture(String taskDesc, Future<T> task, long timeout)55     public static <T> T joinFuture(String taskDesc, Future<T> task, long timeout)
56             throws DeviceNotAvailableException, TimeoutException {
57         try {
58             T ret = task.get(timeout, TimeUnit.MILLISECONDS);
59             return ret;
60         } catch (InterruptedException e) {
61             CLog.e("interrupted while executing " + taskDesc);
62         } catch (ExecutionException e) {
63             Throwable t = e.getCause();
64             if (t instanceof DeviceNotAvailableException) {
65                 // passthru
66                 throw (DeviceNotAvailableException)t;
67             } else {
68                 CLog.e("%s while executing %s", t.getClass().getSimpleName(), taskDesc);
69                 CLog.e(t);
70             }
71         }
72         return null;
73     }
74 
75     /**
76      * A {@link Callable} that wraps the details of executing shell command on
77      * an {@link ITestDevice}.
78      * <p>
79      * Must implement {@link #processOutput(String)} to process the command
80      * output and determine return of the <code>Callable</code>
81      *
82      * @param <V> passthru of the {@link Callable} return type, see
83      *            {@link Callable}
84      */
85     public static abstract class ShellCommandCallable<V> implements Callable<V> {
86         private String mCommand;
87         private long mTimeout;
88         private ITestDevice mDevice;
89 
ShellCommandCallable()90         public ShellCommandCallable() {
91             // do nothing for default
92         }
93 
ShellCommandCallable(ITestDevice device, String command, long timeout)94         public ShellCommandCallable(ITestDevice device, String command, long timeout) {
95             this();
96             mCommand = command;
97             mTimeout = timeout;
98             mDevice = device;
99         }
100 
setCommand(String command)101         public ShellCommandCallable<V> setCommand(String command) {
102             mCommand = command;
103             return this;
104         }
105 
setTimeout(long timeout)106         public ShellCommandCallable<V> setTimeout(long timeout) {
107             mTimeout = timeout;
108             return this;
109         }
110 
setDevice(ITestDevice device)111         public ShellCommandCallable<V> setDevice(ITestDevice device) {
112             mDevice = device;
113             return this;
114         }
115 
116         @Override
call()117         public V call() throws Exception {
118             CollectingOutputReceiver receiver = new CollectingOutputReceiver();
119             mDevice.executeShellCommand(mCommand, receiver, mTimeout, TimeUnit.MILLISECONDS, 1);
120             String output = receiver.getOutput();
121             CLog.v("raw output for \"%s\"\n%s", mCommand, output);
122             return processOutput(output);
123         }
124 
processOutput(String output)125         public abstract V processOutput(String output);
126     }
127 
128 }