1 /* 2 * Copyright 2016, 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.test.crashcollector; 18 19 import android.app.IActivityController; 20 import android.app.IActivityManager; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.os.IBinder; 24 import android.os.IBinder.DeathRecipient; 25 import android.os.Process; 26 import android.os.RemoteException; 27 import android.os.ServiceManager; 28 import android.os.SystemClock; 29 import android.util.Log; 30 31 import java.io.File; 32 import java.text.SimpleDateFormat; 33 import java.util.Date; 34 import java.util.HashSet; 35 36 /** 37 * Main class for the crash collector that installs an activity controller to monitor app errors 38 */ 39 public class Collector { 40 41 private static final String LOG_TAG = "CrashCollector"; 42 private static final long CHECK_AM_INTERVAL_MS = 5 * 1000; 43 private static final long MAX_CHECK_AM_TIMEOUT_MS = 30 * 1000; 44 private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss"); 45 private static final File TOMBSTONES_PATH = new File("/data/tombstones"); 46 private HashSet<String> mTombstones = null; 47 48 /** 49 * Command-line entry point. 50 * 51 * @param args The command-line arguments 52 */ main(String[] args)53 public static void main(String[] args) { 54 // Set the process name showing in "ps" or "top" 55 Process.setArgV0("android.test.crashcollector"); 56 57 int resultCode = (new Collector()).run(args); 58 System.exit(resultCode); 59 } 60 61 /** 62 * Command execution entry point 63 * @param args 64 * @return 65 * @throws RemoteException 66 */ run(String[] args)67 public int run(String[] args) { 68 // recipient for activity manager death so that command can survive runtime restart 69 final IBinder.DeathRecipient death = new DeathRecipient() { 70 @Override 71 public void binderDied() { 72 synchronized (this) { 73 notifyAll(); 74 } 75 } 76 }; 77 IBinder am = blockUntilSystemRunning(MAX_CHECK_AM_TIMEOUT_MS); 78 if (am == null) { 79 print("FATAL: Cannot get activity manager, is system running?"); 80 return -1; 81 } 82 IActivityController controller = new CrashCollector(); 83 do { 84 try { 85 // set activity controller 86 IActivityManager iam = IActivityManager.Stub.asInterface(am); 87 iam.setActivityController(controller, false); 88 // register death recipient for activity manager 89 am.linkToDeath(death, 0); 90 } catch (RemoteException re) { 91 print("FATAL: cannot set activity controller, is system running?"); 92 re.printStackTrace(); 93 return -1; 94 } 95 // monitor runtime restart (crash/kill of system server) 96 synchronized (death) { 97 while (am.isBinderAlive()) { 98 try { 99 Log.d(LOG_TAG, "Monitoring death of system server."); 100 death.wait(); 101 } catch (InterruptedException e) { 102 // ignore 103 } 104 } 105 Log.w(LOG_TAG, "Detected crash of system server."); 106 am = blockUntilSystemRunning(MAX_CHECK_AM_TIMEOUT_MS); 107 } 108 } while (true); 109 // for now running indefinitely, until a better mechanism is found to signal shutdown 110 } 111 print(String line)112 private void print(String line) { 113 System.err.println(String.format("%s %s", TIME_FORMAT.format(new Date()), line)); 114 } 115 116 /** 117 * Blocks until system server is running, or timeout has reached 118 * @param timeout 119 * @return 120 */ blockUntilSystemRunning(long timeout)121 private IBinder blockUntilSystemRunning(long timeout) { 122 // waiting for activity manager to come back 123 long start = SystemClock.uptimeMillis(); 124 IBinder am = null; 125 while (SystemClock.uptimeMillis() - start < MAX_CHECK_AM_TIMEOUT_MS) { 126 am = ServiceManager.checkService(Context.ACTIVITY_SERVICE); 127 if (am != null) { 128 break; 129 } else { 130 Log.d(LOG_TAG, "activity manager not ready yet, continue waiting."); 131 try { 132 Thread.sleep(CHECK_AM_INTERVAL_MS); 133 } catch (InterruptedException e) { 134 // break out of current loop upon interruption 135 break; 136 } 137 } 138 } 139 return am; 140 } 141 checkNativeCrashes()142 private boolean checkNativeCrashes() { 143 String[] tombstones = TOMBSTONES_PATH.list(); 144 145 // shortcut path for usually empty directory, so we don't waste even 146 // more objects 147 if ((tombstones == null) || (tombstones.length == 0)) { 148 mTombstones = null; 149 return false; 150 } 151 152 // use set logic to look for new files 153 HashSet<String> newStones = new HashSet<String>(); 154 for (String x : tombstones) { 155 newStones.add(x); 156 } 157 158 boolean result = (mTombstones == null) || !mTombstones.containsAll(newStones); 159 160 // keep the new list for the next time 161 mTombstones = newStones; 162 163 return result; 164 } 165 166 private class CrashCollector extends IActivityController.Stub { 167 168 @Override activityStarting(Intent intent, String pkg)169 public boolean activityStarting(Intent intent, String pkg) throws RemoteException { 170 // check native crashes when we have a chance 171 if (checkNativeCrashes()) { 172 print("NATIVE: new tombstones"); 173 } 174 return true; 175 } 176 177 @Override activityResuming(String pkg)178 public boolean activityResuming(String pkg) throws RemoteException { 179 // check native crashes when we have a chance 180 if (checkNativeCrashes()) { 181 print("NATIVE: new tombstones"); 182 } 183 return true; 184 } 185 186 @Override appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace)187 public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, 188 long timeMillis, String stackTrace) throws RemoteException { 189 if (processName == null) { 190 print("CRASH: null process name, assuming system"); 191 } else { 192 print("CRASH: " + processName); 193 } 194 return false; 195 } 196 197 @Override appEarlyNotResponding(String processName, int pid, String annotation)198 public int appEarlyNotResponding(String processName, int pid, String annotation) 199 throws RemoteException { 200 // ignore 201 return 0; 202 } 203 204 @Override appNotResponding(String processName, int pid, String processStats)205 public int appNotResponding(String processName, int pid, String processStats) 206 throws RemoteException { 207 print("ANR: " + processName); 208 return -1; 209 } 210 211 @Override systemNotResponding(String msg)212 public int systemNotResponding(String msg) throws RemoteException { 213 print("WATCHDOG: " + msg); 214 return -1; 215 } 216 } 217 } 218