1 /*
2  * Copyright (C) 2019 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 com.android.compatibility.common.util;
18 
19 import android.app.Instrumentation;
20 import android.os.ParcelFileDescriptor;
21 import android.os.SystemClock;
22 import android.text.TextUtils;
23 import android.util.ArraySet;
24 import android.util.Log;
25 
26 import java.io.BufferedOutputStream;
27 import java.io.BufferedReader;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.InputStreamReader;
31 import java.io.IOException;
32 import java.io.PrintWriter;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 
36 /**
37  * A utility class interact with "am monitor"
38  */
39 public final class AmMonitor {
40     private static final String TAG = "AmMonitor";
41     public static final String WAIT_FOR_EARLY_ANR =
42             "Waiting after early ANR...  available commands:";
43     public static final String WAIT_FOR_ANR =
44             "Waiting after ANR...  available commands:";
45     public static final String WAIT_FOR_CRASHED =
46             "Waiting after crash...  available commands:";
47     public static final String MONITOR_READY =
48             "Monitoring activity manager...  available commands:";
49 
50     /**
51      * Command for the {@link #sendCommand}: continue the process
52      */
53     public static final String CMD_CONTINUE = "c";
54 
55     /**
56      * Command for the {@link #sendCommand}: kill the process
57      */
58     public static final String CMD_KILL = "k";
59 
60     /**
61      * Command for the {@link #sendCommand}: quit the monitor
62      */
63     public static final String CMD_QUIT = "q";
64 
65     private final Instrumentation mInstrumentation;
66     private final ParcelFileDescriptor mReadFd;
67     private final FileInputStream mReadStream;
68     private final BufferedReader mReadReader;
69     private final ParcelFileDescriptor mWriteFd;
70     private final FileOutputStream mWriteStream;
71     private final PrintWriter mWritePrinter;
72     private final Thread mReaderThread;
73 
74     private final ArraySet<String> mNotExpected = new ArraySet<>();
75     private final ArrayList<String> mPendingLines = new ArrayList<>();
76 
77     /**
78      * Construct an instance of this class.
79      */
AmMonitor(final Instrumentation instrumentation, final String[] notExpected)80     public AmMonitor(final Instrumentation instrumentation, final String[] notExpected) {
81         mInstrumentation = instrumentation;
82         ParcelFileDescriptor[] pfds = instrumentation.getUiAutomation()
83                 .executeShellCommandRw("am monitor");
84         mReadFd = pfds[0];
85         mReadStream = new ParcelFileDescriptor.AutoCloseInputStream(mReadFd);
86         mReadReader = new BufferedReader(new InputStreamReader(mReadStream));
87         mWriteFd = pfds[1];
88         mWriteStream = new ParcelFileDescriptor.AutoCloseOutputStream(mWriteFd);
89         mWritePrinter = new PrintWriter(new BufferedOutputStream(mWriteStream));
90         if (notExpected != null) {
91             mNotExpected.addAll(Arrays.asList(notExpected));
92         }
93         mReaderThread = new ReaderThread();
94         mReaderThread.start();
95         waitFor(MONITOR_READY, 3600000L);
96     }
97 
98     /**
99      * Wait for the given output.
100      *
101      * @return true if it was successful, false if it got a timeout.
102      */
waitFor(final String expected, final long timeout)103     public boolean waitFor(final String expected, final long timeout) {
104         final long waitUntil = SystemClock.uptimeMillis() + timeout;
105         synchronized (mPendingLines) {
106             while (true) {
107                 while (mPendingLines.size() == 0) {
108                     final long now = SystemClock.uptimeMillis();
109                     if (now >= waitUntil) {
110                         String msg = "Timed out waiting for next line: expected=" + expected;
111                         Log.d(TAG, msg);
112                         throw new IllegalStateException(msg);
113                     }
114                     try {
115                         mPendingLines.wait(waitUntil - now);
116                     } catch (InterruptedException e) {
117                     }
118                 }
119                 final String line = mPendingLines.remove(0);
120                 if (TextUtils.equals(line, expected)) {
121                     return true;
122                 } else if (TextUtils.equals(line, WAIT_FOR_EARLY_ANR)
123                         || TextUtils.equals(line, WAIT_FOR_ANR)
124                         || TextUtils.equals(line, WAIT_FOR_CRASHED)) {
125                     // If we are getting any of the unexpected state,
126                     // for example, get a crash while waiting for an ANR,
127                     // it could be from another unrelated process, kill it directly.
128                     sendCommand(CMD_KILL);
129                 }
130             }
131         }
132     }
133 
134     /**
135      * Finish the monitor and close the streams.
136      */
finish()137     public void finish() {
138         sendCommand(CMD_QUIT);
139         try {
140             mWriteStream.close();
141         } catch (IOException e) {
142         }
143         try {
144             mReadStream.close();
145         } catch (IOException e) {
146         }
147     }
148 
149     /**
150      * Send the command to the interactive command.
151      *
152      * @param cmd could be {@link #CMD_KILL}, {@link #CMD_QUIT} or {@link #CMD_CONTINUE}.
153      */
sendCommand(final String cmd)154     public void sendCommand(final String cmd) {
155         synchronized (mPendingLines) {
156             mWritePrinter.println(cmd);
157             mWritePrinter.flush();
158         }
159     }
160 
161     private final class ReaderThread extends Thread {
162         @Override
run()163         public void run() {
164             try {
165                 String line;
166                 while ((line = mReadReader.readLine()) != null) {
167                     Log.i(TAG, "debug: " + line);
168                     if (mNotExpected.contains(line)) {
169                         // If we are getting any of the unexpected state,
170                         // for example, get a crash while waiting for an ANR,
171                         // it could be from another unrelated process, kill it directly.
172                         sendCommand(CMD_KILL);
173                         continue;
174                     }
175                     synchronized (mPendingLines) {
176                         mPendingLines.add(line);
177                         mPendingLines.notifyAll();
178                     }
179                 }
180             } catch (IOException e) {
181                 Log.w(TAG, "Failed reading", e);
182             }
183         }
184     }
185 }
186