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 android.support.test.aupt; 18 19 import android.app.UiAutomation; 20 import android.app.ActivityManager.RunningAppProcessInfo; 21 import android.os.ParcelFileDescriptor; 22 import android.util.Log; 23 24 import java.io.BufferedReader; 25 import java.io.InputStreamReader; 26 import java.io.IOException; 27 import java.util.ArrayList; 28 import java.util.HashMap; 29 import java.util.HashSet; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.regex.Pattern; 33 import java.util.regex.Matcher; 34 import java.util.Set; 35 36 public class ProcessStatusTracker implements IProcessStatusTracker { 37 private static final String TAG = "ProcessStatusTracker"; 38 39 // Example return line from "adb shell ps" for the pattern below 40 // USER PID PPID VSIZE RSS WCHAN PC NAME 41 // root 1 0 2216 804 sys_epoll_ 00000000 S com.android.chrome 42 private final String PS_PATTERN = 43 "\\w+\\s+(\\d+)\\s+\\w+\\s+\\d+\\s+\\d+\\s+\\w+\\s+[0-9a-f]+\\s+\\w+\\s+(.+)"; 44 private final Pattern PS_PATTERN_MATCH = Pattern.compile(PS_PATTERN); 45 private final int PS_PATTERN_PID_GROUP = 1; 46 private final int PS_PATTERN_PKG_GROUP = 2; 47 48 private Map<String, Integer> mPidTracker; 49 private Set<String> mPidExclusions; 50 ProcessStatusTracker(String[] processes)51 public ProcessStatusTracker(String[] processes) { 52 mPidTracker = new HashMap<String, Integer>(); 53 mPidExclusions = new HashSet<String>(); 54 if (processes == null) return; 55 for (String proc : processes) { 56 addMonitoredProcess(proc); 57 } 58 } 59 60 @Override addMonitoredProcess(String processName)61 public void addMonitoredProcess(String processName) { 62 if (mPidTracker.containsKey(processName)) { 63 throw new IllegalArgumentException("Process already being monitored: " + processName); 64 } 65 mPidTracker.put(processName, -1); 66 // don't track right away, until told to 67 mPidExclusions.add(processName); 68 } 69 70 @Override getProcessDetails()71 public List<ProcessDetails> getProcessDetails() { 72 if (mPidTracker == null || mPidTracker.isEmpty()) { 73 // nothing to track 74 Log.v(TAG, "getProcessDetails - No pids to track, not doing anything"); 75 return null; 76 } 77 List<ProcessDetails> ret = new ArrayList<ProcessDetails>(); 78 List<RunningAppProcessInfo> runningApps = null; 79 // Refactored to use shell commands, not ActivityManager 80 runningApps = getRunningAppProcesses(); 81 if (runningApps == null || runningApps.isEmpty()) { 82 Log.e(TAG, "Failed to retrieve list of running apps"); 83 return ret; 84 } 85 // used for keeping track of which process has died 86 Set<String> deadProcesses = new HashSet<String>(mPidTracker.keySet()); 87 Log.i(TAG, "Got running processes"); 88 for (RunningAppProcessInfo info : runningApps) { 89 if (mPidTracker.containsKey(info.processName) 90 && !mPidExclusions.contains(info.processName)) { 91 ProcessDetails detail = new ProcessDetails(); 92 detail.processName = info.processName; 93 detail.pid0 = info.pid; 94 // this is a process that we are monitoring 95 int pid = mPidTracker.get(info.processName); 96 if (pid == -1) { 97 mPidTracker.put(info.processName, info.pid); 98 Log.d(TAG, String.format( 99 "pid detected - %s : %d", info.processName, info.pid)); 100 // all good with this process 101 deadProcesses.remove(info.processName); 102 detail.processStatus = ProcessStatus.PROC_STARTED; 103 } else if (pid == info.pid) { 104 // pid hasn't changed, all good 105 deadProcesses.remove(info.processName); 106 detail.processStatus = ProcessStatus.PROC_OK; 107 } else { 108 //pid changed 109 deadProcesses.remove(info.processName); 110 detail.processStatus = ProcessStatus.PROC_RESTARTED; 111 detail.pid1 = pid; 112 } 113 ret.add(detail); 114 } 115 } 116 for (String proc : deadProcesses) { 117 ProcessDetails detail = new ProcessDetails(); 118 detail.processName = proc; 119 // check the remaining ones are dead, or not started 120 int pid = mPidTracker.get(proc); 121 if (pid != -1) { 122 detail.processStatus = ProcessStatus.PROC_DIED; 123 } else { 124 detail.processStatus = ProcessStatus.PROC_NOT_STARTED; 125 } 126 ret.add(detail); 127 } 128 return ret; 129 } 130 131 @Override setAllowProcessTracking(String processName)132 public void setAllowProcessTracking(String processName) { 133 if (mPidTracker == null || mPidTracker.isEmpty()) { 134 // nothing to track 135 return; 136 } 137 // ignore those not under monitoring 138 if (mPidExclusions.contains(processName)) { 139 mPidExclusions.remove(processName); 140 Log.v(TAG, "Started tracking pid changes: " + processName); 141 } 142 verifyRunningProcess(); 143 } 144 145 @Override verifyRunningProcess()146 public void verifyRunningProcess() { 147 Log.i(TAG, "Getting process details"); 148 List<ProcessDetails> details = getProcessDetails(); 149 if (details == null) { 150 return; 151 } 152 for (ProcessDetails d : details) { 153 Log.v(TAG, String.format("verifyRunningProcess - proc: %s, state: %s", 154 d.processName, d.processStatus.toString())); 155 switch (d.processStatus) { 156 case PROC_OK: 157 case PROC_NOT_STARTED: 158 // no op 159 break; 160 case PROC_STARTED: 161 Log.v(TAG, String.format("Process started: %s - %d", d.processName, d.pid0)); 162 break; 163 case PROC_DIED: { 164 String msg = String.format("Process %s has died.", d.processName); 165 throw new AuptTerminator(msg); 166 } 167 case PROC_RESTARTED: { 168 String msg = String.format("Process %s restarted: %d -> %d", d.processName, 169 d.pid1, d.pid0); 170 throw new AuptTerminator(msg); 171 } 172 default: 173 break; 174 } 175 } 176 } 177 178 /** 179 * Enumerates the list of processes to be tracked and returns a list of RunningAppProcessInfo 180 * objects with pkg name and pid's set to the process's current information 181 * @result a List of RunningAppProcessInfo objects with pkg and pid set 182 */ getRunningAppProcesses()183 public List<RunningAppProcessInfo> getRunningAppProcesses() { 184 List<RunningAppProcessInfo> results = new ArrayList<RunningAppProcessInfo>(); 185 186 Set<String> procSet = mPidTracker.keySet(); 187 // Enumerate status for all currently tracked processes 188 for (String proc : procSet) { 189 // Execute shell command and parse results 190 BufferedReader stream = executeShellCommand("ps"); 191 try { 192 String line; 193 while ((line = stream.readLine()) != null) { 194 Matcher matcher = PS_PATTERN_MATCH.matcher(line); 195 // Find a line that matches the process name exactly 196 if (matcher.matches() && matcher.group(PS_PATTERN_PKG_GROUP).equals(proc)) { 197 int pid = Integer.valueOf(matcher.group(PS_PATTERN_PID_GROUP)); 198 results.add(new RunningAppProcessInfo(proc, pid, null)); 199 } 200 } 201 } catch (IOException exception) { 202 Log.e(TAG, "Error with buffered reader", exception); 203 return null; 204 } finally { 205 try { 206 if (stream != null) { 207 stream.close(); 208 } 209 } catch (IOException exception) { 210 Log.e(TAG, "Error with closing the stream", exception); 211 } 212 } 213 } 214 215 return results; 216 } 217 218 // TODO: Create subclass for shell commands used by this and GraphicsStatsMonitor 219 220 /** 221 * UiAutomation is included solely for the purpose of executing shell commands 222 */ 223 private UiAutomation mUiAutomation; 224 225 /** 226 * Executes a shell command through UiAutomation and puts the results in an 227 * InputStreamReader that is returned inside a BufferedReader. 228 * @param command the command to be executed in the adb shell 229 * @result a BufferedReader that reads the command output 230 */ executeShellCommand(String command)231 public BufferedReader executeShellCommand (String command) { 232 ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command); 233 234 BufferedReader stream = new BufferedReader(new InputStreamReader( 235 new ParcelFileDescriptor.AutoCloseInputStream(stdout))); 236 return stream; 237 } 238 239 /** 240 * Sets the UiAutomation member for shell execution 241 */ setUiAutomation(UiAutomation uiAutomation)242 public void setUiAutomation (UiAutomation uiAutomation) { 243 mUiAutomation = uiAutomation; 244 } 245 246 /** 247 * @return UiAutomation instance from Aupt instrumentation 248 */ getUiAutomation()249 public UiAutomation getUiAutomation () { 250 return mUiAutomation; 251 } 252 } 253