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 androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 30 import java.io.BufferedOutputStream; 31 import java.io.BufferedReader; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.InputStreamReader; 36 import java.io.PrintWriter; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.function.Predicate; 40 import java.util.regex.Pattern; 41 42 /** 43 * bit CtsAppTestCases:ActivityManagerProcessStateTest 44 */ 45 public class WatchUidRunner { 46 static final String TAG = "WatchUidRunner"; 47 48 public static final int CMD_PROCSTATE = 0; 49 public static final int CMD_ACTIVE = 1; 50 public static final int CMD_IDLE = 2; 51 public static final int CMD_UNCACHED = 3; 52 public static final int CMD_CACHED = 4; 53 public static final int CMD_GONE = 5; 54 public static final int CMD_CAPABILITY = 6; 55 56 public static final String STATE_PERSISTENT = "PER"; 57 public static final String STATE_PERSISTENT_UI = "PERU"; 58 public static final String STATE_TOP = "TOP"; 59 public static final String STATE_BOUND_FG_SERVICE = "BFGS"; 60 public static final String STATE_BOUND_TOP = "BTOP"; 61 public static final String STATE_FG_SERVICE_LOCATION = "FGSL"; 62 public static final String STATE_FG_SERVICE = "FGS"; 63 public static final String STATE_TOP_SLEEPING = "TPSL"; 64 public static final String STATE_IMPORTANT_FG = "IMPF"; 65 public static final String STATE_IMPORTANT_BG = "IMPB"; 66 public static final String STATE_TRANSIENT_BG = "TRNB"; 67 public static final String STATE_BACKUP = "BKUP"; 68 public static final String STATE_HEAVY_WEIGHT = "HVY"; 69 public static final String STATE_SERVICE = "SVC"; 70 public static final String STATE_RECEIVER = "RCVR"; 71 public static final String STATE_HOME = "HOME"; 72 public static final String STATE_LAST = "LAST"; 73 public static final String STATE_CACHED_ACTIVITY = "CAC"; 74 public static final String STATE_CACHED_ACTIVITY_CLIENT = "CACC"; 75 public static final String STATE_CACHED_RECENT = "CRE"; 76 public static final String STATE_CACHED_EMPTY = "CEM"; 77 public static final String STATE_NONEXISTENT = "NONE"; 78 79 static final String[] COMMAND_TO_STRING = new String[] { 80 "procstate", "active", "idle", "uncached", "cached", "gone", "capability" 81 }; 82 83 // Index of each value in the `am watch-uid` output line. 84 static final int CMD_INDEX = 1; 85 static final int PROCSTATE_INDEX = 2; 86 static final int CAPABILITY_INDEX = 6; 87 88 final Instrumentation mInstrumentation; 89 final int mUid; 90 final String mUidStr; 91 final long mDefaultWaitTime; 92 final Pattern mSpaceSplitter; 93 final ParcelFileDescriptor mReadFd; 94 final FileInputStream mReadStream; 95 final BufferedReader mReadReader; 96 final ParcelFileDescriptor mWriteFd; 97 final FileOutputStream mWriteStream; 98 final PrintWriter mWritePrinter; 99 final Thread mReaderThread; 100 101 // Shared state is protected by this. 102 final ArrayList<String[]> mPendingLines = new ArrayList<>(); 103 104 boolean mStopping; 105 WatchUidRunner(Instrumentation instrumentation, int uid)106 public WatchUidRunner(Instrumentation instrumentation, int uid) { 107 this(instrumentation, uid, 5*1000); 108 } 109 WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime)110 public WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime) { 111 this(instrumentation, uid, defaultWaitTime, 0); 112 } 113 WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime, int capabilityMask)114 public WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime, 115 int capabilityMask) { 116 mInstrumentation = instrumentation; 117 mUid = uid; 118 mUidStr = Integer.toString(uid); 119 mDefaultWaitTime = defaultWaitTime; 120 mSpaceSplitter = Pattern.compile("\\s+"); 121 final String maskString = capabilityMask == 0 ? "" : " --mask " + capabilityMask; 122 ParcelFileDescriptor[] pfds = instrumentation.getUiAutomation().executeShellCommandRw( 123 "am watch-uids --oom " + uid + maskString); 124 mReadFd = pfds[0]; 125 mReadStream = new ParcelFileDescriptor.AutoCloseInputStream(mReadFd); 126 mReadReader = new BufferedReader(new InputStreamReader(mReadStream)); 127 mWriteFd = pfds[1]; 128 mWriteStream = new ParcelFileDescriptor.AutoCloseOutputStream(mWriteFd); 129 mWritePrinter = new PrintWriter(new BufferedOutputStream(mWriteStream)); 130 // Executing a shell command is asynchronous but we can't proceed further with the test 131 // until the 'watch-uids' cmd is executed. 132 waitUntilUidObserverReady(); 133 mReaderThread = new ReaderThread(); 134 mReaderThread.start(); 135 } 136 waitUntilUidObserverReady()137 private void waitUntilUidObserverReady() { 138 try { 139 final String line = mReadReader.readLine(); 140 assertTrue("Unexpected output: " + line, line.startsWith("Watching uid states")); 141 } catch (IOException e) { 142 fail("Error occurred " + e); 143 } 144 } 145 expect(int cmd, String procState)146 public void expect(int cmd, String procState) { 147 expect(cmd, procState, mDefaultWaitTime); 148 } 149 expect(int cmd, String procState, long timeout)150 public void expect(int cmd, String procState, long timeout) { 151 long waitUntil = SystemClock.uptimeMillis() + timeout; 152 String[] line = waitForNextLine(waitUntil, cmd, procState, 0); 153 if (!COMMAND_TO_STRING[cmd].equals(line[CMD_INDEX])) { 154 String msg = "Expected cmd " + COMMAND_TO_STRING[cmd] 155 + " uid " + mUid + " but next report was " + Arrays.toString(line); 156 Log.d(TAG, msg); 157 logRemainingLines(); 158 throw new IllegalStateException(msg); 159 } 160 if (procState != null && (line.length < 3 || !procState.equals(line[PROCSTATE_INDEX]))) { 161 String msg = "Expected procstate " + procState 162 + " uid " + mUid + " but next report was " + Arrays.toString(line); 163 Log.d(TAG, msg); 164 logRemainingLines(); 165 throw new IllegalStateException(msg); 166 } 167 Log.d(TAG, "Got expected: " + Arrays.toString(line)); 168 } 169 waitFor(int cmd)170 public void waitFor(int cmd) { 171 final WatchUidPredicate predicate = new WatchUidPredicate.Builder(cmd).build(); 172 waitFor(predicate, null); 173 } 174 waitFor(int cmd, long timeout)175 public void waitFor(int cmd, long timeout) { 176 final WatchUidPredicate predicate = new WatchUidPredicate.Builder(cmd).build(); 177 waitFor(predicate, null, timeout); 178 } 179 waitFor(int cmd, String procState)180 public void waitFor(int cmd, String procState) { 181 final WatchUidPredicate predicate = new WatchUidPredicate.Builder(cmd) 182 .setExpectedProcState(procState) 183 .build(); 184 waitFor(predicate, null); 185 } 186 waitFor(int cmd, String procState, Integer capability)187 public void waitFor(int cmd, String procState, Integer capability) { 188 final WatchUidPredicate predicate = new WatchUidPredicate.Builder(cmd) 189 .setExpectedProcState(procState) 190 .setExpectedCapability(capability) 191 .build(); 192 waitFor(predicate, null); 193 } 194 waitFor(int cmd, String procState, long timeout)195 public void waitFor(int cmd, String procState, long timeout) { 196 final WatchUidPredicate predicate = new WatchUidPredicate.Builder(cmd) 197 .setExpectedProcState(procState) 198 .build(); 199 waitFor(predicate, null, timeout); 200 } 201 waitFor(int cmd, String procState, Integer capability, long timeout)202 public void waitFor(int cmd, String procState, Integer capability, long timeout) { 203 final WatchUidPredicate predicate = new WatchUidPredicate.Builder(cmd) 204 .setExpectedProcState(procState) 205 .setExpectedCapability(capability) 206 .build(); 207 waitFor(predicate, null, timeout); 208 } 209 210 /** 211 * Waits for the `am watch-uid` command to output a line that matches the provided predicate. 212 * 213 * @param expectedPredicate the waitFor will return once this predicate returns true. 214 * @param failurePredicate an {@link IllegalStateException} will be thrown if this predicate 215 * returns true before the {@code expectedPredicate}. 216 */ waitFor(@onNull WatchUidPredicate expectedPredicate, @Nullable WatchUidPredicate failurePredicate)217 public void waitFor(@NonNull WatchUidPredicate expectedPredicate, 218 @Nullable WatchUidPredicate failurePredicate) { 219 waitFor(expectedPredicate, failurePredicate, mDefaultWaitTime); 220 } 221 222 /** 223 * Waits for the `am watch-uid` command to output a line that matches the provided predicate. 224 * 225 * @param expectedPredicate the waitFor will return once this predicate returns true. 226 * @param failurePredicate an {@link IllegalStateException} will be thrown if this predicate 227 * returns true before the {@code expectedPredicate}. 228 * @param timeout an {@link IllegalStateException} will be thrown if this timeout (in 229 * milliseconds) is exceeded. 230 */ waitFor(@onNull WatchUidPredicate expectedPredicate, @Nullable WatchUidPredicate failurePredicate, long timeout)231 public void waitFor(@NonNull WatchUidPredicate expectedPredicate, 232 @Nullable WatchUidPredicate failurePredicate, long timeout) { 233 final int cmd = expectedPredicate.cmd; 234 final String procState = expectedPredicate.procState; 235 final Integer capability = expectedPredicate.capability; 236 Log.i(TAG, "waitFor(cmd=" + cmd + ", procState=" + procState + ", capability=" + capability 237 + ", timeout=" + timeout + ")"); 238 long waitUntil = SystemClock.uptimeMillis() + timeout; 239 while (true) { 240 String[] line = waitForNextLine(waitUntil, cmd, procState, capability); 241 if (expectedPredicate.test(line)) { 242 Log.d(TAG, "Waited for: " + Arrays.toString(line)); 243 return; 244 } else if (failurePredicate != null && failurePredicate.test(line)) { 245 String msg = "Unexpected line hit: uid=" + mUidStr 246 + " cmd=" + COMMAND_TO_STRING[failurePredicate.cmd] 247 + " procState=" + failurePredicate.procState 248 + " capability=" + failurePredicate.capability 249 + " (Expected:" 250 + " cmd=" + COMMAND_TO_STRING[cmd] 251 + " procState=" + procState 252 + " capability=" + capability 253 + ")"; 254 Log.d(TAG, msg); 255 throw new IllegalStateException(msg); 256 } else { 257 Log.d(TAG, "Skipping because not " + COMMAND_TO_STRING[cmd] + ": " 258 + Arrays.toString(line)); 259 } 260 } 261 } 262 logRemainingLines()263 void logRemainingLines() { 264 synchronized (mPendingLines) { 265 while (mPendingLines.size() > 0) { 266 String[] res = mPendingLines.remove(0); 267 if (res[0].startsWith("#")) { 268 Log.d(TAG, "Remaining: " + res[0]); 269 } else { 270 Log.d(TAG, "Remaining: " + Arrays.toString(res)); 271 } 272 } 273 } 274 } 275 clearHistory()276 public void clearHistory() { 277 synchronized (mPendingLines) { 278 mPendingLines.clear(); 279 } 280 } 281 waitForNextLine(long waitUntil, int cmd, String procState, Integer capability)282 String[] waitForNextLine(long waitUntil, int cmd, String procState, Integer capability) { 283 synchronized (mPendingLines) { 284 while (true) { 285 while (mPendingLines.size() == 0) { 286 long now = SystemClock.uptimeMillis(); 287 if (now >= waitUntil) { 288 String msg = "Timed out waiting for next line: uid=" + mUidStr 289 + " cmd=" + COMMAND_TO_STRING[cmd] + " procState=" + procState 290 + " capability=" + capability; 291 Log.d(TAG, msg); 292 throw new IllegalStateException(msg); 293 } 294 try { 295 mPendingLines.wait(waitUntil - now); 296 } catch (InterruptedException e) { 297 Thread.currentThread().interrupt(); 298 } 299 } 300 String[] res = mPendingLines.remove(0); 301 if (res[0].startsWith("#")) { 302 Log.d(TAG, "Note: " + res[0]); 303 } else { 304 Log.v(TAG, "LINE: " + Arrays.toString(res)); 305 return res; 306 } 307 } 308 } 309 } 310 finish()311 public void finish() { 312 synchronized (mPendingLines) { 313 mStopping = true; 314 } 315 mWritePrinter.println("q"); 316 try { 317 mWriteStream.close(); 318 } catch (IOException e) { 319 } 320 try { 321 mReadStream.close(); 322 } catch (IOException e) { 323 } 324 } 325 326 final class ReaderThread extends Thread { 327 String mLastReadLine; 328 329 @Override run()330 public void run() { 331 String[] line; 332 try { 333 while ((line = readNextLine()) != null) { 334 boolean comment = line.length == 1 && line[0].startsWith("#"); 335 if (!comment) { 336 if (line.length < 2) { 337 Log.d(TAG, "Skipping too short: " + mLastReadLine); 338 continue; 339 } 340 if (!line[0].equals(mUidStr)) { 341 Log.d(TAG, "Skipping ignored uid: " + mLastReadLine); 342 continue; 343 } 344 } 345 //Log.d(TAG, "Enqueueing: " + mLastReadLine); 346 synchronized (mPendingLines) { 347 if (mStopping) { 348 return; 349 } 350 mPendingLines.add(line); 351 mPendingLines.notifyAll(); 352 } 353 } 354 } catch (IOException e) { 355 Log.w(TAG, "Failed reading", e); 356 } 357 } 358 readNextLine()359 String[] readNextLine() throws IOException { 360 mLastReadLine = mReadReader.readLine(); 361 if (mLastReadLine == null) { 362 return null; 363 } 364 if (mLastReadLine.startsWith("#")) { 365 return new String[] { mLastReadLine }; 366 } 367 return mSpaceSplitter.split(mLastReadLine); 368 } 369 } 370 371 public static final class WatchUidPredicate implements Predicate<String[]> { 372 public final int cmd; 373 @Nullable public final String procState; 374 @Nullable public final Integer capability; 375 WatchUidPredicate(Builder builder)376 private WatchUidPredicate(Builder builder) { 377 cmd = builder.mCmd; 378 procState = builder.mProcState; 379 capability = builder.mCapability; 380 } 381 382 /** 383 * Returns true if the tokenized watch-uid line matches the expected values. 384 */ test(String[] line)385 public boolean test(String[] line) { 386 if (COMMAND_TO_STRING[cmd].equals(line[CMD_INDEX])) { 387 if (procState == null && capability == null) { 388 return true; 389 } 390 if (cmd == CMD_PROCSTATE) { 391 if (procState != null && capability != null) { 392 if (procState.equals(line[PROCSTATE_INDEX]) && capability.toString().equals( 393 line[CAPABILITY_INDEX])) { 394 return true; 395 } 396 } else if (procState != null) { 397 if (procState.equals(line[PROCSTATE_INDEX])) { 398 return true; 399 } 400 } else if (capability != null) { 401 if (capability.toString().equals(line[CAPABILITY_INDEX])) { 402 return true; 403 } 404 } 405 } else { 406 if (procState != null && procState.equals(line[PROCSTATE_INDEX])) { 407 return true; 408 } 409 } 410 } 411 return false; 412 } 413 414 public static class Builder { 415 private final int mCmd; 416 private String mProcState = null; 417 private Integer mCapability = null; 418 Builder(int cmd)419 public Builder(int cmd) { 420 mCmd = cmd; 421 } 422 423 /** 424 * Set the optional expected state ProcState to test against. 425 */ setExpectedProcState(@ullable String procState)426 public Builder setExpectedProcState(@Nullable String procState) { 427 mProcState = procState; 428 return this; 429 } 430 431 /** 432 * Set the optional expected state process capability to test against. 433 */ setExpectedCapability(@ullable Integer capability)434 public Builder setExpectedCapability(@Nullable Integer capability) { 435 mCapability = capability; 436 return this; 437 } 438 439 /** 440 * Build the predicate. 441 */ build()442 public WatchUidPredicate build() { 443 return new WatchUidPredicate(this); 444 } 445 } 446 } 447 } 448