1 /* 2 * Copyright (C) 2017 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.app.cts.android.app.cts.tools; 18 19 import static org.junit.Assert.assertTrue; 20 import static org.junit.Assert.fail; 21 22 import android.app.Instrumentation; 23 import android.os.ParcelFileDescriptor; 24 import android.os.SystemClock; 25 import android.util.Log; 26 27 import java.io.BufferedOutputStream; 28 import java.io.BufferedReader; 29 import java.io.FileInputStream; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 import java.io.InputStreamReader; 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.regex.Pattern; 37 38 /** 39 * bit CtsAppTestCases:ActivityManagerProcessStateTest 40 */ 41 public class WatchUidRunner { 42 static final String TAG = "WatchUidRunner"; 43 44 public static final int CMD_PROCSTATE = 0; 45 public static final int CMD_ACTIVE = 1; 46 public static final int CMD_IDLE = 2; 47 public static final int CMD_UNCACHED = 3; 48 public static final int CMD_CACHED = 4; 49 public static final int CMD_GONE = 5; 50 51 public static final String STATE_PERSISTENT = "PER"; 52 public static final String STATE_PERSISTENT_UI = "PERU"; 53 public static final String STATE_TOP = "TOP"; 54 public static final String STATE_BOUND_FG_SERVICE = "BFGS"; 55 public static final String STATE_FG_SERVICE = "FGS"; 56 public static final String STATE_TOP_SLEEPING = "TPSL"; 57 public static final String STATE_IMPORTANT_FG = "IMPF"; 58 public static final String STATE_IMPORTANT_BG = "IMPB"; 59 public static final String STATE_TRANSIENT_BG = "TRNB"; 60 public static final String STATE_BACKUP = "BKUP"; 61 public static final String STATE_HEAVY_WEIGHT = "HVY"; 62 public static final String STATE_SERVICE = "SVC"; 63 public static final String STATE_RECEIVER = "RCVR"; 64 public static final String STATE_HOME = "HOME"; 65 public static final String STATE_LAST = "LAST"; 66 public static final String STATE_CACHED_ACTIVITY = "CAC"; 67 public static final String STATE_CACHED_ACTIVITY_CLIENT = "CACC"; 68 public static final String STATE_CACHED_RECENT = "CRE"; 69 public static final String STATE_CACHED_EMPTY = "CEM"; 70 public static final String STATE_NONEXISTENT = "NONE"; 71 72 static final String[] COMMAND_TO_STRING = new String[] { 73 "procstate", "active", "idle", "uncached", "cached", "gone" 74 }; 75 76 final Instrumentation mInstrumentation; 77 final int mUid; 78 final String mUidStr; 79 final long mDefaultWaitTime; 80 final Pattern mSpaceSplitter; 81 final ParcelFileDescriptor mReadFd; 82 final FileInputStream mReadStream; 83 final BufferedReader mReadReader; 84 final ParcelFileDescriptor mWriteFd; 85 final FileOutputStream mWriteStream; 86 final PrintWriter mWritePrinter; 87 final Thread mReaderThread; 88 89 // Shared state is protected by this. 90 final ArrayList<String[]> mPendingLines = new ArrayList<>(); 91 92 boolean mStopping; 93 WatchUidRunner(Instrumentation instrumentation, int uid)94 public WatchUidRunner(Instrumentation instrumentation, int uid) { 95 this(instrumentation, uid, 5*1000); 96 } 97 WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime)98 public WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime) { 99 mInstrumentation = instrumentation; 100 mUid = uid; 101 mUidStr = Integer.toString(uid); 102 mDefaultWaitTime = defaultWaitTime; 103 mSpaceSplitter = Pattern.compile("\\s+"); 104 ParcelFileDescriptor[] pfds = instrumentation.getUiAutomation().executeShellCommandRw( 105 "am watch-uids --oom " + uid); 106 mReadFd = pfds[0]; 107 mReadStream = new ParcelFileDescriptor.AutoCloseInputStream(mReadFd); 108 mReadReader = new BufferedReader(new InputStreamReader(mReadStream)); 109 mWriteFd = pfds[1]; 110 mWriteStream = new ParcelFileDescriptor.AutoCloseOutputStream(mWriteFd); 111 mWritePrinter = new PrintWriter(new BufferedOutputStream(mWriteStream)); 112 // Executing a shell command is asynchronous but we can't proceed further with the test 113 // until the 'watch-uids' cmd is executed. 114 waitUntilUidObserverReady(); 115 mReaderThread = new ReaderThread(); 116 mReaderThread.start(); 117 } 118 waitUntilUidObserverReady()119 private void waitUntilUidObserverReady() { 120 try { 121 final String line = mReadReader.readLine(); 122 assertTrue("Unexpected output: " + line, line.startsWith("Watching uid states")); 123 } catch (IOException e) { 124 fail("Error occurred " + e); 125 } 126 } 127 expect(int cmd, String procState)128 public void expect(int cmd, String procState) { 129 expect(cmd, procState, mDefaultWaitTime); 130 } 131 expect(int cmd, String procState, long timeout)132 public void expect(int cmd, String procState, long timeout) { 133 long waitUntil = SystemClock.uptimeMillis() + timeout; 134 String[] line = waitForNextLine(waitUntil, cmd, procState); 135 if (!COMMAND_TO_STRING[cmd].equals(line[1])) { 136 String msg = "Expected cmd " + COMMAND_TO_STRING[cmd] 137 + " uid " + mUid + " but next report was " + Arrays.toString(line); 138 Log.d(TAG, msg); 139 logRemainingLines(); 140 throw new IllegalStateException(msg); 141 } 142 if (procState != null && (line.length < 3 || !procState.equals(line[2]))) { 143 String msg = "Expected procstate " + procState 144 + " uid " + mUid + " but next report was " + Arrays.toString(line); 145 Log.d(TAG, msg); 146 logRemainingLines(); 147 throw new IllegalStateException(msg); 148 } 149 Log.d(TAG, "Got expected: " + Arrays.toString(line)); 150 } 151 waitFor(int cmd, String procState)152 public void waitFor(int cmd, String procState) { 153 waitFor(cmd, procState, mDefaultWaitTime); 154 } 155 waitFor(int cmd, String procState, long timeout)156 public void waitFor(int cmd, String procState, long timeout) { 157 long waitUntil = SystemClock.uptimeMillis() + timeout; 158 while (true) { 159 String[] line = waitForNextLine(waitUntil, cmd, procState); 160 if (COMMAND_TO_STRING[cmd].equals(line[1])) { 161 if (procState == null) { 162 Log.d(TAG, "Waited for: " + Arrays.toString(line)); 163 return; 164 } 165 if (line.length >= 3 && procState.equals(line[2])) { 166 Log.d(TAG, "Waited for: " + Arrays.toString(line)); 167 return; 168 } else { 169 Log.d(TAG, "Skipping because procstate not " + procState + ": " 170 + Arrays.toString(line)); 171 } 172 } else { 173 Log.d(TAG, "Skipping because not " + COMMAND_TO_STRING[cmd] + ": " 174 + Arrays.toString(line)); 175 } 176 } 177 } 178 logRemainingLines()179 void logRemainingLines() { 180 synchronized (mPendingLines) { 181 while (mPendingLines.size() > 0) { 182 String[] res = mPendingLines.remove(0); 183 if (res[0].startsWith("#")) { 184 Log.d(TAG, "Remaining: " + res[0]); 185 } else { 186 Log.d(TAG, "Remaining: " + Arrays.toString(res)); 187 } 188 } 189 } 190 } 191 waitForNextLine(long waitUntil, int cmd, String procState)192 String[] waitForNextLine(long waitUntil, int cmd, String procState) { 193 synchronized (mPendingLines) { 194 while (true) { 195 while (mPendingLines.size() == 0) { 196 long now = SystemClock.uptimeMillis(); 197 if (now >= waitUntil) { 198 String msg = "Timed out waiting for next line: " 199 + "cmd=" + COMMAND_TO_STRING[cmd] + " procState=" + procState; 200 Log.d(TAG, msg); 201 throw new IllegalStateException(msg); 202 } 203 try { 204 mPendingLines.wait(waitUntil - now); 205 } catch (InterruptedException e) { 206 } 207 } 208 String[] res = mPendingLines.remove(0); 209 if (res[0].startsWith("#")) { 210 Log.d(TAG, "Note: " + res[0]); 211 } else { 212 return res; 213 } 214 } 215 } 216 } 217 finish()218 public void finish() { 219 synchronized (mPendingLines) { 220 mStopping = true; 221 } 222 mWritePrinter.println("q"); 223 try { 224 mWriteStream.close(); 225 } catch (IOException e) { 226 } 227 try { 228 mReadStream.close(); 229 } catch (IOException e) { 230 } 231 } 232 233 final class ReaderThread extends Thread { 234 String mLastReadLine; 235 236 @Override run()237 public void run() { 238 String[] line; 239 try { 240 while ((line = readNextLine()) != null) { 241 boolean comment = line.length == 1 && line[0].startsWith("#"); 242 if (!comment) { 243 if (line.length < 2) { 244 Log.d(TAG, "Skipping too short: " + mLastReadLine); 245 continue; 246 } 247 if (!line[0].equals(mUidStr)) { 248 Log.d(TAG, "Skipping ignored uid: " + mLastReadLine); 249 continue; 250 } 251 } 252 //Log.d(TAG, "Enqueueing: " + mLastReadLine); 253 synchronized (mPendingLines) { 254 if (mStopping) { 255 return; 256 } 257 mPendingLines.add(line); 258 mPendingLines.notifyAll(); 259 } 260 } 261 } catch (IOException e) { 262 Log.w(TAG, "Failed reading", e); 263 } 264 } 265 readNextLine()266 String[] readNextLine() throws IOException { 267 mLastReadLine = mReadReader.readLine(); 268 if (mLastReadLine == null) { 269 return null; 270 } 271 if (mLastReadLine.startsWith("#")) { 272 return new String[] { mLastReadLine }; 273 } 274 return mSpaceSplitter.split(mLastReadLine); 275 } 276 } 277 } 278