• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.server.am;
18 
19 import static android.app.ActivityManager.RunningAppProcessInfo.procStateToImportance;
20 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
21 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
22 
23 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
24 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
25 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
26 
27 import android.annotation.CurrentTimeMillisLong;
28 import android.annotation.Nullable;
29 import android.app.ApplicationExitInfo;
30 import android.app.ApplicationExitInfo.Reason;
31 import android.app.ApplicationExitInfo.SubReason;
32 import android.app.IAppTraceRetriever;
33 import android.content.BroadcastReceiver;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.pm.PackageManager;
38 import android.icu.text.SimpleDateFormat;
39 import android.os.Binder;
40 import android.os.FileUtils;
41 import android.os.Handler;
42 import android.os.Looper;
43 import android.os.Message;
44 import android.os.ParcelFileDescriptor;
45 import android.os.Process;
46 import android.os.SystemProperties;
47 import android.os.UserHandle;
48 import android.system.OsConstants;
49 import android.text.TextUtils;
50 import android.util.ArrayMap;
51 import android.util.ArraySet;
52 import android.util.AtomicFile;
53 import android.util.Pair;
54 import android.util.Pools.SynchronizedPool;
55 import android.util.Slog;
56 import android.util.SparseArray;
57 import android.util.proto.ProtoInputStream;
58 import android.util.proto.ProtoOutputStream;
59 import android.util.proto.WireTypeMismatchException;
60 
61 import com.android.internal.annotations.GuardedBy;
62 import com.android.internal.annotations.VisibleForTesting;
63 import com.android.internal.app.ProcessMap;
64 import com.android.internal.util.ArrayUtils;
65 import com.android.internal.util.FrameworkStatsLog;
66 import com.android.internal.util.function.pooled.PooledLambda;
67 import com.android.server.IoThread;
68 import com.android.server.LocalServices;
69 import com.android.server.ServiceThread;
70 import com.android.server.SystemServiceManager;
71 import com.android.server.os.NativeTombstoneManager;
72 
73 import java.io.BufferedInputStream;
74 import java.io.BufferedOutputStream;
75 import java.io.File;
76 import java.io.FileInputStream;
77 import java.io.FileNotFoundException;
78 import java.io.FileOutputStream;
79 import java.io.IOException;
80 import java.io.PrintWriter;
81 import java.util.ArrayList;
82 import java.util.Collections;
83 import java.util.Date;
84 import java.util.List;
85 import java.util.Optional;
86 import java.util.concurrent.TimeUnit;
87 import java.util.concurrent.atomic.AtomicBoolean;
88 import java.util.function.BiConsumer;
89 import java.util.function.BiFunction;
90 import java.util.function.Consumer;
91 import java.util.function.Function;
92 import java.util.function.Predicate;
93 import java.util.function.Supplier;
94 import java.util.zip.GZIPOutputStream;
95 
96 /**
97  * A class to manage all the {@link android.app.ApplicationExitInfo} records.
98  */
99 public final class AppExitInfoTracker {
100     private static final String TAG = TAG_WITH_CLASS_NAME ? "AppExitInfoTracker" : TAG_AM;
101 
102     /**
103      * Interval of persisting the app exit info to persistent storage.
104      */
105     private static final long APP_EXIT_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30);
106 
107     /** These are actions that the forEach* should take after each iteration */
108     @VisibleForTesting static final int FOREACH_ACTION_NONE = 0;
109     @VisibleForTesting static final int FOREACH_ACTION_REMOVE_ITEM = 1;
110     @VisibleForTesting static final int FOREACH_ACTION_STOP_ITERATION = 2;
111 
112     private static final int APP_EXIT_RAW_INFO_POOL_SIZE = 8;
113 
114     /**
115      * How long we're going to hold before logging an app exit info into statsd;
116      * we do this is because there could be multiple sources signaling an app exit, we'd like to
117      * gather the most accurate information before logging into statsd.
118      */
119     private static final long APP_EXIT_INFO_STATSD_LOG_DEBOUNCE = TimeUnit.SECONDS.toMillis(15);
120 
121     @VisibleForTesting
122     static final String APP_EXIT_STORE_DIR = "procexitstore";
123 
124     @VisibleForTesting
125     static final String APP_EXIT_INFO_FILE = "procexitinfo";
126 
127     private static final String APP_TRACE_FILE_SUFFIX = ".gz";
128 
129     @VisibleForTesting final Object mLock = new Object();
130 
131     /**
132      * Initialized in {@link #init} and read-only after that.
133      */
134     private ActivityManagerService mService;
135 
136     /**
137      * Initialized in {@link #init} and read-only after that.
138      */
139     private KillHandler mKillHandler;
140 
141     /**
142      * The task to persist app process exit info
143      */
144     @GuardedBy("mLock")
145     private Runnable mAppExitInfoPersistTask = null;
146 
147     /**
148      * Last time(in ms) since epoch that the app exit info was persisted into persistent storage.
149      */
150     @GuardedBy("mLock")
151     private long mLastAppExitInfoPersistTimestamp = 0L;
152 
153     /**
154      * Retention policy: keep up to X historical exit info per package.
155      *
156      * Initialized in {@link #init} and read-only after that.
157      * Not lock is needed.
158      */
159     private int mAppExitInfoHistoryListSize;
160 
161     /*
162      * PackageName/uid -> [pid/info, ...] holder, the uid here is the package uid.
163      */
164     @GuardedBy("mLock")
165     private final ProcessMap<AppExitInfoContainer> mData;
166 
167     /** A pool of raw {@link android.app.ApplicationExitInfo} records. */
168     @GuardedBy("mLock")
169     private final SynchronizedPool<ApplicationExitInfo> mRawRecordsPool;
170 
171     /**
172      * Wheather or not we've loaded the historical app process exit info from
173      * persistent storage.
174      */
175     @VisibleForTesting
176     AtomicBoolean mAppExitInfoLoaded = new AtomicBoolean();
177 
178     /**
179      * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}.
180      */
181     @GuardedBy("mLock")
182     final ArrayList<ApplicationExitInfo> mTmpInfoList = new ArrayList<ApplicationExitInfo>();
183 
184     /**
185      * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}.
186      */
187     @GuardedBy("mLock")
188     final ArrayList<ApplicationExitInfo> mTmpInfoList2 = new ArrayList<ApplicationExitInfo>();
189 
190     /**
191      * The path to the directory which includes the historical proc exit info file
192      * as specified in {@link #mProcExitInfoFile}, as well as the associated trace files.
193      */
194     @VisibleForTesting
195     File mProcExitStoreDir;
196 
197     /**
198      * The path to the historical proc exit info file, persisted in the storage.
199      */
200     @VisibleForTesting
201     File mProcExitInfoFile;
202 
203     /**
204      * Mapping between the isolated UID to its application uid.
205      */
206     final IsolatedUidRecords mIsolatedUidRecords =
207             new IsolatedUidRecords();
208 
209     /**
210      * Bookkeeping app process exit info from Zygote.
211      */
212     final AppExitInfoExternalSource mAppExitInfoSourceZygote =
213             new AppExitInfoExternalSource("zygote", null);
214 
215     /**
216      * Bookkeeping low memory kills info from lmkd.
217      */
218     final AppExitInfoExternalSource mAppExitInfoSourceLmkd =
219             new AppExitInfoExternalSource("lmkd", ApplicationExitInfo.REASON_LOW_MEMORY);
220 
221     /**
222      * The active per-UID/PID state data set by
223      * {@link android.app.ActivityManager#setProcessStateSummary};
224      * these state data are to be "claimed" when its process dies, by then the data will be moved
225      * from this list to the new instance of ApplicationExitInfo.
226      *
227      * <p> The mapping here is UID -> PID -> state </p>
228      *
229      * @see android.app.ActivityManager#setProcessStateSummary(byte[])
230      */
231     @GuardedBy("mLock")
232     final SparseArray<SparseArray<byte[]>> mActiveAppStateSummary = new SparseArray<>();
233 
234     /**
235      * The active per-UID/PID trace file when an ANR occurs but the process hasn't been killed yet,
236      * each record is a path to the actual trace file;  these files are to be "claimed"
237      * when its process dies, by then the "ownership" of the files will be transferred
238      * from this list to the new instance of ApplicationExitInfo.
239      *
240      * <p> The mapping here is UID -> PID -> file </p>
241      */
242     @GuardedBy("mLock")
243     final SparseArray<SparseArray<File>> mActiveAppTraces = new SparseArray<>();
244 
245     /**
246      * The implementation of the interface IAppTraceRetriever.
247      */
248     final AppTraceRetriever mAppTraceRetriever = new AppTraceRetriever();
249 
AppExitInfoTracker()250     AppExitInfoTracker() {
251         mData = new ProcessMap<AppExitInfoContainer>();
252         mRawRecordsPool = new SynchronizedPool<ApplicationExitInfo>(APP_EXIT_RAW_INFO_POOL_SIZE);
253     }
254 
init(ActivityManagerService service)255     void init(ActivityManagerService service) {
256         mService = service;
257         ServiceThread thread = new ServiceThread(TAG + ":killHandler",
258                 THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
259         thread.start();
260         mKillHandler = new KillHandler(thread.getLooper());
261 
262         mProcExitStoreDir = new File(SystemServiceManager.ensureSystemDir(), APP_EXIT_STORE_DIR);
263         if (!FileUtils.createDir(mProcExitStoreDir)) {
264             Slog.e(TAG, "Unable to create " + mProcExitStoreDir);
265             return;
266         }
267         mProcExitInfoFile = new File(mProcExitStoreDir, APP_EXIT_INFO_FILE);
268 
269         mAppExitInfoHistoryListSize = service.mContext.getResources().getInteger(
270                 com.android.internal.R.integer.config_app_exit_info_history_list_size);
271     }
272 
onSystemReady()273     void onSystemReady() {
274         registerForUserRemoval();
275         registerForPackageRemoval();
276         IoThread.getHandler().post(() -> {
277             // Read the sysprop set by lmkd and set this to persist so app could read it.
278             SystemProperties.set("persist.sys.lmk.reportkills",
279                     Boolean.toString(SystemProperties.getBoolean("sys.lmk.reportkills", false)));
280             loadExistingProcessExitInfo();
281         });
282     }
283 
scheduleNoteProcessDied(final ProcessRecord app)284     void scheduleNoteProcessDied(final ProcessRecord app) {
285         if (app == null || app.info == null) {
286             return;
287         }
288 
289         if (!mAppExitInfoLoaded.get()) {
290             return;
291         }
292         mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED,
293                 obtainRawRecord(app, System.currentTimeMillis())).sendToTarget();
294     }
295 
scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason, final @SubReason int subReason, final String msg)296     void scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason,
297             final @SubReason int subReason, final String msg) {
298         if (!mAppExitInfoLoaded.get()) {
299             return;
300         }
301         if (app == null || app.info == null) {
302             return;
303         }
304 
305         ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis());
306         raw.setReason(reason);
307         raw.setSubReason(subReason);
308         raw.setDescription(msg);
309         mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
310     }
311 
scheduleNoteAppRecoverableCrash(final ProcessRecord app)312     void scheduleNoteAppRecoverableCrash(final ProcessRecord app) {
313         if (!mAppExitInfoLoaded.get() || app == null || app.info == null) return;
314 
315         ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis());
316         raw.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);
317         raw.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
318         raw.setDescription("recoverable_crash");
319         mKillHandler.obtainMessage(KillHandler.MSG_APP_RECOVERABLE_CRASH, raw).sendToTarget();
320     }
321 
scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason, final @SubReason int subReason, final String msg)322     void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
323             final @SubReason int subReason, final String msg) {
324         if (!mAppExitInfoLoaded.get()) {
325             return;
326         }
327         ProcessRecord app;
328         synchronized (mService.mPidsSelfLocked) {
329             app = mService.mPidsSelfLocked.get(pid);
330         }
331         if (app == null) {
332             if (DEBUG_PROCESSES) {
333                 Slog.w(TAG, "Skipping saving the kill reason for pid " + pid
334                         + "(uid=" + uid + ") since its process record is not found");
335             }
336         } else {
337             scheduleNoteAppKill(app, reason, subReason, msg);
338         }
339     }
340 
341     interface LmkdKillListener {
342         /**
343          * Called when there is a process kill by lmkd.
344          */
onLmkdKillOccurred(int pid, int uid)345         void onLmkdKillOccurred(int pid, int uid);
346     }
347 
setLmkdKillListener(final LmkdKillListener listener)348     void setLmkdKillListener(final LmkdKillListener listener) {
349         synchronized (mLock) {
350             mAppExitInfoSourceLmkd.setOnProcDiedListener((pid, uid) ->
351                     listener.onLmkdKillOccurred(pid, uid));
352         }
353     }
354 
355     /** Called when there is a low memory kill */
scheduleNoteLmkdProcKilled(final int pid, final int uid, final int rssKb)356     void scheduleNoteLmkdProcKilled(final int pid, final int uid, final int rssKb) {
357         mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid, Long.valueOf(rssKb))
358                 .sendToTarget();
359     }
360 
scheduleChildProcDied(int pid, int uid, int status)361     private void scheduleChildProcDied(int pid, int uid, int status) {
362         mKillHandler.obtainMessage(KillHandler.MSG_CHILD_PROC_DIED, pid, uid, (Integer) status)
363                 .sendToTarget();
364     }
365 
366     /** Calls when zygote sends us SIGCHLD */
handleZygoteSigChld(int pid, int uid, int status)367     void handleZygoteSigChld(int pid, int uid, int status) {
368         if (DEBUG_PROCESSES) {
369             Slog.i(TAG, "Got SIGCHLD from zygote: pid=" + pid + ", uid=" + uid
370                     + ", status=" + Integer.toHexString(status));
371         }
372         scheduleChildProcDied(pid, uid, status);
373     }
374 
375     /**
376      * Main routine to create or update the {@link android.app.ApplicationExitInfo} for the given
377      * ProcessRecord, also query the zygote and lmkd records to make the information more accurate.
378      */
379     @VisibleForTesting
380     @GuardedBy("mLock")
handleNoteProcessDiedLocked(final ApplicationExitInfo raw)381     void handleNoteProcessDiedLocked(final ApplicationExitInfo raw) {
382         if (raw != null) {
383             if (DEBUG_PROCESSES) {
384                 Slog.i(TAG, "Update process exit info for " + raw.getPackageName()
385                         + "(" + raw.getPid() + "/u" + raw.getRealUid() + ")");
386             }
387 
388             ApplicationExitInfo info = getExitInfoLocked(raw.getPackageName(),
389                     raw.getPackageUid(), raw.getPid());
390 
391             // query zygote and lmkd to get the exit info, and clear the saved info
392             Pair<Long, Object> zygote = mAppExitInfoSourceZygote.remove(
393                     raw.getPid(), raw.getRealUid());
394             Pair<Long, Object> lmkd = mAppExitInfoSourceLmkd.remove(
395                     raw.getPid(), raw.getRealUid());
396             mIsolatedUidRecords.removeIsolatedUidLocked(raw.getRealUid());
397 
398             if (info == null) {
399                 info = addExitInfoLocked(raw);
400             }
401 
402             if (lmkd != null) {
403                 updateExistingExitInfoRecordLocked(info, null,
404                         ApplicationExitInfo.REASON_LOW_MEMORY, (Long) lmkd.second);
405             } else if (zygote != null) {
406                 updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null, null);
407             } else {
408                 scheduleLogToStatsdLocked(info, false);
409             }
410         }
411     }
412 
413     /**
414      * Certain types of crashes should not be updated. This could end up deleting valuable
415      * information, for example, if a test application crashes and then the `am instrument`
416      * finishes, then the crash whould be replaced with a `reason == USER_REQUESTED`
417      * ApplicationExitInfo from ActivityManager, and the original crash would be lost.
418      */
preventExitInfoUpdate(final ApplicationExitInfo exitInfo)419     private boolean preventExitInfoUpdate(final ApplicationExitInfo exitInfo) {
420         switch (exitInfo.getReason()) {
421             case ApplicationExitInfo.REASON_ANR:
422             case ApplicationExitInfo.REASON_CRASH:
423             case ApplicationExitInfo.REASON_CRASH_NATIVE:
424                 return true;
425             default:
426                 return false;
427         }
428     }
429 
430     /**
431      * Make note when ActivityManagerService decides to kill an application process.
432      */
433     @VisibleForTesting
434     @GuardedBy("mLock")
handleNoteAppKillLocked(final ApplicationExitInfo raw)435     void handleNoteAppKillLocked(final ApplicationExitInfo raw) {
436         ApplicationExitInfo info = getExitInfoLocked(
437                 raw.getPackageName(), raw.getPackageUid(), raw.getPid());
438 
439         if (info == null || preventExitInfoUpdate(info)) {
440             info = addExitInfoLocked(raw);
441         } else {
442             // Override the existing info since we have more information.
443             info.setReason(raw.getReason());
444             info.setSubReason(raw.getSubReason());
445             info.setStatus(0);
446             info.setTimestamp(System.currentTimeMillis());
447             info.setDescription(raw.getDescription());
448         }
449         scheduleLogToStatsdLocked(info, true);
450     }
451 
452     @GuardedBy("mLock")
addExitInfoLocked(ApplicationExitInfo raw)453     private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
454         if (!mAppExitInfoLoaded.get()) {
455             Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
456             return null;
457         }
458 
459         final ApplicationExitInfo info = new ApplicationExitInfo(raw);
460         final String[] packages = raw.getPackageList();
461         int uid = raw.getRealUid();
462         if (UserHandle.isIsolated(uid)) {
463             Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
464             if (k != null) {
465                 uid = k;
466             }
467         }
468         for (int i = 0; i < packages.length; i++) {
469             addExitInfoInnerLocked(packages[i], uid, info);
470         }
471 
472         // SDK sandbox exits are stored under both real and package UID
473         if (Process.isSdkSandboxUid(uid)) {
474             for (int i = 0; i < packages.length; i++) {
475                 addExitInfoInnerLocked(packages[i], raw.getPackageUid(), info);
476             }
477         }
478 
479         schedulePersistProcessExitInfo(false);
480 
481         return info;
482     }
483 
484     /**
485      * Update an existing {@link android.app.ApplicationExitInfo} record with given information.
486      */
487     @GuardedBy("mLock")
updateExistingExitInfoRecordLocked(ApplicationExitInfo info, Integer status, Integer reason, Long rssKb)488     private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info,
489             Integer status, Integer reason, Long rssKb) {
490         if (info == null || !isFresh(info.getTimestamp())) {
491             // if the record is way outdated, don't update it then (because of potential pid reuse)
492             return;
493         }
494         boolean immediateLog = false;
495         if (status != null) {
496             if (OsConstants.WIFEXITED(status)) {
497                 info.setReason(ApplicationExitInfo.REASON_EXIT_SELF);
498                 info.setStatus(OsConstants.WEXITSTATUS(status));
499                 immediateLog = true;
500             } else if (OsConstants.WIFSIGNALED(status)) {
501                 if (info.getReason() == ApplicationExitInfo.REASON_UNKNOWN) {
502                     info.setReason(ApplicationExitInfo.REASON_SIGNALED);
503                     info.setStatus(OsConstants.WTERMSIG(status));
504                 } else if (info.getReason() == ApplicationExitInfo.REASON_CRASH_NATIVE) {
505                     info.setStatus(OsConstants.WTERMSIG(status));
506                     immediateLog = true;
507                 }
508             }
509         }
510         if (reason != null) {
511             info.setReason(reason);
512             if (reason == ApplicationExitInfo.REASON_LOW_MEMORY) {
513                 immediateLog = true;
514             }
515         }
516         if (rssKb != null) {
517             info.setRss(rssKb.longValue());
518         }
519         scheduleLogToStatsdLocked(info, immediateLog);
520     }
521 
522     /**
523      * Update an existing {@link android.app.ApplicationExitInfo} record with given information.
524      *
525      * @return true if a recond is updated
526      */
527     @GuardedBy("mLock")
updateExitInfoIfNecessaryLocked( int pid, int uid, Integer status, Integer reason, Long rssKb)528     private boolean updateExitInfoIfNecessaryLocked(
529             int pid, int uid, Integer status, Integer reason, Long rssKb) {
530         Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
531         if (k != null) {
532             uid = k;
533         }
534         final int targetUid = uid;
535         // Launder the modification bit through a `final` array, as Java doesn't allow you to mutate
536         // a captured boolean inside of a lambda.
537         final boolean[] isModified = {false};
538         forEachPackageLocked((packageName, records) -> {
539             AppExitInfoContainer container = records.get(targetUid);
540             if (container == null) {
541                 return FOREACH_ACTION_NONE;
542             }
543             mTmpInfoList.clear();
544             container.getExitInfosLocked(pid, /* maxNum */ 0, mTmpInfoList);
545             if (mTmpInfoList.size() == 0) {
546                 return FOREACH_ACTION_NONE;
547             }
548 
549             for (int i = 0, size = mTmpInfoList.size(); i < size; i++) {
550                 ApplicationExitInfo info = mTmpInfoList.get(i);
551                 if (info.getRealUid() != targetUid) {
552                     continue;
553                 }
554                 // We only update the most recent `ApplicationExitInfo` for this pid, which will
555                 // always be the first one we se as `getExitInfosLocked()` returns them sorted
556                 // by most-recent-first.
557                 isModified[0] = true;
558                 updateExistingExitInfoRecordLocked(info, status, reason, rssKb);
559                 return FOREACH_ACTION_STOP_ITERATION;
560             }
561             return FOREACH_ACTION_NONE;
562         });
563         mTmpInfoList.clear();
564         return isModified[0];
565     }
566 
567     /**
568      * Get the exit info with matching package name, filterUid and filterPid (if > 0)
569      */
570     @VisibleForTesting
getExitInfo(final String packageName, final int filterUid, final int filterPid, final int maxNum, final List<ApplicationExitInfo> results)571     void getExitInfo(final String packageName, final int filterUid, final int filterPid,
572             final int maxNum, final List<ApplicationExitInfo> results) {
573         final long identity = Binder.clearCallingIdentity();
574         try {
575             synchronized (mLock) {
576                 if (!TextUtils.isEmpty(packageName)) {
577                     // Fast path - just a single package.
578                     AppExitInfoContainer container = mData.get(packageName, filterUid);
579                     if (container != null) {
580                         container.getExitInfosLocked(filterPid, maxNum, results);
581                     }
582                     return;
583                 }
584 
585                 // Slow path - get all the packages.
586                 forEachPackageLocked((name, records) -> {
587                     AppExitInfoContainer container = records.get(filterUid);
588                     if (container != null) {
589                         container.getExitInfosLocked(filterPid, /* maxNum */ 0, results);
590                     }
591                     return AppExitInfoTracker.FOREACH_ACTION_NONE;
592                 });
593 
594                 // And while the results for each package are sorted, we should
595                 // sort over and trim the quantity of global results as well.
596                 Collections.sort(
597                         results, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
598                 if (maxNum <= 0) {
599                     return;
600                 }
601 
602                 int elementsToRemove = results.size() - maxNum;
603                 for (int i = 0; i < elementsToRemove; i++) {
604                     results.removeLast();
605                 }
606             }
607         } finally {
608             Binder.restoreCallingIdentity(identity);
609         }
610     }
611 
612     /**
613      * Return the first matching exit info record, for internal use, the parameters are not supposed
614      * to be empty.
615      */
616     @GuardedBy("mLock")
getExitInfoLocked(final String packageName, final int filterUid, final int filterPid)617     private ApplicationExitInfo getExitInfoLocked(final String packageName,
618             final int filterUid, final int filterPid) {
619         mTmpInfoList.clear();
620         getExitInfo(packageName, filterUid, filterPid, 1, mTmpInfoList);
621         ApplicationExitInfo info = mTmpInfoList.size() > 0 ? mTmpInfoList.getFirst() : null;
622         mTmpInfoList.clear();
623         return info;
624     }
625 
626     @VisibleForTesting
onUserRemoved(int userId)627     void onUserRemoved(int userId) {
628         mAppExitInfoSourceZygote.removeByUserId(userId);
629         mAppExitInfoSourceLmkd.removeByUserId(userId);
630         mIsolatedUidRecords.removeByUserId(userId);
631         synchronized (mLock) {
632             removeByUserIdLocked(userId);
633             schedulePersistProcessExitInfo(true);
634         }
635     }
636 
637     @VisibleForTesting
onPackageRemoved(String packageName, int uid, boolean allUsers)638     void onPackageRemoved(String packageName, int uid, boolean allUsers) {
639         if (packageName != null) {
640             final boolean removeUid = TextUtils.isEmpty(
641                     mService.mPackageManagerInt.getNameForUid(uid));
642             synchronized (mLock) {
643                 if (removeUid) {
644                     mAppExitInfoSourceZygote.removeByUidLocked(uid, allUsers);
645                     mAppExitInfoSourceLmkd.removeByUidLocked(uid, allUsers);
646                     mIsolatedUidRecords.removeAppUid(uid, allUsers);
647                 }
648                 removePackageLocked(packageName, uid, removeUid,
649                         allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid));
650                 schedulePersistProcessExitInfo(true);
651             }
652         }
653     }
654 
registerForUserRemoval()655     private void registerForUserRemoval() {
656         IntentFilter filter = new IntentFilter();
657         filter.addAction(Intent.ACTION_USER_REMOVED);
658         mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
659             @Override
660             public void onReceive(Context context, Intent intent) {
661                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
662                 if (userId < 1) return;
663                 onUserRemoved(userId);
664             }
665         }, filter, null, mKillHandler);
666     }
667 
registerForPackageRemoval()668     private void registerForPackageRemoval() {
669         IntentFilter filter = new IntentFilter();
670         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
671         filter.addDataScheme("package");
672         mService.mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
673             @Override
674             public void onReceive(Context context, Intent intent) {
675                 boolean replacing = intent.getBooleanExtra(
676                         Intent.EXTRA_REPLACING, false);
677                 if (replacing) {
678                     return;
679                 }
680                 int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
681                 boolean allUsers = intent.getBooleanExtra(
682                         Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);
683                 onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers);
684             }
685         }, filter, null, mKillHandler);
686     }
687 
688     /**
689      * Load the existing {@link android.app.ApplicationExitInfo} records from persistent storage.
690      */
691     @VisibleForTesting
loadExistingProcessExitInfo()692     void loadExistingProcessExitInfo() {
693         if (!mProcExitInfoFile.canRead()) {
694             mAppExitInfoLoaded.set(true);
695             return;
696         }
697 
698         FileInputStream fin = null;
699         try {
700             AtomicFile af = new AtomicFile(mProcExitInfoFile);
701             fin = af.openRead();
702             ProtoInputStream proto = new ProtoInputStream(fin);
703             for (int next = proto.nextField();
704                     next != ProtoInputStream.NO_MORE_FIELDS;
705                     next = proto.nextField()) {
706                 switch (next) {
707                     case (int) AppsExitInfoProto.LAST_UPDATE_TIMESTAMP:
708                         synchronized (mLock) {
709                             mLastAppExitInfoPersistTimestamp =
710                                     proto.readLong(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP);
711                         }
712                         break;
713                     case (int) AppsExitInfoProto.PACKAGES:
714                         loadPackagesFromProto(proto, next);
715                         break;
716                 }
717             }
718         } catch (Exception e) {
719             Slog.w(TAG, "Error in loading historical app exit info from persistent storage: " + e);
720         } finally {
721             if (fin != null) {
722                 try {
723                     fin.close();
724                 } catch (IOException e) {
725                 }
726             }
727         }
728         synchronized (mLock) {
729             pruneAnrTracesIfNecessaryLocked();
730             mAppExitInfoLoaded.set(true);
731         }
732     }
733 
loadPackagesFromProto(ProtoInputStream proto, long fieldId)734     private void loadPackagesFromProto(ProtoInputStream proto, long fieldId)
735             throws IOException, WireTypeMismatchException {
736         long token = proto.start(fieldId);
737         String pkgName = "";
738         for (int next = proto.nextField();
739                 next != ProtoInputStream.NO_MORE_FIELDS;
740                 next = proto.nextField()) {
741             switch (next) {
742                 case (int) AppsExitInfoProto.Package.PACKAGE_NAME:
743                     pkgName = proto.readString(AppsExitInfoProto.Package.PACKAGE_NAME);
744                     break;
745                 case (int) AppsExitInfoProto.Package.USERS:
746                     AppExitInfoContainer container = new AppExitInfoContainer(
747                             mAppExitInfoHistoryListSize);
748                     int uid = container.readFromProto(proto, AppsExitInfoProto.Package.USERS);
749                     synchronized (mLock) {
750                         mData.put(pkgName, uid, container);
751                     }
752                     break;
753             }
754         }
755         proto.end(token);
756     }
757 
758     /**
759      * Persist the existing {@link android.app.ApplicationExitInfo} records to storage.
760      */
761     @VisibleForTesting
persistProcessExitInfo()762     void persistProcessExitInfo() {
763         AtomicFile af = new AtomicFile(mProcExitInfoFile);
764         FileOutputStream out = null;
765         long now = System.currentTimeMillis();
766         try {
767             out = af.startWrite();
768             ProtoOutputStream proto = new ProtoOutputStream(out);
769             proto.write(AppsExitInfoProto.LAST_UPDATE_TIMESTAMP, now);
770             synchronized (mLock) {
771                 forEachPackageLocked((packageName, records) -> {
772                     long token = proto.start(AppsExitInfoProto.PACKAGES);
773                     proto.write(AppsExitInfoProto.Package.PACKAGE_NAME, packageName);
774                     int uidArraySize = records.size();
775                     for (int j = 0; j < uidArraySize; j++) {
776                         records.valueAt(j).writeToProto(proto, AppsExitInfoProto.Package.USERS);
777                     }
778                     proto.end(token);
779                     return AppExitInfoTracker.FOREACH_ACTION_NONE;
780                 });
781                 mLastAppExitInfoPersistTimestamp = now;
782             }
783             proto.flush();
784             af.finishWrite(out);
785         } catch (IOException e) {
786             Slog.w(TAG, "Unable to write historical app exit info into persistent storage: " + e);
787             af.failWrite(out);
788         }
789         synchronized (mLock) {
790             mAppExitInfoPersistTask = null;
791         }
792     }
793 
794     /**
795      * Schedule a task to persist the {@link android.app.ApplicationExitInfo} records to storage.
796      */
797     @VisibleForTesting
schedulePersistProcessExitInfo(boolean immediately)798     void schedulePersistProcessExitInfo(boolean immediately) {
799         synchronized (mLock) {
800             if (mAppExitInfoPersistTask == null || immediately) {
801                 if (mAppExitInfoPersistTask != null) {
802                     IoThread.getHandler().removeCallbacks(mAppExitInfoPersistTask);
803                 }
804                 mAppExitInfoPersistTask = this::persistProcessExitInfo;
805                 IoThread.getHandler().postDelayed(mAppExitInfoPersistTask,
806                         immediately ? 0 : APP_EXIT_INFO_PERSIST_INTERVAL);
807             }
808         }
809     }
810 
811     /**
812      * Helper function for testing only.
813      */
814     @VisibleForTesting
clearProcessExitInfo(boolean removeFile)815     void clearProcessExitInfo(boolean removeFile) {
816         synchronized (mLock) {
817             if (mAppExitInfoPersistTask != null) {
818                 IoThread.getHandler().removeCallbacks(mAppExitInfoPersistTask);
819                 mAppExitInfoPersistTask = null;
820             }
821             if (removeFile && mProcExitInfoFile != null) {
822                 mProcExitInfoFile.delete();
823             }
824             mData.getMap().clear();
825             mActiveAppStateSummary.clear();
826             mActiveAppTraces.clear();
827             pruneAnrTracesIfNecessaryLocked();
828         }
829     }
830 
831     /**
832      * Helper function for shell command
833      */
clearHistoryProcessExitInfo(String packageName, int userId)834     void clearHistoryProcessExitInfo(String packageName, int userId) {
835         NativeTombstoneManager tombstoneService = LocalServices.getService(
836                 NativeTombstoneManager.class);
837         Optional<Integer> appId = Optional.empty();
838 
839         if (TextUtils.isEmpty(packageName)) {
840             synchronized (mLock) {
841                 removeByUserIdLocked(userId);
842             }
843         } else {
844             final int uid = mService.mPackageManagerInt.getPackageUid(packageName,
845                     PackageManager.MATCH_ALL, userId);
846             appId = Optional.of(UserHandle.getAppId(uid));
847             synchronized (mLock) {
848                 removePackageLocked(packageName, uid, true, userId);
849             }
850         }
851 
852         tombstoneService.purge(Optional.of(userId), appId);
853         schedulePersistProcessExitInfo(true);
854     }
855 
dumpHistoryProcessExitInfo(PrintWriter pw, String packageName)856     void dumpHistoryProcessExitInfo(PrintWriter pw, String packageName) {
857         pw.println("ACTIVITY MANAGER PROCESS EXIT INFO (dumpsys activity exit-info)");
858         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
859         synchronized (mLock) {
860             pw.println("Last Timestamp of Persistence Into Persistent Storage: "
861                     + sdf.format(new Date(mLastAppExitInfoPersistTimestamp)));
862             if (TextUtils.isEmpty(packageName)) {
863                 forEachPackageLocked((name, records) -> {
864                     dumpHistoryProcessExitInfoLocked(pw, "  ", name, records, sdf);
865                     return AppExitInfoTracker.FOREACH_ACTION_NONE;
866                 });
867             } else {
868                 SparseArray<AppExitInfoContainer> array = mData.getMap().get(packageName);
869                 if (array != null) {
870                     dumpHistoryProcessExitInfoLocked(pw, "  ", packageName, array, sdf);
871                 }
872             }
873         }
874     }
875 
876     @GuardedBy("mLock")
dumpHistoryProcessExitInfoLocked(PrintWriter pw, String prefix, String packageName, SparseArray<AppExitInfoContainer> array, SimpleDateFormat sdf)877     private void dumpHistoryProcessExitInfoLocked(PrintWriter pw, String prefix,
878             String packageName, SparseArray<AppExitInfoContainer> array,
879             SimpleDateFormat sdf) {
880         pw.println(prefix + "package: " + packageName);
881         int size = array.size();
882         for (int i = 0; i < size; i++) {
883             pw.println(prefix + "  Historical Process Exit for uid=" + array.keyAt(i));
884             array.valueAt(i).dumpLocked(pw, prefix + "    ", sdf);
885         }
886     }
887 
888     @GuardedBy("mLock")
addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info)889     private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) {
890         AppExitInfoContainer container = mData.get(packageName, uid);
891         if (container == null) {
892             container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
893             if (UserHandle.isIsolated(info.getRealUid())) {
894                 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(info.getRealUid());
895                 if (k != null) {
896                     container.mUid = k;
897                 }
898             } else {
899                 container.mUid = info.getRealUid();
900             }
901             mData.put(packageName, uid, container);
902         }
903         container.addExitInfoLocked(info);
904     }
905 
906     @GuardedBy("mLock")
scheduleLogToStatsdLocked(ApplicationExitInfo info, boolean immediate)907     private void scheduleLogToStatsdLocked(ApplicationExitInfo info, boolean immediate) {
908         if (info.isLoggedInStatsd()) {
909             return;
910         }
911         if (immediate) {
912             mKillHandler.removeMessages(KillHandler.MSG_STATSD_LOG, info);
913             performLogToStatsdLocked(info);
914         } else if (!mKillHandler.hasMessages(KillHandler.MSG_STATSD_LOG, info)) {
915             mKillHandler.sendMessageDelayed(mKillHandler.obtainMessage(
916                     KillHandler.MSG_STATSD_LOG, info), APP_EXIT_INFO_STATSD_LOG_DEBOUNCE);
917         }
918     }
919 
920     @GuardedBy("mLock")
performLogToStatsdLocked(ApplicationExitInfo info)921     private void performLogToStatsdLocked(ApplicationExitInfo info) {
922         if (info.isLoggedInStatsd()) {
923             return;
924         }
925         info.setLoggedInStatsd(true);
926         final String pkgName = info.getPackageName();
927         String processName = info.getProcessName();
928         if (TextUtils.equals(pkgName, processName)) {
929             // Omit the process name here to save space
930             processName = null;
931         } else if (processName != null && pkgName != null && processName.startsWith(pkgName)) {
932             // Strip the prefix to save space
933             processName = processName.substring(pkgName.length());
934         }
935         FrameworkStatsLog.write(FrameworkStatsLog.APP_PROCESS_DIED,
936                 info.getPackageUid(), processName, info.getReason(), info.getSubReason(),
937                 info.getImportance(), (int) info.getPss(), (int) info.getRss(),
938                 info.hasForegroundServices());
939     }
940 
941     @GuardedBy("mLock")
forEachPackageLocked( BiFunction<String, SparseArray<AppExitInfoContainer>, Integer> callback)942     private void forEachPackageLocked(
943             BiFunction<String, SparseArray<AppExitInfoContainer>, Integer> callback) {
944         if (callback != null) {
945             ArrayMap<String, SparseArray<AppExitInfoContainer>> map = mData.getMap();
946             for (int i = map.size() - 1; i >= 0; i--) {
947                 switch (callback.apply(map.keyAt(i), map.valueAt(i))) {
948                     case FOREACH_ACTION_REMOVE_ITEM:
949                         final SparseArray<AppExitInfoContainer> records = map.valueAt(i);
950                         for (int j = records.size() - 1; j >= 0; j--) {
951                             records.valueAt(j).destroyLocked();
952                         }
953                         map.removeAt(i);
954                         break;
955                     case FOREACH_ACTION_STOP_ITERATION:
956                         i = 0;
957                         break;
958                     case FOREACH_ACTION_NONE:
959                     default:
960                         break;
961                 }
962             }
963         }
964     }
965 
966     @GuardedBy("mLock")
removePackageLocked(String packageName, int uid, boolean removeUid, int userId)967     private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) {
968         if (removeUid) {
969             mActiveAppStateSummary.remove(uid);
970             final int idx = mActiveAppTraces.indexOfKey(uid);
971             if (idx >= 0) {
972                 final SparseArray<File> array = mActiveAppTraces.valueAt(idx);
973                 for (int i = array.size() - 1; i >= 0; i--) {
974                     array.valueAt(i).delete();
975                 }
976                 mActiveAppTraces.removeAt(idx);
977             }
978         }
979         ArrayMap<String, SparseArray<AppExitInfoContainer>> map = mData.getMap();
980         SparseArray<AppExitInfoContainer> array = map.get(packageName);
981         if (array == null) {
982             return;
983         }
984         if (userId == UserHandle.USER_ALL) {
985             for (int i = array.size() - 1; i >= 0; i--) {
986                 array.valueAt(i).destroyLocked();
987             }
988             mData.getMap().remove(packageName);
989         } else {
990             for (int i = array.size() - 1; i >= 0; i--) {
991                 if (UserHandle.getUserId(array.keyAt(i)) == userId) {
992                     array.valueAt(i).destroyLocked();
993                     array.removeAt(i);
994                     break;
995                 }
996             }
997             if (array.size() == 0) {
998                 map.remove(packageName);
999             }
1000         }
1001     }
1002 
1003     @GuardedBy("mLock")
removeByUserIdLocked(final int userId)1004     private void removeByUserIdLocked(final int userId) {
1005         if (userId == UserHandle.USER_ALL) {
1006             mData.getMap().clear();
1007             mActiveAppStateSummary.clear();
1008             mActiveAppTraces.clear();
1009             pruneAnrTracesIfNecessaryLocked();
1010             return;
1011         }
1012         removeFromSparse2dArray(mActiveAppStateSummary,
1013                 (v) -> UserHandle.getUserId(v) == userId, null, null);
1014         removeFromSparse2dArray(mActiveAppTraces,
1015                 (v) -> UserHandle.getUserId(v) == userId, null, (v) -> v.delete());
1016         forEachPackageLocked((packageName, records) -> {
1017             for (int i = records.size() - 1; i >= 0; i--) {
1018                 if (UserHandle.getUserId(records.keyAt(i)) == userId) {
1019                     records.valueAt(i).destroyLocked();
1020                     records.removeAt(i);
1021                     break;
1022                 }
1023             }
1024             return records.size() == 0 ? FOREACH_ACTION_REMOVE_ITEM : FOREACH_ACTION_NONE;
1025         });
1026     }
1027 
1028     @VisibleForTesting
obtainRawRecord(ProcessRecord app, @CurrentTimeMillisLong long timestamp)1029     ApplicationExitInfo obtainRawRecord(ProcessRecord app, @CurrentTimeMillisLong long timestamp) {
1030         ApplicationExitInfo info = mRawRecordsPool.acquire();
1031         if (info == null) {
1032             info = new ApplicationExitInfo();
1033         }
1034 
1035         synchronized (mService.mProcLock) {
1036             final int definingUid = app.getHostingRecord() != null
1037                     ? app.getHostingRecord().getDefiningUid() : 0;
1038             info.setPid(app.getPid());
1039             info.setRealUid(app.uid);
1040             info.setPackageUid(app.info.uid);
1041             info.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
1042             info.setProcessName(app.processName);
1043             info.setConnectionGroup(app.mServices.getConnectionGroup());
1044             info.setPackageName(app.info.packageName);
1045             info.setPackageList(app.getPackageList());
1046             info.setReason(ApplicationExitInfo.REASON_UNKNOWN);
1047             info.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
1048             info.setStatus(0);
1049             info.setImportance(procStateToImportance(app.mState.getReportedProcState()));
1050             info.setPss(app.mProfile.getLastPss());
1051             info.setRss(app.mProfile.getLastRss());
1052             info.setTimestamp(timestamp);
1053             info.setHasForegroundServices(app.mServices.hasReportedForegroundServices());
1054         }
1055 
1056         return info;
1057     }
1058 
1059     @VisibleForTesting
recycleRawRecord(ApplicationExitInfo info)1060     void recycleRawRecord(ApplicationExitInfo info) {
1061         info.setProcessName(null);
1062         info.setDescription(null);
1063         info.setPackageList(null);
1064 
1065         mRawRecordsPool.release(info);
1066     }
1067 
1068     /**
1069      * Called from {@link ActivityManagerService#setProcessStateSummary}.
1070      */
1071     @VisibleForTesting
setProcessStateSummary(int uid, final int pid, final byte[] data)1072     void setProcessStateSummary(int uid, final int pid, final byte[] data) {
1073         synchronized (mLock) {
1074             Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
1075             if (k != null) {
1076                 uid = k;
1077             }
1078             putToSparse2dArray(mActiveAppStateSummary, uid, pid, data, SparseArray::new, null);
1079         }
1080     }
1081 
1082     @VisibleForTesting
getProcessStateSummary(int uid, final int pid)1083     @Nullable byte[] getProcessStateSummary(int uid, final int pid) {
1084         synchronized (mLock) {
1085             Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
1086             if (k != null) {
1087                 uid = k;
1088             }
1089             int index = mActiveAppStateSummary.indexOfKey(uid);
1090             if (index < 0) {
1091                 return null;
1092             }
1093             return mActiveAppStateSummary.valueAt(index).get(pid);
1094         }
1095     }
1096 
1097     /**
1098      * Called from ProcessRecord when an ANR occurred and the ANR trace is taken.
1099      */
scheduleLogAnrTrace(final int pid, final int uid, final String[] packageList, final File traceFile, final long startOff, final long endOff)1100     void scheduleLogAnrTrace(final int pid, final int uid, final String[] packageList,
1101             final File traceFile, final long startOff, final long endOff) {
1102         mKillHandler.sendMessage(PooledLambda.obtainMessage(
1103                 this::handleLogAnrTrace, pid, uid, packageList,
1104                 traceFile, startOff, endOff));
1105     }
1106 
1107     /**
1108      * Copy and compress the given ANR trace file
1109      */
1110     @VisibleForTesting
handleLogAnrTrace(final int pid, int uid, final String[] packageList, final File traceFile, final long startOff, final long endOff)1111     void handleLogAnrTrace(final int pid, int uid, final String[] packageList,
1112             final File traceFile, final long startOff, final long endOff) {
1113         if (!traceFile.exists() || ArrayUtils.isEmpty(packageList)) {
1114             return;
1115         }
1116         final long size = traceFile.length();
1117         final long length = endOff - startOff;
1118         if (startOff >= size || endOff > size || length <= 0) {
1119             return;
1120         }
1121 
1122         final File outFile = new File(mProcExitStoreDir, traceFile.getName()
1123                 + APP_TRACE_FILE_SUFFIX);
1124         // Copy & compress
1125         if (copyToGzFile(traceFile, outFile, startOff, length)) {
1126             // Wrote successfully.
1127             synchronized (mLock) {
1128                 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
1129                 if (k != null) {
1130                     uid = k;
1131                 }
1132                 if (DEBUG_PROCESSES) {
1133                     Slog.i(TAG, "Stored ANR traces of " + pid + "/u" + uid + " in " + outFile);
1134                 }
1135                 boolean pending = true;
1136                 // Unlikely but possible: the app has died
1137                 for (int i = 0; i < packageList.length; i++) {
1138                     final AppExitInfoContainer container = mData.get(packageList[i], uid);
1139                     // Try to see if we could append this trace to an existing record
1140                     if (container != null && container.appendTraceIfNecessaryLocked(pid, outFile)) {
1141                         // Okay someone took it
1142                         pending = false;
1143                     }
1144                 }
1145                 if (pending) {
1146                     // Save it into a temporary list for later use (when the app dies).
1147                     putToSparse2dArray(mActiveAppTraces, uid, pid, outFile,
1148                             SparseArray::new, (v) -> v.delete());
1149                 }
1150             }
1151         }
1152     }
1153 
1154     /**
1155      * Copy the given portion of the file into a gz file.
1156      *
1157      * @param inFile The source file.
1158      * @param outFile The destination file, which will be compressed in gzip format.
1159      * @param start The start offset where the copy should start from.
1160      * @param length The number of bytes that should be copied.
1161      * @return If the copy was successful or not.
1162      */
copyToGzFile(final File inFile, final File outFile, final long start, final long length)1163     private static boolean copyToGzFile(final File inFile, final File outFile,
1164             final long start, final long length) {
1165         long remaining = length;
1166         try (
1167             BufferedInputStream in = new BufferedInputStream(new FileInputStream(inFile));
1168             GZIPOutputStream out = new GZIPOutputStream(new BufferedOutputStream(
1169                     new FileOutputStream(outFile)))) {
1170             final byte[] buffer = new byte[8192];
1171             in.skip(start);
1172             while (remaining > 0) {
1173                 int t = in.read(buffer, 0, (int) Math.min(buffer.length, remaining));
1174                 if (t < 0) {
1175                     break;
1176                 }
1177                 out.write(buffer, 0, t);
1178                 remaining -= t;
1179             }
1180         } catch (IOException e) {
1181             if (DEBUG_PROCESSES) {
1182                 Slog.e(TAG, "Error in copying ANR trace from " + inFile + " to " + outFile, e);
1183             }
1184             return false;
1185         }
1186         return remaining == 0 && outFile.exists();
1187     }
1188 
1189     /**
1190      * In case there is any orphan ANR trace file, remove it.
1191      */
1192     @GuardedBy("mLock")
pruneAnrTracesIfNecessaryLocked()1193     private void pruneAnrTracesIfNecessaryLocked() {
1194         final ArraySet<String> allFiles = new ArraySet();
1195         final File[] files = mProcExitStoreDir.listFiles((f) -> {
1196             final String name = f.getName();
1197             boolean trace = name.startsWith(StackTracesDumpHelper.ANR_FILE_PREFIX)
1198                     && name.endsWith(APP_TRACE_FILE_SUFFIX);
1199             if (trace) {
1200                 allFiles.add(name);
1201             }
1202             return trace;
1203         });
1204         if (ArrayUtils.isEmpty(files)) {
1205             return;
1206         }
1207         // Find out the owners from the existing records
1208         forEachPackageLocked((name, records) -> {
1209             for (int i = records.size() - 1; i >= 0; i--) {
1210                 final AppExitInfoContainer container = records.valueAt(i);
1211                 container.forEachRecordLocked((info) -> {
1212                     final File traceFile = info.getTraceFile();
1213                     if (traceFile != null) {
1214                         allFiles.remove(traceFile.getName());
1215                     }
1216                     return FOREACH_ACTION_NONE;
1217                 });
1218             }
1219             return AppExitInfoTracker.FOREACH_ACTION_NONE;
1220         });
1221         // See if there is any active process owns it.
1222         forEachSparse2dArray(mActiveAppTraces, (v) -> allFiles.remove(v.getName()));
1223 
1224         // Remove orphan traces if nobody claims it.
1225         for (int i = allFiles.size() - 1; i >= 0; i--) {
1226             (new File(mProcExitStoreDir, allFiles.valueAt(i))).delete();
1227         }
1228     }
1229 
1230     /**
1231      * A utility function to add the given value to the given 2d SparseArray
1232      */
putToSparse2dArray(final SparseArray<T> array, final int outerKey, final int innerKey, final U value, final Supplier<T> newInstance, final Consumer<U> actionToOldValue)1233     private static <T extends SparseArray<U>, U> void putToSparse2dArray(final SparseArray<T> array,
1234             final int outerKey, final int innerKey, final U value, final Supplier<T> newInstance,
1235             final Consumer<U> actionToOldValue) {
1236         int idx = array.indexOfKey(outerKey);
1237         T innerArray = null;
1238         if (idx < 0) {
1239             innerArray = newInstance.get();
1240             array.put(outerKey, innerArray);
1241         } else {
1242             innerArray = array.valueAt(idx);
1243         }
1244         idx = innerArray.indexOfKey(innerKey);
1245         if (idx >= 0) {
1246             if (actionToOldValue != null) {
1247                 actionToOldValue.accept(innerArray.valueAt(idx));
1248             }
1249             innerArray.setValueAt(idx, value);
1250         } else {
1251             innerArray.put(innerKey, value);
1252         }
1253     }
1254 
1255     /**
1256      * A utility function to iterate through the given 2d SparseArray
1257      */
forEachSparse2dArray( final SparseArray<T> array, final Consumer<U> action)1258     private static <T extends SparseArray<U>, U> void forEachSparse2dArray(
1259             final SparseArray<T> array, final Consumer<U> action) {
1260         if (action != null) {
1261             for (int i = array.size() - 1; i >= 0; i--) {
1262                 T innerArray = array.valueAt(i);
1263                 if (innerArray == null) {
1264                     continue;
1265                 }
1266                 for (int j = innerArray.size() - 1; j >= 0; j--) {
1267                     action.accept(innerArray.valueAt(j));
1268                 }
1269             }
1270         }
1271     }
1272 
1273     /**
1274      * A utility function to remove elements from the given 2d SparseArray
1275      */
removeFromSparse2dArray( final SparseArray<T> array, final Predicate<Integer> outerPredicate, final Predicate<Integer> innerPredicate, final Consumer<U> action)1276     private static <T extends SparseArray<U>, U> void removeFromSparse2dArray(
1277             final SparseArray<T> array, final Predicate<Integer> outerPredicate,
1278             final Predicate<Integer> innerPredicate, final Consumer<U> action) {
1279         for (int i = array.size() - 1; i >= 0; i--) {
1280             if (outerPredicate == null || outerPredicate.test(array.keyAt(i))) {
1281                 final T innerArray = array.valueAt(i);
1282                 if (innerArray == null) {
1283                     continue;
1284                 }
1285                 for (int j = innerArray.size() - 1; j >= 0; j--) {
1286                     if (innerPredicate == null || innerPredicate.test(innerArray.keyAt(j))) {
1287                         if (action != null) {
1288                             action.accept(innerArray.valueAt(j));
1289                         }
1290                         innerArray.removeAt(j);
1291                     }
1292                 }
1293                 if (innerArray.size() == 0) {
1294                     array.removeAt(i);
1295                 }
1296             }
1297         }
1298     }
1299 
1300     /**
1301      * A utility function to find and remove elements from the given 2d SparseArray.
1302      */
findAndRemoveFromSparse2dArray( final SparseArray<T> array, final int outerKey, final int innerKey)1303     private static <T extends SparseArray<U>, U> U findAndRemoveFromSparse2dArray(
1304             final SparseArray<T> array, final int outerKey, final int innerKey) {
1305         final int idx = array.indexOfKey(outerKey);
1306         if (idx >= 0) {
1307             T p = array.valueAt(idx);
1308             if (p == null) {
1309                 return null;
1310             }
1311             final int innerIdx = p.indexOfKey(innerKey);
1312             if (innerIdx >= 0) {
1313                 final U ret = p.valueAt(innerIdx);
1314                 p.removeAt(innerIdx);
1315                 if (p.size() == 0) {
1316                     array.removeAt(idx);
1317                 }
1318                 return ret;
1319             }
1320         }
1321         return null;
1322     }
1323 
1324     /**
1325      * A container class of {@link android.app.ApplicationExitInfo}
1326      */
1327     final class AppExitInfoContainer {
1328         private ArrayList<ApplicationExitInfo> mExitInfos;
1329         private int mMaxCapacity;
1330         private int mUid; // Application uid, not isolated uid.
1331 
AppExitInfoContainer(final int maxCapacity)1332         AppExitInfoContainer(final int maxCapacity) {
1333             mExitInfos = new ArrayList<ApplicationExitInfo>();
1334             mMaxCapacity = maxCapacity;
1335         }
1336 
1337         @VisibleForTesting
1338         @GuardedBy("mLock")
getExitInfosLocked( final int filterPid, final int maxNum, List<ApplicationExitInfo> results)1339         void getExitInfosLocked(
1340                 final int filterPid, final int maxNum, List<ApplicationExitInfo> results) {
1341             if (mExitInfos.size() == 0) {
1342                 return;
1343             }
1344 
1345             // Most of the callers might only be interested with the most recent
1346             // ApplicationExitInfo, and so we can special case an O(n) walk.
1347             if (maxNum == 1) {
1348                 ApplicationExitInfo result = null;
1349                 for (int i = 0, size = mExitInfos.size(); i < size; i++) {
1350                     ApplicationExitInfo info = mExitInfos.get(i);
1351                     if (filterPid > 0 && info.getPid() != filterPid) {
1352                         continue;
1353                     }
1354 
1355                     if (result == null || result.getTimestamp() < info.getTimestamp()) {
1356                         result = info;
1357                     }
1358                 }
1359                 if (result != null) {
1360                     results.add(result);
1361                 }
1362                 return;
1363             }
1364 
1365             mTmpInfoList2.clear();
1366             if (filterPid <= 0) {
1367                 mTmpInfoList2.addAll(mExitInfos);
1368             } else {
1369                 for (int i = 0, size = mExitInfos.size(); i < size; i++) {
1370                     ApplicationExitInfo info = mExitInfos.get(i);
1371                     if (info.getPid() == filterPid) {
1372                         mTmpInfoList2.add(info);
1373                     }
1374                 }
1375             }
1376 
1377             Collections.sort(
1378                     mTmpInfoList2, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
1379             if (maxNum <= 0) {
1380                 results.addAll(mTmpInfoList2);
1381                 return;
1382             }
1383 
1384             int elementsToRemove = mTmpInfoList2.size() - maxNum;
1385             for (int i = 0; i < elementsToRemove; i++) {
1386                 mTmpInfoList2.removeLast();
1387             }
1388             results.addAll(mTmpInfoList2);
1389         }
1390 
1391         @VisibleForTesting
1392         @GuardedBy("mLock")
addExitInfoLocked(ApplicationExitInfo info)1393         void addExitInfoLocked(ApplicationExitInfo info) {
1394             // Claim the state information if there is any
1395             int uid = info.getPackageUid();
1396             // SDK sandbox app states and app traces are stored under real UID
1397             if (Process.isSdkSandboxUid(info.getRealUid())) {
1398                 uid = info.getRealUid();
1399             }
1400             final int pid = info.getPid();
1401             if (info.getProcessStateSummary() == null) {
1402                 info.setProcessStateSummary(findAndRemoveFromSparse2dArray(
1403                         mActiveAppStateSummary, uid, pid));
1404             }
1405             if (info.getTraceFile() == null) {
1406                 info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid));
1407             }
1408             info.setAppTraceRetriever(mAppTraceRetriever);
1409 
1410             mExitInfos.add(info);
1411             if (mExitInfos.size() <= mMaxCapacity) {
1412                 return;
1413             }
1414 
1415             ApplicationExitInfo oldest = null;
1416             for (int i = 0, size = mExitInfos.size(); i < size; i++) {
1417                 ApplicationExitInfo info2 = mExitInfos.get(i);
1418                 if (oldest == null || info2.getTimestamp() < oldest.getTimestamp()) {
1419                     oldest = info2;
1420                 }
1421             }
1422             File traceFile = oldest.getTraceFile();
1423             if (traceFile != null) {
1424                 traceFile.delete();
1425             }
1426             mExitInfos.remove(oldest);
1427         }
1428 
1429         @GuardedBy("mLock")
getLastExitInfoForPid(final int pid)1430         ApplicationExitInfo getLastExitInfoForPid(final int pid) {
1431             mTmpInfoList.clear();
1432             getExitInfosLocked(pid, /* maxNum */ 1, mTmpInfoList);
1433             ApplicationExitInfo info = mTmpInfoList.size() == 0 ? null : mTmpInfoList.getFirst();
1434             mTmpInfoList.clear();
1435             return info;
1436         }
1437 
1438         @GuardedBy("mLock")
appendTraceIfNecessaryLocked(final int pid, final File traceFile)1439         boolean appendTraceIfNecessaryLocked(final int pid, final File traceFile) {
1440             final ApplicationExitInfo r = getLastExitInfoForPid(pid);
1441             if (r != null) {
1442                 r.setTraceFile(traceFile);
1443                 r.setAppTraceRetriever(mAppTraceRetriever);
1444                 return true;
1445             }
1446             return false;
1447         }
1448 
1449         @GuardedBy("mLock")
destroyLocked()1450         void destroyLocked() {
1451             for (int i = 0, size = mExitInfos.size(); i < size; i++) {
1452                 ApplicationExitInfo info = mExitInfos.get(i);
1453                 final File traceFile = info.getTraceFile();
1454                 if (traceFile != null) {
1455                     traceFile.delete();
1456                 }
1457                 info.setTraceFile(null);
1458                 info.setAppTraceRetriever(null);
1459             }
1460         }
1461 
1462         /**
1463          * Go through each record in an *unspecified* order, execute `callback()` on each element,
1464          * and potentially do some action (stopping iteration, removing the element, etc.) based on
1465          * the return value of the callback.
1466          */
1467         @GuardedBy("mLock")
forEachRecordLocked(final Function<ApplicationExitInfo, Integer> callback)1468         void forEachRecordLocked(final Function<ApplicationExitInfo, Integer> callback) {
1469             if (callback == null) return;
1470             for (int i = mExitInfos.size() - 1; i >= 0; i--) {
1471                 ApplicationExitInfo info = mExitInfos.get(i);
1472                 switch (callback.apply(info)) {
1473                     case FOREACH_ACTION_STOP_ITERATION: return;
1474                     case FOREACH_ACTION_REMOVE_ITEM:
1475                         File traceFile;
1476                         if ((traceFile = info.getTraceFile()) != null) {
1477                             traceFile.delete();
1478                         }
1479                         mExitInfos.remove(info);
1480                         break;
1481                 }
1482             }
1483         }
1484 
1485         @GuardedBy("mLock")
dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf)1486         void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
1487             mTmpInfoList.clear();
1488             getExitInfosLocked(/* filterPid */ 0, /* maxNum */ 0, mTmpInfoList);
1489             for (int i = 0, size = mTmpInfoList.size(); i < size; i++) {
1490                 mTmpInfoList.get(i).dump(pw, prefix + "  ", "#" + i, sdf);
1491             }
1492             mTmpInfoList.clear();
1493         }
1494 
1495         @GuardedBy("mLock")
writeToProto(ProtoOutputStream proto, long fieldId)1496         void writeToProto(ProtoOutputStream proto, long fieldId) {
1497             long token = proto.start(fieldId);
1498             proto.write(AppsExitInfoProto.Package.User.UID, mUid);
1499             for (int i = 0, size = mExitInfos.size(); i < size; i++) {
1500                 mExitInfos.get(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
1501             }
1502             proto.end(token);
1503         }
1504 
readFromProto(ProtoInputStream proto, long fieldId)1505         int readFromProto(ProtoInputStream proto, long fieldId)
1506                 throws IOException, WireTypeMismatchException {
1507             long token = proto.start(fieldId);
1508             for (int next = proto.nextField();
1509                     next != ProtoInputStream.NO_MORE_FIELDS;
1510                     next = proto.nextField()) {
1511                 switch (next) {
1512                     case (int) AppsExitInfoProto.Package.User.UID: {
1513                         mUid = proto.readInt(AppsExitInfoProto.Package.User.UID);
1514                         break;
1515                     }
1516                     case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: {
1517                         ApplicationExitInfo info = new ApplicationExitInfo();
1518                         info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
1519                         mExitInfos.add(info);
1520                         break;
1521                     }
1522                 }
1523             }
1524             proto.end(token);
1525             return mUid;
1526         }
1527     }
1528 
1529     /**
1530      * Maintains the mapping between real UID and the application uid.
1531      */
1532     final class IsolatedUidRecords {
1533         /**
1534          * A mapping from application uid (with the userId) to isolated uids.
1535          */
1536         @GuardedBy("mLock")
1537         private final SparseArray<ArraySet<Integer>> mUidToIsolatedUidMap;
1538 
1539         /**
1540          * A mapping from isolated uids to application uid (with the userId)
1541          */
1542         @GuardedBy("mLock")
1543         private final SparseArray<Integer> mIsolatedUidToUidMap;
1544 
IsolatedUidRecords()1545         IsolatedUidRecords() {
1546             mUidToIsolatedUidMap = new SparseArray<ArraySet<Integer>>();
1547             mIsolatedUidToUidMap = new SparseArray<Integer>();
1548         }
1549 
addIsolatedUid(int isolatedUid, int uid)1550         void addIsolatedUid(int isolatedUid, int uid) {
1551             synchronized (mLock) {
1552                 ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
1553                 if (set == null) {
1554                     set = new ArraySet<Integer>();
1555                     mUidToIsolatedUidMap.put(uid, set);
1556                 }
1557                 set.add(isolatedUid);
1558 
1559                 mIsolatedUidToUidMap.put(isolatedUid, uid);
1560             }
1561         }
1562 
removeIsolatedUid(int isolatedUid, int uid)1563         void removeIsolatedUid(int isolatedUid, int uid) {
1564             synchronized (mLock) {
1565                 final int index = mUidToIsolatedUidMap.indexOfKey(uid);
1566                 if (index >= 0) {
1567                     final ArraySet<Integer> set = mUidToIsolatedUidMap.valueAt(index);
1568                     set.remove(isolatedUid);
1569                     if (set.isEmpty()) {
1570                         mUidToIsolatedUidMap.removeAt(index);
1571                     }
1572                 }
1573                 mIsolatedUidToUidMap.remove(isolatedUid);
1574             }
1575         }
1576 
1577         @GuardedBy("mLock")
getUidByIsolatedUid(int isolatedUid)1578         Integer getUidByIsolatedUid(int isolatedUid) {
1579             if (UserHandle.isIsolated(isolatedUid)) {
1580                 synchronized (mLock) {
1581                     return mIsolatedUidToUidMap.get(isolatedUid);
1582                 }
1583             }
1584             return isolatedUid;
1585         }
1586 
1587         @GuardedBy("mLock")
removeAppUidLocked(int uid)1588         private void removeAppUidLocked(int uid) {
1589             ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
1590             if (set != null) {
1591                 for (int i = set.size() - 1; i >= 0; i--) {
1592                     int isolatedUid = set.removeAt(i);
1593                     mIsolatedUidToUidMap.remove(isolatedUid);
1594                 }
1595             }
1596         }
1597 
1598         @VisibleForTesting
removeAppUid(int uid, boolean allUsers)1599         void removeAppUid(int uid, boolean allUsers) {
1600             synchronized (mLock) {
1601                 if (allUsers) {
1602                     uid = UserHandle.getAppId(uid);
1603                     for (int i = mUidToIsolatedUidMap.size() - 1; i >= 0; i--) {
1604                         int u = mUidToIsolatedUidMap.keyAt(i);
1605                         if (uid == UserHandle.getAppId(u)) {
1606                             removeAppUidLocked(u);
1607                         }
1608                         mUidToIsolatedUidMap.removeAt(i);
1609                     }
1610                 } else {
1611                     removeAppUidLocked(uid);
1612                     mUidToIsolatedUidMap.remove(uid);
1613                 }
1614             }
1615         }
1616 
1617         @GuardedBy("mLock")
removeIsolatedUidLocked(int isolatedUid)1618         int removeIsolatedUidLocked(int isolatedUid) {
1619             if (!UserHandle.isIsolated(isolatedUid)) {
1620                 return isolatedUid;
1621             }
1622             int uid = mIsolatedUidToUidMap.get(isolatedUid, -1);
1623             if (uid == -1) {
1624                 return isolatedUid;
1625             }
1626             mIsolatedUidToUidMap.remove(isolatedUid);
1627             ArraySet<Integer> set = mUidToIsolatedUidMap.get(uid);
1628             if (set != null) {
1629                 set.remove(isolatedUid);
1630             }
1631             // let the ArraySet stay in the mUidToIsolatedUidMap even if it's empty
1632             return uid;
1633         }
1634 
removeByUserId(int userId)1635         void removeByUserId(int userId) {
1636             if (userId == UserHandle.USER_CURRENT) {
1637                 userId = mService.mUserController.getCurrentUserId();
1638             }
1639             synchronized (mLock) {
1640                 if (userId == UserHandle.USER_ALL) {
1641                     mIsolatedUidToUidMap.clear();
1642                     mUidToIsolatedUidMap.clear();
1643                     return;
1644                 }
1645                 for (int i = mIsolatedUidToUidMap.size() - 1; i >= 0; i--) {
1646                     int isolatedUid = mIsolatedUidToUidMap.keyAt(i);
1647                     int uid = mIsolatedUidToUidMap.valueAt(i);
1648                     if (UserHandle.getUserId(uid) == userId) {
1649                         mIsolatedUidToUidMap.removeAt(i);
1650                         mUidToIsolatedUidMap.remove(uid);
1651                     }
1652                 }
1653             }
1654         }
1655     }
1656 
1657     final class KillHandler extends Handler {
1658         static final int MSG_LMKD_PROC_KILLED = 4101;
1659         static final int MSG_CHILD_PROC_DIED = 4102;
1660         static final int MSG_PROC_DIED = 4103;
1661         static final int MSG_APP_KILL = 4104;
1662         static final int MSG_STATSD_LOG = 4105;
1663         static final int MSG_APP_RECOVERABLE_CRASH = 4106;
1664 
KillHandler(Looper looper)1665         KillHandler(Looper looper) {
1666             super(looper, null, true);
1667         }
1668 
1669         @Override
handleMessage(Message msg)1670         public void handleMessage(Message msg) {
1671             switch (msg.what) {
1672                 case MSG_LMKD_PROC_KILLED:
1673                     mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
1674                             null /* status */, (Long) msg.obj /* rss_kb */);
1675                     break;
1676                 case MSG_CHILD_PROC_DIED:
1677                     mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
1678                             (Integer) msg.obj /* status */, null /* rss_kb */);
1679                     break;
1680                 case MSG_PROC_DIED: {
1681                     ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
1682                     synchronized (mLock) {
1683                         handleNoteProcessDiedLocked(raw);
1684                     }
1685                     recycleRawRecord(raw);
1686                 }
1687                 break;
1688                 case MSG_APP_KILL: {
1689                     ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
1690                     synchronized (mLock) {
1691                         handleNoteAppKillLocked(raw);
1692                     }
1693                     recycleRawRecord(raw);
1694                 }
1695                 break;
1696                 case MSG_STATSD_LOG: {
1697                     synchronized (mLock) {
1698                         performLogToStatsdLocked((ApplicationExitInfo) msg.obj);
1699                     }
1700                 }
1701                 break;
1702                 case MSG_APP_RECOVERABLE_CRASH: {
1703                     ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
1704                     synchronized (mLock) {
1705                         // Unlike MSG_APP_KILL, this is a recoverable crash, and
1706                         // so we want to bypass the statsd app-kill logging.
1707                         // Hence, call `addExitInfoLocked()` directly instead of
1708                         // `handleNoteAppKillLocked()`.
1709                         addExitInfoLocked(raw);
1710                     }
1711                     recycleRawRecord(raw);
1712                 }
1713                 break;
1714                 default:
1715                     super.handleMessage(msg);
1716             }
1717         }
1718     }
1719 
1720     @VisibleForTesting
isFresh(long timestamp)1721     boolean isFresh(long timestamp) {
1722         // A process could be dying but being stuck in some state, i.e.,
1723         // being TRACED by tombstoned, thus the zygote receives SIGCHILD
1724         // way after we already knew the kill (maybe because we did the kill :P),
1725         // so here check if the last known kill information is "fresh" enough.
1726         long now = System.currentTimeMillis();
1727 
1728         return (timestamp + AppExitInfoExternalSource.APP_EXIT_INFO_FRESHNESS_MS) >= now;
1729     }
1730 
1731     /**
1732      * Keep the raw information about app kills from external sources, i.e., lmkd
1733      */
1734     final class AppExitInfoExternalSource {
1735         private static final long APP_EXIT_INFO_FRESHNESS_MS = 300 * 1000;
1736 
1737         /**
1738          * A mapping between uid -> pid -> {timestamp, extra info(Nullable)}.
1739          * The uid here is the application uid, not the isolated uid.
1740          */
1741         @GuardedBy("mLock")
1742         private final SparseArray<SparseArray<Pair<Long, Object>>> mData;
1743 
1744         /** A tag for logging only */
1745         private final String mTag;
1746 
1747         /** A preset reason in case a proc dies */
1748         private final Integer mPresetReason;
1749 
1750         /** A callback that will be notified when a proc dies */
1751         private BiConsumer<Integer, Integer> mProcDiedListener;
1752 
AppExitInfoExternalSource(String tag, Integer reason)1753         AppExitInfoExternalSource(String tag, Integer reason) {
1754             mData = new SparseArray<SparseArray<Pair<Long, Object>>>();
1755             mTag = tag;
1756             mPresetReason = reason;
1757         }
1758 
1759         @GuardedBy("mLock")
addLocked(int pid, int uid, Object extra)1760         private void addLocked(int pid, int uid, Object extra) {
1761             Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
1762             if (k != null) {
1763                 uid = k;
1764             }
1765 
1766             SparseArray<Pair<Long, Object>> array = mData.get(uid);
1767             if (array == null) {
1768                 array = new SparseArray<Pair<Long, Object>>();
1769                 mData.put(uid, array);
1770             }
1771             array.put(pid, new Pair<Long, Object>(System.currentTimeMillis(), extra));
1772         }
1773 
1774         @VisibleForTesting
remove(int pid, int uid)1775         Pair<Long, Object> remove(int pid, int uid) {
1776             synchronized (mLock) {
1777                 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
1778                 if (k != null) {
1779                     uid = k;
1780                 }
1781 
1782                 SparseArray<Pair<Long, Object>> array = mData.get(uid);
1783                 if (array != null) {
1784                     Pair<Long, Object> p = array.get(pid);
1785                     if (p != null) {
1786                         array.remove(pid);
1787                         return isFresh(p.first) ? p : null;
1788                     }
1789                 }
1790                 return null;
1791             }
1792         }
1793 
removeByUserId(int userId)1794         void removeByUserId(int userId) {
1795             if (userId == UserHandle.USER_CURRENT) {
1796                 userId = mService.mUserController.getCurrentUserId();
1797             }
1798             synchronized (mLock) {
1799                 if (userId == UserHandle.USER_ALL) {
1800                     mData.clear();
1801                     return;
1802                 }
1803                 for (int i = mData.size() - 1; i >= 0; i--) {
1804                     int uid = mData.keyAt(i);
1805                     if (UserHandle.getUserId(uid) == userId) {
1806                         mData.removeAt(i);
1807                     }
1808                 }
1809             }
1810         }
1811 
1812         @GuardedBy("mLock")
removeByUidLocked(int uid, boolean allUsers)1813         void removeByUidLocked(int uid, boolean allUsers) {
1814             if (UserHandle.isIsolated(uid)) {
1815                 Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
1816                 if (k != null) {
1817                     uid = k;
1818                 }
1819             }
1820 
1821             if (allUsers) {
1822                 uid = UserHandle.getAppId(uid);
1823                 for (int i = mData.size() - 1; i >= 0; i--) {
1824                     if (UserHandle.getAppId(mData.keyAt(i)) == uid) {
1825                         mData.removeAt(i);
1826                     }
1827                 }
1828             } else {
1829                 mData.remove(uid);
1830             }
1831         }
1832 
setOnProcDiedListener(BiConsumer<Integer, Integer> listener)1833         void setOnProcDiedListener(BiConsumer<Integer, Integer> listener) {
1834             synchronized (mLock) {
1835                 mProcDiedListener = listener;
1836             }
1837         }
1838 
onProcDied(final int pid, final int uid, final Integer status, final Long rssKb)1839         void onProcDied(final int pid, final int uid, final Integer status, final Long rssKb) {
1840             if (DEBUG_PROCESSES) {
1841                 Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid
1842                         + ", status=" + status);
1843             }
1844 
1845             if (mService == null) {
1846                 return;
1847             }
1848 
1849             // Unlikely but possible: the record has been created
1850             // Let's update it if we could find a ApplicationExitInfo record
1851             synchronized (mLock) {
1852                 if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason, rssKb)) {
1853                     if (rssKb != null) {
1854                         addLocked(pid, uid, rssKb);     // lmkd
1855                     } else {
1856                         addLocked(pid, uid, status);    // zygote
1857                     }
1858                 }
1859 
1860                 // Notify any interesed party regarding the lmkd kills
1861                 final BiConsumer<Integer, Integer> listener = mProcDiedListener;
1862                 if (listener != null) {
1863                     mService.mHandler.post(()-> listener.accept(pid, uid));
1864                 }
1865             }
1866         }
1867     }
1868 
1869     /**
1870      * The implementation to the IAppTraceRetriever interface.
1871      */
1872     @VisibleForTesting
1873     class AppTraceRetriever extends IAppTraceRetriever.Stub {
1874         @Override
getTraceFileDescriptor(final String packageName, final int uid, final int pid)1875         public ParcelFileDescriptor getTraceFileDescriptor(final String packageName,
1876                 final int uid, final int pid) {
1877             mService.enforceNotIsolatedCaller("getTraceFileDescriptor");
1878 
1879             if (TextUtils.isEmpty(packageName)) {
1880                 throw new IllegalArgumentException("Invalid package name");
1881             }
1882             final int callingPid = Binder.getCallingPid();
1883             final int callingUid = Binder.getCallingUid();
1884             final int userId = UserHandle.getUserId(uid);
1885 
1886             mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
1887                     ALLOW_NON_FULL, "getTraceFileDescriptor", null);
1888             final int filterUid = mService.enforceDumpPermissionForPackage(packageName, userId,
1889                     callingUid, "getTraceFileDescriptor");
1890             if (filterUid != Process.INVALID_UID) {
1891                 synchronized (mLock) {
1892                     final ApplicationExitInfo info = getExitInfoLocked(packageName, filterUid, pid);
1893                     if (info == null) {
1894                         return null;
1895                     }
1896                     final File traceFile = info.getTraceFile();
1897                     if (traceFile == null) {
1898                         return null;
1899                     }
1900                     final long identity = Binder.clearCallingIdentity();
1901                     try {
1902                         // The fd will be closed after being written into Parcel
1903                         return ParcelFileDescriptor.open(traceFile,
1904                                 ParcelFileDescriptor.MODE_READ_ONLY);
1905                     } catch (FileNotFoundException e) {
1906                         return null;
1907                     } finally {
1908                         Binder.restoreCallingIdentity(identity);
1909                     }
1910                 }
1911             }
1912             return null;
1913         }
1914     }
1915 }
1916