xref: /aosp_15_r20/cts/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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