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