blob: 87f23859ae8f5be50e55fb7bc5f762f0c061e056 [file] [log] [blame]
/*
* Copyright 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.test.crashcollector;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
/**
* Main class for the crash collector that installs an activity controller to monitor app errors
*/
public class Collector {
private static final String LOG_TAG = "CrashCollector";
private static final long CHECK_AM_INTERVAL_MS = 5 * 1000;
private static final long MAX_CHECK_AM_TIMEOUT_MS = 30 * 1000;
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss");
private static final File TOMBSTONES_PATH = new File("/data/tombstones");
private HashSet<String> mTombstones = null;
/**
* Command-line entry point.
*
* @param args The command-line arguments
*/
public static void main(String[] args) {
// Set the process name showing in "ps" or "top"
Process.setArgV0("android.test.crashcollector");
int resultCode = (new Collector()).run(args);
System.exit(resultCode);
}
/**
* Command execution entry point
* @param args
* @return
* @throws RemoteException
*/
public int run(String[] args) {
// recipient for activity manager death so that command can survive runtime restart
final IBinder.DeathRecipient death = new DeathRecipient() {
@Override
public void binderDied() {
synchronized (this) {
notifyAll();
}
}
};
IBinder am = blockUntilSystemRunning(MAX_CHECK_AM_TIMEOUT_MS);
if (am == null) {
print("FATAL: Cannot get activity manager, is system running?");
return -1;
}
IActivityController controller = new CrashCollector();
do {
try {
// set activity controller
IActivityManager iam = IActivityManager.Stub.asInterface(am);
iam.setActivityController(controller, false);
// register death recipient for activity manager
am.linkToDeath(death, 0);
} catch (RemoteException re) {
print("FATAL: cannot set activity controller, is system running?");
re.printStackTrace();
return -1;
}
// monitor runtime restart (crash/kill of system server)
synchronized (death) {
while (am.isBinderAlive()) {
try {
Log.d(LOG_TAG, "Monitoring death of system server.");
death.wait();
} catch (InterruptedException e) {
// ignore
}
}
Log.w(LOG_TAG, "Detected crash of system server.");
am = blockUntilSystemRunning(MAX_CHECK_AM_TIMEOUT_MS);
}
} while (true);
// for now running indefinitely, until a better mechanism is found to signal shutdown
}
private void print(String line) {
System.err.println(String.format("%s %s", TIME_FORMAT.format(new Date()), line));
}
/**
* Blocks until system server is running, or timeout has reached
* @param timeout
* @return
*/
private IBinder blockUntilSystemRunning(long timeout) {
// waiting for activity manager to come back
long start = SystemClock.uptimeMillis();
IBinder am = null;
while (SystemClock.uptimeMillis() - start < MAX_CHECK_AM_TIMEOUT_MS) {
am = ServiceManager.checkService(Context.ACTIVITY_SERVICE);
if (am != null) {
break;
} else {
Log.d(LOG_TAG, "activity manager not ready yet, continue waiting.");
try {
Thread.sleep(CHECK_AM_INTERVAL_MS);
} catch (InterruptedException e) {
// break out of current loop upon interruption
break;
}
}
}
return am;
}
private boolean checkNativeCrashes() {
String[] tombstones = TOMBSTONES_PATH.list();
// shortcut path for usually empty directory, so we don't waste even
// more objects
if ((tombstones == null) || (tombstones.length == 0)) {
mTombstones = null;
return false;
}
// use set logic to look for new files
HashSet<String> newStones = new HashSet<String>();
for (String x : tombstones) {
newStones.add(x);
}
boolean result = (mTombstones == null) || !mTombstones.containsAll(newStones);
// keep the new list for the next time
mTombstones = newStones;
return result;
}
private class CrashCollector extends IActivityController.Stub {
@Override
public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
// check native crashes when we have a chance
if (checkNativeCrashes()) {
print("NATIVE: new tombstones");
}
return true;
}
@Override
public boolean activityResuming(String pkg) throws RemoteException {
// check native crashes when we have a chance
if (checkNativeCrashes()) {
print("NATIVE: new tombstones");
}
return true;
}
@Override
public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
long timeMillis, String stackTrace) throws RemoteException {
if (processName == null) {
print("CRASH: null process name, assuming system");
} else {
print("CRASH: " + processName);
}
return false;
}
@Override
public int appEarlyNotResponding(String processName, int pid, String annotation)
throws RemoteException {
// ignore
return 0;
}
@Override
public int appNotResponding(String processName, int pid, String processStats)
throws RemoteException {
print("ANR: " + processName);
return -1;
}
@Override
public int systemNotResponding(String msg) throws RemoteException {
print("WATCHDOG: " + msg);
return -1;
}
}
}