1 /*
2  * Copyright (C) 2016 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.googlecode.android_scripting;
18 
19 import com.googlecode.android_scripting.interpreter.InterpreterConstants;
20 import com.trilead.ssh2.StreamGobbler;
21 
22 import java.io.File;
23 import java.io.FileDescriptor;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import java.util.concurrent.atomic.AtomicInteger;
35 
36 public class Process {
37 
38   private static final int DEFAULT_BUFFER_SIZE = 8192;
39 
40   private final List<String> mArguments;
41   private final Map<String, String> mEnvironment;
42 
43   private static final int PID_INIT_VALUE = -1;
44 
45   private File mBinary;
46   private String mName;
47   private long mStartTime;
48   private long mEndTime;
49 
50   protected final AtomicInteger mPid;
51   protected FileDescriptor mFd;
52   protected OutputStream mOut;
53   protected InputStream mIn;
54   protected File mLog;
55 
Process()56   public Process() {
57     mArguments = new ArrayList<String>();
58     mEnvironment = new HashMap<String, String>();
59     mPid = new AtomicInteger(PID_INIT_VALUE);
60   }
61 
addArgument(String argument)62   public void addArgument(String argument) {
63     mArguments.add(argument);
64   }
65 
addAllArguments(List<String> arguments)66   public void addAllArguments(List<String> arguments) {
67     mArguments.addAll(arguments);
68   }
69 
putAllEnvironmentVariables(Map<String, String> environment)70   public void putAllEnvironmentVariables(Map<String, String> environment) {
71     mEnvironment.putAll(environment);
72   }
73 
putEnvironmentVariable(String key, String value)74   public void putEnvironmentVariable(String key, String value) {
75     mEnvironment.put(key, value);
76   }
77 
setBinary(File binary)78   public void setBinary(File binary) {
79     if (!binary.exists()) {
80       throw new RuntimeException("Binary " + binary + " does not exist!");
81     }
82     mBinary = binary;
83   }
84 
getPid()85   public Integer getPid() {
86     return mPid.get();
87   }
88 
getFd()89   public FileDescriptor getFd() {
90     return mFd;
91   }
92 
getOut()93   public OutputStream getOut() {
94     return mOut;
95   }
96 
getErr()97   public OutputStream getErr() {
98     return getOut();
99   }
100 
getLogFile()101   public File getLogFile() {
102     return mLog;
103   }
104 
getIn()105   public InputStream getIn() {
106     return mIn;
107   }
108 
start(final Runnable shutdownHook)109   public void start(final Runnable shutdownHook) {
110     if (isAlive()) {
111       throw new RuntimeException("Attempted to start process that is already running.");
112     }
113 
114     String binaryPath = mBinary.getAbsolutePath();
115     Log.v("Executing " + binaryPath + " with arguments " + mArguments + " and with environment "
116         + mEnvironment.toString());
117 
118     int[] pid = new int[1];
119     String[] argumentsArray = mArguments.toArray(new String[mArguments.size()]);
120     mLog = new File(String.format("%s/%s.log", InterpreterConstants.SDCARD_SL4A_ROOT, getName()));
121 
122     mFd =
123         Exec.createSubprocess(binaryPath, argumentsArray, getEnvironmentArray(),
124             getWorkingDirectory(), pid);
125     mPid.set(pid[0]);
126     mOut = new FileOutputStream(mFd);
127     mIn = new StreamGobbler(new FileInputStream(mFd), mLog, DEFAULT_BUFFER_SIZE);
128     mStartTime = System.currentTimeMillis();
129 
130     new Thread(new Runnable() {
131       public void run() {
132         int result = Exec.waitFor(mPid.get());
133         mEndTime = System.currentTimeMillis();
134         int pid = mPid.getAndSet(PID_INIT_VALUE);
135         Log.v("Process " + pid + " exited with result code " + result + ".");
136         try {
137           mIn.close();
138         } catch (IOException e) {
139           Log.e(e);
140         }
141         try {
142           mOut.close();
143         } catch (IOException e) {
144           Log.e(e);
145         }
146         if (shutdownHook != null) {
147           shutdownHook.run();
148         }
149       }
150     }).start();
151   }
152 
getEnvironmentArray()153   private String[] getEnvironmentArray() {
154     List<String> environmentVariables = new ArrayList<String>();
155     for (Entry<String, String> entry : mEnvironment.entrySet()) {
156       environmentVariables.add(entry.getKey() + "=" + entry.getValue());
157     }
158     String[] environment = environmentVariables.toArray(new String[environmentVariables.size()]);
159     return environment;
160   }
161 
kill()162   public void kill() {
163     if (isAlive()) {
164       android.os.Process.killProcess(mPid.get());
165       Log.v("Killed process " + mPid);
166     }
167   }
168 
isAlive()169   public boolean isAlive() {
170     return (mFd != null && mFd.valid()) && mPid.get() != PID_INIT_VALUE;
171   }
172 
getUptime()173   public String getUptime() {
174     long ms;
175     if (!isAlive()) {
176       ms = mEndTime - mStartTime;
177     } else {
178       ms = System.currentTimeMillis() - mStartTime;
179     }
180     StringBuilder buffer = new StringBuilder();
181     int days = (int) (ms / (1000 * 60 * 60 * 24));
182     int hours = (int) (ms % (1000 * 60 * 60 * 24)) / 3600000;
183     int minutes = (int) (ms % 3600000) / 60000;
184     int seconds = (int) (ms % 60000) / 1000;
185     if (days != 0) {
186       buffer.append(String.format("%02d:%02d:", days, hours));
187     } else if (hours != 0) {
188       buffer.append(String.format("%02d:", hours));
189     }
190     buffer.append(String.format("%02d:%02d", minutes, seconds));
191     return buffer.toString();
192   }
193 
getName()194   public String getName() {
195     return mName;
196   }
197 
setName(String name)198   public void setName(String name) {
199     mName = name;
200   }
201 
getWorkingDirectory()202   public String getWorkingDirectory() {
203     return null;
204   }
205 }
206