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