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