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     public static final int CMD_CAPABILITY = 6;
51 
52     public static final String STATE_PERSISTENT = "PER";
53     public static final String STATE_PERSISTENT_UI = "PERU";
54     public static final String STATE_TOP = "TOP";
55     public static final String STATE_BOUND_FG_SERVICE = "BFGS";
56     public static final String STATE_BOUND_TOP = "BTOP";
57     public static final String STATE_FG_SERVICE_LOCATION = "FGSL";
58     public static final String STATE_FG_SERVICE = "FGS";
59     public static final String STATE_TOP_SLEEPING = "TPSL";
60     public static final String STATE_IMPORTANT_FG = "IMPF";
61     public static final String STATE_IMPORTANT_BG = "IMPB";
62     public static final String STATE_TRANSIENT_BG = "TRNB";
63     public static final String STATE_BACKUP = "BKUP";
64     public static final String STATE_HEAVY_WEIGHT = "HVY";
65     public static final String STATE_SERVICE = "SVC";
66     public static final String STATE_RECEIVER = "RCVR";
67     public static final String STATE_HOME = "HOME";
68     public static final String STATE_LAST = "LAST";
69     public static final String STATE_CACHED_ACTIVITY = "CAC";
70     public static final String STATE_CACHED_ACTIVITY_CLIENT = "CACC";
71     public static final String STATE_CACHED_RECENT = "CRE";
72     public static final String STATE_CACHED_EMPTY = "CEM";
73     public static final String STATE_NONEXISTENT = "NONE";
74 
75     static final String[] COMMAND_TO_STRING = new String[] {
76             "procstate", "active", "idle", "uncached", "cached", "gone", "capability"
77     };
78 
79     final Instrumentation mInstrumentation;
80     final int mUid;
81     final String mUidStr;
82     final long mDefaultWaitTime;
83     final Pattern mSpaceSplitter;
84     final ParcelFileDescriptor mReadFd;
85     final FileInputStream mReadStream;
86     final BufferedReader mReadReader;
87     final ParcelFileDescriptor mWriteFd;
88     final FileOutputStream mWriteStream;
89     final PrintWriter mWritePrinter;
90     final Thread mReaderThread;
91 
92     // Shared state is protected by this.
93     final ArrayList<String[]> mPendingLines = new ArrayList<>();
94 
95     boolean mStopping;
96 
WatchUidRunner(Instrumentation instrumentation, int uid)97     public WatchUidRunner(Instrumentation instrumentation, int uid) {
98         this(instrumentation, uid, 5*1000);
99     }
100 
WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime)101     public WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime) {
102         this(instrumentation, uid, defaultWaitTime, 0);
103     }
104 
WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime, int capabilityMask)105     public WatchUidRunner(Instrumentation instrumentation, int uid, long defaultWaitTime,
106             int capabilityMask) {
107         mInstrumentation = instrumentation;
108         mUid = uid;
109         mUidStr = Integer.toString(uid);
110         mDefaultWaitTime = defaultWaitTime;
111         mSpaceSplitter = Pattern.compile("\\s+");
112         final String maskString = capabilityMask == 0 ? "" : " --mask " + capabilityMask;
113         ParcelFileDescriptor[] pfds = instrumentation.getUiAutomation().executeShellCommandRw(
114                 "am watch-uids --oom " + uid + maskString);
115         mReadFd = pfds[0];
116         mReadStream = new ParcelFileDescriptor.AutoCloseInputStream(mReadFd);
117         mReadReader = new BufferedReader(new InputStreamReader(mReadStream));
118         mWriteFd = pfds[1];
119         mWriteStream = new ParcelFileDescriptor.AutoCloseOutputStream(mWriteFd);
120         mWritePrinter = new PrintWriter(new BufferedOutputStream(mWriteStream));
121         // Executing a shell command is asynchronous but we can't proceed further with the test
122         // until the 'watch-uids' cmd is executed.
123         waitUntilUidObserverReady();
124         mReaderThread = new ReaderThread();
125         mReaderThread.start();
126     }
127 
waitUntilUidObserverReady()128     private void waitUntilUidObserverReady() {
129         try {
130             final String line = mReadReader.readLine();
131             assertTrue("Unexpected output: " + line, line.startsWith("Watching uid states"));
132         } catch (IOException e) {
133             fail("Error occurred " + e);
134         }
135     }
136 
expect(int cmd, String procState)137     public void expect(int cmd, String procState) {
138         expect(cmd, procState, mDefaultWaitTime);
139     }
140 
expect(int cmd, String procState, long timeout)141     public void expect(int cmd, String procState, long timeout) {
142         long waitUntil = SystemClock.uptimeMillis() + timeout;
143         String[] line = waitForNextLine(waitUntil, cmd, procState, 0);
144         if (!COMMAND_TO_STRING[cmd].equals(line[1])) {
145             String msg = "Expected cmd " + COMMAND_TO_STRING[cmd]
146                     + " uid " + mUid + " but next report was " + Arrays.toString(line);
147             Log.d(TAG, msg);
148             logRemainingLines();
149             throw new IllegalStateException(msg);
150         }
151         if (procState != null && (line.length < 3 || !procState.equals(line[2]))) {
152             String msg = "Expected procstate " + procState
153                     + " uid " + mUid + " but next report was " + Arrays.toString(line);
154             Log.d(TAG, msg);
155             logRemainingLines();
156             throw new IllegalStateException(msg);
157         }
158         Log.d(TAG, "Got expected: " + Arrays.toString(line));
159     }
160 
waitFor(int cmd)161     public void waitFor(int cmd) {
162         waitFor(cmd, null, null, mDefaultWaitTime);
163     }
164 
waitFor(int cmd, long timeout)165     public void waitFor(int cmd, long timeout) {
166         waitFor(cmd, null, null, timeout);
167     }
168 
waitFor(int cmd, String procState)169     public void waitFor(int cmd, String procState) {
170         waitFor(cmd, procState, null, mDefaultWaitTime);
171     }
172 
waitFor(int cmd, String procState, Integer capability)173     public void waitFor(int cmd, String procState, Integer capability) {
174         waitFor(cmd, procState, capability, mDefaultWaitTime);
175     }
176 
waitFor(int cmd, String procState, long timeout)177     public void waitFor(int cmd, String procState, long timeout) {
178         waitFor(cmd, procState, null, timeout);
179     }
180 
waitFor(int cmd, String procState, Integer capability, long timeout)181     public void waitFor(int cmd, String procState, Integer capability, long timeout) {
182         Log.i(TAG, "waitFor(cmd=" + cmd + ", procState=" + procState + ", capability=" + capability
183                 + ", timeout=" + timeout + ")");
184         long waitUntil = SystemClock.uptimeMillis() + timeout;
185         while (true) {
186             String[] line = waitForNextLine(waitUntil, cmd, procState, capability);
187             if (COMMAND_TO_STRING[cmd].equals(line[1])) {
188                 if (procState == null && capability == null) {
189                     Log.d(TAG, "Waited for: " + Arrays.toString(line));
190                     return;
191                 }
192                 if (cmd == CMD_PROCSTATE) {
193                     if (procState != null && capability != null) {
194                         if (procState.equals(line[2]) && capability.toString().equals(line[6])) {
195                             Log.d(TAG, "Waited for: " + Arrays.toString(line));
196                             return;
197                         }
198                     } else if (procState != null) {
199                         if (procState.equals(line[2])) {
200                             Log.d(TAG, "Waited for: " + Arrays.toString(line));
201                             return;
202                         }
203                     } else if (capability != null) {
204                         if (capability.toString().equals(line[6])) {
205                             Log.d(TAG, "Waited for: " + Arrays.toString(line));
206                             return;
207                         }
208                     }
209                 } else {
210                     if (procState != null
211                             && procState.equals(line[2])) {
212                         Log.d(TAG, "Waited for: " + Arrays.toString(line));
213                         return;
214                     }
215                 }
216                 Log.d(TAG, "Skipping because procstate not " + procState + ": "
217                         + Arrays.toString(line));
218             } else {
219                 Log.d(TAG, "Skipping because not " + COMMAND_TO_STRING[cmd] + ": "
220                         + Arrays.toString(line));
221             }
222         }
223     }
224 
logRemainingLines()225     void logRemainingLines() {
226         synchronized (mPendingLines) {
227             while (mPendingLines.size() > 0) {
228                 String[] res = mPendingLines.remove(0);
229                 if (res[0].startsWith("#")) {
230                     Log.d(TAG, "Remaining: " + res[0]);
231                 } else {
232                     Log.d(TAG, "Remaining: " + Arrays.toString(res));
233                 }
234             }
235         }
236     }
237 
clearHistory()238     public void clearHistory() {
239         synchronized (mPendingLines) {
240             mPendingLines.clear();
241         }
242     }
243 
waitForNextLine(long waitUntil, int cmd, String procState, Integer capability)244     String[] waitForNextLine(long waitUntil, int cmd, String procState, Integer capability) {
245         synchronized (mPendingLines) {
246             while (true) {
247                 while (mPendingLines.size() == 0) {
248                     long now = SystemClock.uptimeMillis();
249                     if (now >= waitUntil) {
250                         String msg = "Timed out waiting for next line: uid=" + mUidStr
251                                 + " cmd=" + COMMAND_TO_STRING[cmd] + " procState=" + procState
252                                 + " capability=" + capability;
253                         Log.d(TAG, msg);
254                         throw new IllegalStateException(msg);
255                     }
256                     try {
257                         mPendingLines.wait(waitUntil - now);
258                     } catch (InterruptedException e) {
259                         Thread.currentThread().interrupt();
260                     }
261                 }
262                 String[] res = mPendingLines.remove(0);
263                 if (res[0].startsWith("#")) {
264                     Log.d(TAG, "Note: " + res[0]);
265                 } else {
266                     Log.v(TAG, "LINE: " + Arrays.toString(res));
267                     return res;
268                 }
269             }
270         }
271     }
272 
finish()273     public void finish() {
274         synchronized (mPendingLines) {
275             mStopping = true;
276         }
277         mWritePrinter.println("q");
278         try {
279             mWriteStream.close();
280         } catch (IOException e) {
281         }
282         try {
283             mReadStream.close();
284         } catch (IOException e) {
285         }
286     }
287 
288     final class ReaderThread extends Thread {
289         String mLastReadLine;
290 
291         @Override
run()292         public void run() {
293             String[] line;
294             try {
295                 while ((line = readNextLine()) != null) {
296                     boolean comment = line.length == 1 && line[0].startsWith("#");
297                     if (!comment) {
298                         if (line.length < 2) {
299                             Log.d(TAG, "Skipping too short: " + mLastReadLine);
300                             continue;
301                         }
302                         if (!line[0].equals(mUidStr)) {
303                             Log.d(TAG, "Skipping ignored uid: " + mLastReadLine);
304                             continue;
305                         }
306                     }
307                     //Log.d(TAG, "Enqueueing: " + mLastReadLine);
308                     synchronized (mPendingLines) {
309                         if (mStopping) {
310                             return;
311                         }
312                         mPendingLines.add(line);
313                         mPendingLines.notifyAll();
314                     }
315                 }
316             } catch (IOException e) {
317                 Log.w(TAG, "Failed reading", e);
318             }
319         }
320 
readNextLine()321         String[] readNextLine() throws IOException {
322             mLastReadLine = mReadReader.readLine();
323             if (mLastReadLine == null) {
324                 return null;
325             }
326             if (mLastReadLine.startsWith("#")) {
327                 return new String[] { mLastReadLine };
328             }
329             return mSpaceSplitter.split(mLastReadLine);
330         }
331     }
332 }
333