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