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.os;
18 
19 import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
20 import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
21 
22 import android.Manifest;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.app.AppOpsManager;
27 import android.app.admin.DevicePolicyManager;
28 import android.app.role.RoleManager;
29 import android.content.Context;
30 import android.content.pm.PackageManager;
31 import android.content.pm.UserInfo;
32 import android.os.Binder;
33 import android.os.BugreportManager.BugreportCallback;
34 import android.os.BugreportParams;
35 import android.os.Build;
36 import android.os.Environment;
37 import android.os.IDumpstate;
38 import android.os.IDumpstateListener;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.SystemClock;
42 import android.os.SystemProperties;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.telephony.TelephonyManager;
46 import android.text.TextUtils;
47 import android.util.ArrayMap;
48 import android.util.ArraySet;
49 import android.util.AtomicFile;
50 import android.util.LocalLog;
51 import android.util.MutableBoolean;
52 import android.util.Pair;
53 import android.util.Slog;
54 import android.util.Xml;
55 
56 import com.android.internal.annotations.GuardedBy;
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.internal.util.DumpUtils;
59 import com.android.internal.util.XmlUtils;
60 import com.android.modules.utils.TypedXmlPullParser;
61 import com.android.modules.utils.TypedXmlSerializer;
62 import com.android.server.SystemConfig;
63 import com.android.server.utils.Slogf;
64 
65 import org.xmlpull.v1.XmlPullParserException;
66 
67 import java.io.File;
68 import java.io.FileDescriptor;
69 import java.io.FileNotFoundException;
70 import java.io.FileOutputStream;
71 import java.io.IOException;
72 import java.io.InputStream;
73 import java.io.PrintWriter;
74 import java.util.HashMap;
75 import java.util.HashSet;
76 import java.util.List;
77 import java.util.Map;
78 import java.util.Objects;
79 import java.util.OptionalInt;
80 import java.util.Set;
81 import java.util.concurrent.TimeUnit;
82 
83 /**
84  * Implementation of the service that provides a privileged API to capture and consume bugreports.
85  *
86  * <p>Delegates the actualy generation to a native implementation of {@code IDumpstate}.
87  */
88 class BugreportManagerServiceImpl extends IDumpstate.Stub {
89 
90     private static final int LOCAL_LOG_SIZE = 20;
91     private static final String TAG = "BugreportManagerService";
92     private static final boolean DEBUG = false;
93     private static final String ROLE_SYSTEM_AUTOMOTIVE_PROJECTION =
94             "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
95     private static final String TAG_BUGREPORT_DATA = "bugreport-data";
96     private static final String TAG_BUGREPORT_MAP = "bugreport-map";
97     private static final String TAG_PERSISTENT_BUGREPORT = "persistent-bugreport";
98     private static final String ATTR_CALLING_UID = "calling-uid";
99     private static final String ATTR_CALLING_PACKAGE = "calling-package";
100     private static final String ATTR_BUGREPORT_FILE = "bugreport-file";
101 
102     private static final String BUGREPORT_SERVICE = "bugreportd";
103     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
104 
105     private static final long DEFAULT_BUGREPORT_CONSENTLESS_GRACE_PERIOD_MILLIS =
106             TimeUnit.MINUTES.toMillis(2);
107 
108     private final Object mLock = new Object();
109     private final Injector mInjector;
110     private final Context mContext;
111     private final AppOpsManager mAppOps;
112     private final TelephonyManager mTelephonyManager;
113     private final ArraySet<String> mBugreportAllowlistedPackages;
114     private final BugreportFileManager mBugreportFileManager;
115     private static final FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
116 
117 
118     @GuardedBy("mLock")
119     private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
120 
121     // Attributes below are just Used for dump() purposes
122     @Nullable
123     @GuardedBy("mLock")
124     private DumpstateListener mCurrentDumpstateListener;
125     @GuardedBy("mLock")
126     private int mNumberFinishedBugreports;
127     @GuardedBy("mLock")
128     private final LocalLog mFinishedBugreports = new LocalLog(LOCAL_LOG_SIZE);
129 
130     /** Helper class for associating previously generated bugreports with their callers. */
131     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
132     static class BugreportFileManager {
133 
134         private final Object mLock = new Object();
135         private boolean mReadBugreportMapping = false;
136         private final AtomicFile mMappingFile;
137 
138         @GuardedBy("mLock")
139         private ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
140                 new ArrayMap<>();
141 
142         // Map of <CallerPackage, Pair<TimestampOfLastConsent, skipConsentForFullReport>>
143         @GuardedBy("mLock")
144         private Map<String, Pair<Long, Boolean>> mConsentGranted = new HashMap<>();
145 
146         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
147         @GuardedBy("mLock")
148         final Set<String> mBugreportFilesToPersist = new HashSet<>();
149 
BugreportFileManager(AtomicFile mappingFile)150         BugreportFileManager(AtomicFile mappingFile) {
151             mMappingFile = mappingFile;
152         }
153 
154         /**
155          * Checks that a given file was generated on behalf of the given caller. If the file was
156          * not generated on behalf of the caller, an
157          * {@link IllegalArgumentException} is thrown.
158          *
159          * @param callingInfo a (uid, package name) pair identifying the caller
160          * @param bugreportFile the file name which was previously given to the caller in the
161          *                      {@link BugreportCallback#onFinished(String)} callback.
162          * @param forceUpdateMapping if {@code true}, updates the bugreport mapping by reading from
163          *                           the mapping file.
164          *
165          * @throws IllegalArgumentException if {@code bugreportFile} is not associated with
166          *                                  {@code callingInfo}.
167          */
168         @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
169                 conditional = true)
ensureCallerPreviouslyGeneratedFile( Context context, PackageManager packageManager, Pair<Integer, String> callingInfo, int userId, String bugreportFile, boolean forceUpdateMapping)170         void ensureCallerPreviouslyGeneratedFile(
171                 Context context, PackageManager packageManager, Pair<Integer, String> callingInfo,
172                 int userId, String bugreportFile, boolean forceUpdateMapping) {
173             synchronized (mLock) {
174                 if (onboardingBugreportV2Enabled()) {
175                     final int uidForUser = Binder.withCleanCallingIdentity(() -> {
176                         try {
177                             return packageManager.getPackageUidAsUser(callingInfo.second, userId);
178                         } catch (PackageManager.NameNotFoundException exception) {
179                             throwInvalidBugreportFileForCallerException(
180                                     bugreportFile, callingInfo.second);
181                             return -1;
182                         }
183                     });
184                     if (uidForUser != callingInfo.first && context.checkCallingOrSelfPermission(
185                             Manifest.permission.INTERACT_ACROSS_USERS)
186                             != PackageManager.PERMISSION_GRANTED) {
187                         throw new SecurityException(
188                                 callingInfo.second + " does not hold the "
189                                         + "INTERACT_ACROSS_USERS permission to access "
190                                         + "cross-user bugreports.");
191                     }
192                     if (!mReadBugreportMapping || forceUpdateMapping) {
193                         readBugreportMappingLocked();
194                     }
195                     ArraySet<String> bugreportFilesForUid = mBugreportFiles.get(
196                             new Pair<>(uidForUser, callingInfo.second));
197                     if (bugreportFilesForUid == null
198                             || !bugreportFilesForUid.contains(bugreportFile)) {
199                         throwInvalidBugreportFileForCallerException(
200                                 bugreportFile, callingInfo.second);
201                     }
202 
203                     boolean keepBugreportOnRetrieval = false;
204                     if (onboardingBugreportV2Enabled()) {
205                         keepBugreportOnRetrieval = mBugreportFilesToPersist.contains(
206                                 bugreportFile);
207                     }
208 
209                     if (!keepBugreportOnRetrieval) {
210                         bugreportFilesForUid.remove(bugreportFile);
211                     }
212                 } else {
213                     ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
214                     if (bugreportFilesForCaller != null
215                             && bugreportFilesForCaller.contains(bugreportFile)) {
216                         bugreportFilesForCaller.remove(bugreportFile);
217                         if (bugreportFilesForCaller.isEmpty()) {
218                             mBugreportFiles.remove(callingInfo);
219                         }
220                     } else {
221                         throwInvalidBugreportFileForCallerException(
222                                 bugreportFile, callingInfo.second);
223 
224                     }
225                 }
226             }
227         }
228 
throwInvalidBugreportFileForCallerException( String bugreportFile, String packageName)229         private static void throwInvalidBugreportFileForCallerException(
230                 String bugreportFile, String packageName) {
231             throw new IllegalArgumentException("File " + bugreportFile + " was not generated on"
232                     + " behalf of calling package " + packageName);
233         }
234 
235         /**
236          * Associates a bugreport file with a caller, which is identified as a
237          * (uid, package name) pair.
238          */
addBugreportFileForCaller( Pair<Integer, String> caller, String bugreportFile, boolean keepOnRetrieval)239         void addBugreportFileForCaller(
240                 Pair<Integer, String> caller, String bugreportFile, boolean keepOnRetrieval) {
241             addBugreportMapping(caller, bugreportFile);
242             synchronized (mLock) {
243                 if (onboardingBugreportV2Enabled()) {
244                     if (keepOnRetrieval) {
245                         mBugreportFilesToPersist.add(bugreportFile);
246                     }
247                     writeBugreportDataLocked();
248                 }
249             }
250         }
251 
252         /**
253          * Logs an entry with a timestamp of a consent being granted by the user to the calling
254          * {@code packageName}.
255          */
logConsentGrantedForCaller( String packageName, boolean consentGranted, boolean isDeferredReport)256         void logConsentGrantedForCaller(
257                 String packageName, boolean consentGranted, boolean isDeferredReport) {
258             if (!onboardingConsentlessBugreports() || !Build.IS_DEBUGGABLE) {
259                 return;
260             }
261             synchronized (mLock) {
262                 // Adds an entry with the timestamp of the consent being granted by the user, and
263                 // whether the consent can be skipped for a full bugreport, because a single
264                 // consent can be used for multiple deferred reports but only one full report.
265                 if (consentGranted) {
266                     mConsentGranted.put(packageName, new Pair<>(
267                             System.currentTimeMillis(),
268                             isDeferredReport));
269                 } else if (!isDeferredReport) {
270                     if (!mConsentGranted.containsKey(packageName)) {
271                         Slog.e(TAG, "Previous consent from package: " + packageName + " should"
272                                 + "have been logged.");
273                         return;
274                     }
275                     mConsentGranted.put(packageName, new Pair<>(
276                             mConsentGranted.get(packageName).first,
277                             /* second = */ false
278                     ));
279                 }
280             }
281         }
282 
283         /**
284          * Returns {@code true} if user consent be skippeb because a previous consens has been
285          * granted to the caller within the allowed time period.
286          */
canSkipConsentScreen(String packageName, boolean isFullReport)287         boolean canSkipConsentScreen(String packageName, boolean isFullReport) {
288             if (!onboardingConsentlessBugreports() || !Build.IS_DEBUGGABLE) {
289                 return false;
290             }
291             synchronized (mLock) {
292                 if (!mConsentGranted.containsKey(packageName)) {
293                     return false;
294                 }
295                 long currentTime = System.currentTimeMillis();
296                 long consentGrantedTime = mConsentGranted.get(packageName).first;
297                 if (consentGrantedTime + DEFAULT_BUGREPORT_CONSENTLESS_GRACE_PERIOD_MILLIS
298                         < currentTime) {
299                     mConsentGranted.remove(packageName);
300                     return false;
301                 }
302                 boolean skipConsentForFullReport = mConsentGranted.get(packageName).second;
303                 if (isFullReport && !skipConsentForFullReport) {
304                     return false;
305                 }
306                 return true;
307             }
308         }
309 
addBugreportMapping(Pair<Integer, String> caller, String bugreportFile)310         private void addBugreportMapping(Pair<Integer, String> caller, String bugreportFile) {
311             synchronized (mLock) {
312                 if (!mBugreportFiles.containsKey(caller)) {
313                     mBugreportFiles.put(caller, new ArraySet<>());
314                 }
315                 ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller);
316                 bugreportFilesForCaller.add(bugreportFile);
317             }
318         }
319 
320         @GuardedBy("mLock")
readBugreportMappingLocked()321         private void readBugreportMappingLocked() {
322             mBugreportFiles = new ArrayMap<>();
323             try (InputStream inputStream = mMappingFile.openRead()) {
324                 final TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
325                 XmlUtils.beginDocument(parser, TAG_BUGREPORT_DATA);
326                 int depth = parser.getDepth();
327                 while (XmlUtils.nextElementWithin(parser, depth)) {
328                     String tag = parser.getName();
329                     switch (tag) {
330                         case TAG_BUGREPORT_MAP:
331                             readBugreportMapEntry(parser);
332                             break;
333                         case TAG_PERSISTENT_BUGREPORT:
334                             readPersistentBugreportEntry(parser);
335                             break;
336                         default:
337                             Slog.e(TAG, "Unknown tag while reading bugreport mapping file: "
338                                     + tag);
339                     }
340                 }
341                 mReadBugreportMapping = true;
342             } catch (FileNotFoundException e) {
343                 Slog.i(TAG, "Bugreport mapping file does not exist");
344             } catch (IOException | XmlPullParserException e) {
345                 mMappingFile.delete();
346             }
347         }
348 
349         @GuardedBy("mLock")
writeBugreportDataLocked()350         private void writeBugreportDataLocked() {
351             if (mBugreportFiles.isEmpty() && mBugreportFilesToPersist.isEmpty()) {
352                 return;
353             }
354             try (FileOutputStream stream = mMappingFile.startWrite()) {
355                 TypedXmlSerializer out = Xml.resolveSerializer(stream);
356                 out.startDocument(null, true);
357                 out.startTag(null, TAG_BUGREPORT_DATA);
358                 for (Map.Entry<Pair<Integer, String>, ArraySet<String>> entry:
359                         mBugreportFiles.entrySet()) {
360                     Pair<Integer, String> callingInfo = entry.getKey();
361                     ArraySet<String> callersBugreports = entry.getValue();
362                     for (String bugreportFile: callersBugreports) {
363                         writeBugreportMapEntry(callingInfo, bugreportFile, out);
364                     }
365                 }
366                 for (String file : mBugreportFilesToPersist) {
367                     writePersistentBugreportEntry(file, out);
368                 }
369                 out.endTag(null, TAG_BUGREPORT_DATA);
370                 out.endDocument();
371                 mMappingFile.finishWrite(stream);
372             } catch (IOException e) {
373                 Slog.e(TAG, "Failed to write bugreport mapping file", e);
374             }
375         }
376 
readBugreportMapEntry(TypedXmlPullParser parser)377         private void readBugreportMapEntry(TypedXmlPullParser parser)
378                 throws XmlPullParserException {
379             int callingUid = parser.getAttributeInt(null, ATTR_CALLING_UID);
380             String callingPackage = parser.getAttributeValue(null, ATTR_CALLING_PACKAGE);
381             String bugreportFile = parser.getAttributeValue(null, ATTR_BUGREPORT_FILE);
382             addBugreportMapping(new Pair<>(callingUid, callingPackage), bugreportFile);
383         }
384 
readPersistentBugreportEntry(TypedXmlPullParser parser)385         private void readPersistentBugreportEntry(TypedXmlPullParser parser)
386                 throws XmlPullParserException {
387             String bugreportFile = parser.getAttributeValue(null, ATTR_BUGREPORT_FILE);
388             synchronized (mLock) {
389                 mBugreportFilesToPersist.add(bugreportFile);
390             }
391         }
392 
writeBugreportMapEntry(Pair<Integer, String> callingInfo, String bugreportFile, TypedXmlSerializer out)393         private void writeBugreportMapEntry(Pair<Integer, String> callingInfo, String bugreportFile,
394                 TypedXmlSerializer out) throws IOException {
395             out.startTag(null, TAG_BUGREPORT_MAP);
396             out.attributeInt(null, ATTR_CALLING_UID, callingInfo.first);
397             out.attribute(null, ATTR_CALLING_PACKAGE, callingInfo.second);
398             out.attribute(null, ATTR_BUGREPORT_FILE, bugreportFile);
399             out.endTag(null, TAG_BUGREPORT_MAP);
400         }
401 
writePersistentBugreportEntry( String bugreportFile, TypedXmlSerializer out)402         private void writePersistentBugreportEntry(
403                 String bugreportFile, TypedXmlSerializer out) throws IOException {
404             out.startTag(null, TAG_PERSISTENT_BUGREPORT);
405             out.attribute(null, ATTR_BUGREPORT_FILE, bugreportFile);
406             out.endTag(null, TAG_PERSISTENT_BUGREPORT);
407         }
408     }
409 
410     static class Injector {
411         class RoleManagerWrapper {
getRoleHolders(@onNull String roleName)412             List<String> getRoleHolders(@NonNull String roleName) {
413                 return mContext.getSystemService(RoleManager.class).getRoleHolders(roleName);
414             }
415         }
416 
417         Context mContext;
418         ArraySet<String> mAllowlistedPackages;
419         AtomicFile mMappingFile;
420         RoleManagerWrapper mRoleManagerWrapper;
421 
Injector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile)422         Injector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile) {
423             mContext = context;
424             mAllowlistedPackages = allowlistedPackages;
425             mMappingFile = mappingFile;
426             mRoleManagerWrapper = new RoleManagerWrapper();
427         }
428 
getContext()429         Context getContext() {
430             return mContext;
431         }
432 
getAllowlistedPackages()433         ArraySet<String> getAllowlistedPackages() {
434             return mAllowlistedPackages;
435         }
436 
getMappingFile()437         AtomicFile getMappingFile() {
438             return mMappingFile;
439         }
440 
getUserManager()441         UserManager getUserManager() {
442             return mContext.getSystemService(UserManager.class);
443         }
444 
getDevicePolicyManager()445         DevicePolicyManager getDevicePolicyManager() {
446             return mContext.getSystemService(DevicePolicyManager.class);
447         }
448 
setSystemProperty(String key, String value)449         void setSystemProperty(String key, String value) {
450             SystemProperties.set(key, value);
451         }
452 
getRoleManagerWrapper()453         RoleManagerWrapper getRoleManagerWrapper() {
454             return mRoleManagerWrapper;
455         }
456     }
457 
BugreportManagerServiceImpl(Context context)458     BugreportManagerServiceImpl(Context context) {
459         this(new Injector(
460                 context, SystemConfig.getInstance().getBugreportWhitelistedPackages(),
461                 new AtomicFile(new File(new File(
462                         Environment.getDataDirectory(), "system"), "bugreport-mapping.xml"))));
463     }
464 
465     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
BugreportManagerServiceImpl(Injector injector)466     BugreportManagerServiceImpl(Injector injector) {
467         mInjector = injector;
468         mContext = injector.getContext();
469         mAppOps = mContext.getSystemService(AppOpsManager.class);
470         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
471         mBugreportFileManager = new BugreportFileManager(injector.getMappingFile());
472         mBugreportAllowlistedPackages = injector.getAllowlistedPackages();
473     }
474 
475     @Override
476     @RequiresPermission(android.Manifest.permission.DUMP)
preDumpUiData(String callingPackage)477     public void preDumpUiData(String callingPackage) {
478         enforcePermission(callingPackage, Binder.getCallingUid(), true);
479 
480         synchronized (mLock) {
481             preDumpUiDataLocked(callingPackage);
482         }
483     }
484 
485     @Override
486     @RequiresPermission(android.Manifest.permission.DUMP)
startBugreport(int callingUidUnused, String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, int bugreportMode, int bugreportFlags, IDumpstateListener listener, boolean isScreenshotRequested, boolean skipUserConsentUnused)487     public void startBugreport(int callingUidUnused, String callingPackage,
488             FileDescriptor bugreportFd, FileDescriptor screenshotFd,
489             int bugreportMode, int bugreportFlags, IDumpstateListener listener,
490             boolean isScreenshotRequested, boolean skipUserConsentUnused) {
491         Objects.requireNonNull(callingPackage);
492         Objects.requireNonNull(bugreportFd);
493         Objects.requireNonNull(listener);
494         validateBugreportMode(bugreportMode);
495         validateBugreportFlags(bugreportFlags);
496 
497         int callingUid = Binder.getCallingUid();
498         enforcePermission(callingPackage, callingUid, bugreportMode
499                 == BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */);
500         ensureUserCanTakeBugReport(bugreportMode);
501 
502         Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
503         final MutableBoolean handoffLock = new MutableBoolean(false);
504         if (sFeatureFlags.asyncStartBugreport()) {
505             synchronized (handoffLock) {
506                 new Thread(()-> {
507                     try {
508                         synchronized (mLock) {
509                             synchronized (handoffLock) {
510                                 handoffLock.value = true;
511                                 handoffLock.notifyAll();
512                             }
513                             startBugreportLocked(
514                                     callingUid,
515                                     callingPackage,
516                                     bugreportFd,
517                                     screenshotFd,
518                                     bugreportMode,
519                                     bugreportFlags,
520                                     listener,
521                                     isScreenshotRequested);
522                         }
523                     } catch (Exception e) {
524                         Slog.e(TAG, "Cannot start a new bugreport due to an unknown error", e);
525                         reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
526                     }
527                 }, "BugreportManagerServiceThread").start();
528                 try {
529                     while (!handoffLock.value) { // handle the rare case of a spurious wakeup
530                         handoffLock.wait(DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS);
531                     }
532                 } catch (InterruptedException e) {
533                     Slog.e(TAG, "Unexpectedly interrupted waiting for startBugreportLocked", e);
534                 }
535             }
536         } else {
537             synchronized (mLock) {
538                 startBugreportLocked(
539                         callingUid,
540                         callingPackage,
541                         bugreportFd,
542                         screenshotFd,
543                         bugreportMode,
544                         bugreportFlags,
545                         listener,
546                         isScreenshotRequested);
547             }
548         }
549     }
550 
551     @Override
552     @RequiresPermission(android.Manifest.permission.DUMP) // or carrier privileges
cancelBugreport(int callingUidUnused, String callingPackage)553     public void cancelBugreport(int callingUidUnused, String callingPackage) {
554         int callingUid = Binder.getCallingUid();
555         enforcePermission(callingPackage, callingUid, true /* checkCarrierPrivileges */);
556 
557         Slogf.i(TAG, "Cancelling bugreport for %s / %d", callingPackage, callingUid);
558         synchronized (mLock) {
559             IDumpstate ds = getDumpstateBinderServiceLocked();
560             if (ds == null) {
561                 Slog.w(TAG, "cancelBugreport: Could not find native dumpstate service");
562                 return;
563             }
564             try {
565                 // Note: this may throw SecurityException back out to the caller if they aren't
566                 // allowed to cancel the report, in which case we should NOT stop the dumpstate
567                 // service, since that would unintentionally kill some other app's bugreport, which
568                 // we specifically disallow.
569                 ds.cancelBugreport(callingUid, callingPackage);
570             } catch (RemoteException e) {
571                 Slog.e(TAG, "RemoteException in cancelBugreport", e);
572             }
573             stopDumpstateBinderServiceLocked();
574         }
575     }
576 
577     @Override
578     @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
retrieveBugreport(int callingUidUnused, String callingPackage, int userId, FileDescriptor bugreportFd, String bugreportFile, boolean keepBugreportOnRetrievalUnused, boolean skipUserConsentUnused, IDumpstateListener listener)579     public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
580             FileDescriptor bugreportFd, String bugreportFile,
581             boolean keepBugreportOnRetrievalUnused, boolean skipUserConsentUnused,
582             IDumpstateListener listener) {
583         int callingUid = Binder.getCallingUid();
584         enforcePermission(callingPackage, callingUid, false);
585 
586         Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
587         try {
588             mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
589                     mContext, mContext.getPackageManager(), new Pair<>(callingUid, callingPackage),
590                     userId, bugreportFile, /* forceUpdateMapping= */ false);
591         } catch (IllegalArgumentException e) {
592             Slog.e(TAG, e.getMessage());
593             reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
594             return;
595         }
596 
597         synchronized (mLock) {
598             if (isDumpstateBinderServiceRunningLocked()) {
599                 Slog.w(TAG, "'dumpstate' is already running. Cannot retrieve a bugreport"
600                         + " while another one is currently in progress.");
601                 reportError(listener,
602                         IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
603                 return;
604             }
605 
606             IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
607             if (ds == null) {
608                 Slog.w(TAG, "Unable to get bugreport service");
609                 reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
610                 return;
611             }
612 
613             boolean skipUserConsent = mBugreportFileManager.canSkipConsentScreen(
614                     callingPackage, /* isFullReport = */ false);
615 
616             // Wrap the listener so we can intercept binder events directly.
617             DumpstateListener myListener = new DumpstateListener(listener, ds,
618                     new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true,
619                     !skipUserConsent, /* isDeferredReport = */ true);
620 
621             boolean keepBugreportOnRetrieval = false;
622             if (onboardingBugreportV2Enabled()) {
623                 keepBugreportOnRetrieval = mBugreportFileManager.mBugreportFilesToPersist.contains(
624                         bugreportFile);
625             }
626 
627             setCurrentDumpstateListenerLocked(myListener);
628             try {
629                 ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd,
630                         bugreportFile, keepBugreportOnRetrieval, skipUserConsent, myListener);
631             } catch (RemoteException e) {
632                 Slog.e(TAG, "RemoteException in retrieveBugreport", e);
633             }
634         }
635     }
636 
637     @GuardedBy("mLock")
setCurrentDumpstateListenerLocked(DumpstateListener listener)638     private void setCurrentDumpstateListenerLocked(DumpstateListener listener) {
639         if (mCurrentDumpstateListener != null) {
640             Slogf.w(TAG, "setCurrentDumpstateListenerLocked(%s): called when "
641                     + "mCurrentDumpstateListener is already set (%s)", listener,
642                     mCurrentDumpstateListener);
643         }
644         mCurrentDumpstateListener = listener;
645     }
646 
validateBugreportMode(@ugreportParams.BugreportMode int mode)647     private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
648         if (mode != BugreportParams.BUGREPORT_MODE_FULL
649                 && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
650                 && mode != BugreportParams.BUGREPORT_MODE_REMOTE
651                 && mode != BugreportParams.BUGREPORT_MODE_WEAR
652                 && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY
653                 && mode != BugreportParams.BUGREPORT_MODE_WIFI
654                 && mode != BugreportParams.BUGREPORT_MODE_ONBOARDING) {
655             Slog.w(TAG, "Unknown bugreport mode: " + mode);
656             throw new IllegalArgumentException("Unknown bugreport mode: " + mode);
657         }
658     }
659 
validateBugreportFlags(int flags)660     private void validateBugreportFlags(int flags) {
661         flags = clearBugreportFlag(flags,
662                 BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
663                         | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT
664                         | BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL);
665         if (flags != 0) {
666             Slog.w(TAG, "Unknown bugreport flags: " + flags);
667             throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
668         }
669     }
670 
enforcePermission( String callingPackage, int callingUid, boolean checkCarrierPrivileges)671     private void enforcePermission(
672             String callingPackage, int callingUid, boolean checkCarrierPrivileges) {
673         mAppOps.checkPackage(callingUid, callingPackage);
674 
675         // To gain access through the DUMP permission, the OEM has to allow this package explicitly
676         // via sysconfig and privileged permissions.
677         boolean allowlisted = mBugreportAllowlistedPackages.contains(callingPackage);
678         if (!allowlisted) {
679             final long token = Binder.clearCallingIdentity();
680             try {
681                 allowlisted = mInjector.getRoleManagerWrapper().getRoleHolders(
682                         ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
683             } finally {
684                 Binder.restoreCallingIdentity(token);
685             }
686         }
687 
688         if (allowlisted && mContext.checkCallingOrSelfPermission(
689                 android.Manifest.permission.DUMP) == PackageManager.PERMISSION_GRANTED) {
690             return;
691         }
692 
693         // For carrier privileges, this can include user-installed apps. This is essentially a
694         // function of the current active SIM(s) in the device to let carrier apps through.
695         final long token = Binder.clearCallingIdentity();
696         try {
697             if (checkCarrierPrivileges
698                     && mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
699                             == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
700                 return;
701             }
702         } finally {
703             Binder.restoreCallingIdentity(token);
704         }
705 
706         String message =
707                 callingPackage
708                         + " does not hold the DUMP permission or is not bugreport-whitelisted or "
709                         + "does not have an allowed role "
710                         + (checkCarrierPrivileges ? "and does not have carrier privileges " : "")
711                         + "to request a bugreport";
712         Slog.w(TAG, message);
713         throw new SecurityException(message);
714     }
715 
716     /**
717      * Validates that the calling user is an admin user or, when bugreport is requested remotely
718      * that the user is an affiliated user.
719      *
720      * @throws IllegalArgumentException if the calling user or the parent of the calling profile
721      *                                  user is not an admin user.
722      */
ensureUserCanTakeBugReport(int bugreportMode)723     private void ensureUserCanTakeBugReport(int bugreportMode) {
724         // Get the calling userId before clearing the caller identity.
725         int effectiveCallingUserId = UserHandle.getUserId(Binder.getCallingUid());
726         boolean isAdminUser = false;
727         final long identity = Binder.clearCallingIdentity();
728         try {
729             UserInfo profileParent =
730                     mInjector.getUserManager().getProfileParent(effectiveCallingUserId);
731             if (profileParent == null) {
732                 isAdminUser = mInjector.getUserManager().isUserAdmin(effectiveCallingUserId);
733             } else {
734                 // If the caller is a profile, we need to check its parent user instead.
735                 // Therefore setting the profile parent user as the effective calling user.
736                 effectiveCallingUserId = profileParent.id;
737                 isAdminUser = profileParent.isAdmin();
738             }
739         } finally {
740             Binder.restoreCallingIdentity(identity);
741         }
742         if (!isAdminUser) {
743             if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
744                     && isUserAffiliated(effectiveCallingUserId)) {
745                 return;
746             }
747             logAndThrow(TextUtils.formatSimple("Calling user %s is not an admin user."
748                     + " Only admin users and their profiles are allowed to take bugreport.",
749                     effectiveCallingUserId));
750         }
751     }
752 
753     /**
754      * Returns {@code true} if the device has device owner and the specified user is affiliated
755      * with the device owner.
756      */
isUserAffiliated(int userId)757     private boolean isUserAffiliated(int userId) {
758         DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
759         int deviceOwnerUid = dpm.getDeviceOwnerUserId();
760         if (deviceOwnerUid == UserHandle.USER_NULL) {
761             return false;
762         }
763 
764         if (DEBUG) {
765             Slog.d(TAG, "callingUid: " + userId + " deviceOwnerUid: " + deviceOwnerUid);
766         }
767 
768         if (userId != deviceOwnerUid && !dpm.isAffiliatedUser(userId)) {
769             logAndThrow("User " + userId + " is not affiliated to the device owner.");
770         }
771         return true;
772     }
773 
774     @GuardedBy("mLock")
preDumpUiDataLocked(String callingPackage)775     private void preDumpUiDataLocked(String callingPackage) {
776         mPreDumpedDataUid = OptionalInt.empty();
777 
778         if (isDumpstateBinderServiceRunningLocked()) {
779             Slog.e(TAG, "'dumpstate' is already running. "
780                     + "Cannot pre-dump data while another operation is currently in progress.");
781             return;
782         }
783 
784         IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
785         if (ds == null) {
786             Slog.e(TAG, "Unable to get bugreport service");
787             return;
788         }
789 
790         try {
791             ds.preDumpUiData(callingPackage);
792         } catch (RemoteException e) {
793             return;
794         } finally {
795             // dumpstate service is already started now. We need to kill it to manage the
796             // lifecycle correctly. If we don't subsequent callers will get
797             // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
798             stopDumpstateBinderServiceLocked();
799         }
800 
801 
802         mPreDumpedDataUid = OptionalInt.of(Binder.getCallingUid());
803     }
804 
805     @GuardedBy("mLock")
startBugreportLocked(int callingUid, String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, int bugreportMode, int bugreportFlags, IDumpstateListener listener, boolean isScreenshotRequested)806     private void startBugreportLocked(int callingUid, String callingPackage,
807             FileDescriptor bugreportFd, FileDescriptor screenshotFd,
808             int bugreportMode, int bugreportFlags, IDumpstateListener listener,
809             boolean isScreenshotRequested) {
810         if (isDumpstateBinderServiceRunningLocked()) {
811             Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport"
812                     + " while another operation is currently in progress.");
813             reportError(listener, IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
814             return;
815         }
816 
817         if ((bugreportFlags & BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA) != 0) {
818             if (mPreDumpedDataUid.isEmpty()) {
819                 bugreportFlags = clearBugreportFlag(bugreportFlags,
820                         BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
821                 Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA."
822                         + " No pre-dumped data is available.");
823             } else if (mPreDumpedDataUid.getAsInt() != callingUid) {
824                 bugreportFlags = clearBugreportFlag(bugreportFlags,
825                         BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
826                 Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA."
827                         + " Data was pre-dumped by a different UID.");
828             }
829         }
830 
831         boolean isDeferredConsentReport =
832                 (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
833 
834         boolean keepBugreportOnRetrieval =
835                 (bugreportFlags & BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL) != 0;
836 
837         IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
838         if (ds == null) {
839             Slog.w(TAG, "Unable to get bugreport service");
840             reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
841             return;
842         }
843         boolean skipUserConsent = mBugreportFileManager.canSkipConsentScreen(
844                 callingPackage, !isDeferredConsentReport);
845         DumpstateListener myListener = new DumpstateListener(listener, ds,
846                 new Pair<>(callingUid, callingPackage),
847                 /* reportFinishedFile = */ isDeferredConsentReport, keepBugreportOnRetrieval,
848                 !isDeferredConsentReport && !skipUserConsent,
849                 isDeferredConsentReport);
850         setCurrentDumpstateListenerLocked(myListener);
851         try {
852             ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
853                     bugreportFlags, myListener, isScreenshotRequested, skipUserConsent);
854         } catch (RemoteException e) {
855             // dumpstate service is already started now. We need to kill it to manage the
856             // lifecycle correctly. If we don't subsequent callers will get
857             // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
858             // Note that listener will be notified by the death recipient below.
859             cancelBugreport(callingUid, callingPackage);
860         }
861     }
862 
863     @GuardedBy("mLock")
isDumpstateBinderServiceRunningLocked()864     private boolean isDumpstateBinderServiceRunningLocked() {
865         return getDumpstateBinderServiceLocked() != null;
866     }
867 
868     @GuardedBy("mLock")
869     @Nullable
getDumpstateBinderServiceLocked()870     private IDumpstate getDumpstateBinderServiceLocked() {
871         // Note that the binder service on the native side is "dumpstate".
872         return IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
873     }
874 
875     /*
876      * Start and get a handle to the native implementation of {@code IDumpstate} which does the
877      * actual bugreport generation.
878      *
879      * <p>Generating bugreports requires root privileges. To limit the footprint
880      * of the root access, the actual generation in Dumpstate binary is accessed as a
881      * oneshot service 'bugreport'.
882      *
883      * <p>Note that starting the service is achieved through setting a system property, which is
884      * not thread-safe. So the lock here offers thread-safety only among callers of the API.
885      */
886     @GuardedBy("mLock")
startAndGetDumpstateBinderServiceLocked()887     private IDumpstate startAndGetDumpstateBinderServiceLocked() {
888         // Start bugreport service.
889         mInjector.setSystemProperty("ctl.start", BUGREPORT_SERVICE);
890 
891         IDumpstate ds = null;
892         boolean timedOut = false;
893         int totalTimeWaitedMillis = 0;
894         int seedWaitTimeMillis = 500;
895         while (!timedOut) {
896             ds = getDumpstateBinderServiceLocked();
897             if (ds != null) {
898                 Slog.i(TAG, "Got bugreport service handle.");
899                 break;
900             }
901             SystemClock.sleep(seedWaitTimeMillis);
902             Slog.i(TAG,
903                     "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)");
904             totalTimeWaitedMillis += seedWaitTimeMillis;
905             seedWaitTimeMillis *= 2;
906             timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS;
907         }
908         if (timedOut) {
909             Slog.w(TAG,
910                     "Timed out waiting to get dumpstate service handle ("
911                     + totalTimeWaitedMillis + "ms)");
912         }
913         return ds;
914     }
915 
916     @GuardedBy("mLock")
stopDumpstateBinderServiceLocked()917     private void stopDumpstateBinderServiceLocked() {
918         // This tells init to cancel bugreportd service. Note that this is achieved through
919         // setting a system property which is not thread-safe. So the lock here offers
920         // thread-safety only among callers of the API.
921         mInjector.setSystemProperty("ctl.stop", BUGREPORT_SERVICE);
922     }
923 
924     @RequiresPermission(android.Manifest.permission.DUMP)
925     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)926     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
927         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
928 
929         pw.printf("Allow-listed packages: %s\n", mBugreportAllowlistedPackages);
930 
931         synchronized (mLock) {
932             pw.print("Pre-dumped data UID: ");
933             if (mPreDumpedDataUid.isEmpty()) {
934                 pw.println("none");
935             } else {
936                 pw.println(mPreDumpedDataUid.getAsInt());
937             }
938 
939             if (mCurrentDumpstateListener == null) {
940                 pw.println("Not taking a bug report");
941             } else {
942                 mCurrentDumpstateListener.dump(pw);
943             }
944 
945             if (mNumberFinishedBugreports == 0) {
946                 pw.println("No finished bugreports");
947             } else {
948                 pw.printf("%d finished bugreport%s. Last %d:\n", mNumberFinishedBugreports,
949                         (mNumberFinishedBugreports > 1 ? "s" : ""),
950                         Math.min(mNumberFinishedBugreports, LOCAL_LOG_SIZE));
951                 mFinishedBugreports.dump("  ", pw);
952             }
953         }
954 
955         synchronized (mBugreportFileManager.mLock) {
956             if (!mBugreportFileManager.mReadBugreportMapping) {
957                 pw.println("Has not read bugreport mapping");
958             }
959             int numberFiles = mBugreportFileManager.mBugreportFiles.size();
960             pw.printf("%d pending file%s", numberFiles, (numberFiles > 1 ? "s" : ""));
961             if (numberFiles > 0) {
962                 for (int i = 0; i < numberFiles; i++) {
963                     Pair<Integer, String> caller = mBugreportFileManager.mBugreportFiles.keyAt(i);
964                     ArraySet<String> files = mBugreportFileManager.mBugreportFiles.valueAt(i);
965                     pw.printf("  %s: %s\n", callerToString(caller), files);
966                 }
967             } else {
968                 pw.println();
969             }
970         }
971     }
972 
callerToString(@ullable Pair<Integer, String> caller)973     private static String callerToString(@Nullable Pair<Integer, String> caller) {
974         return (caller == null) ? "N/A" : caller.second + "/" + caller.first;
975     }
976 
clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag)977     private int clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag) {
978         flags &= ~flag;
979         return flags;
980     }
981 
reportError(IDumpstateListener listener, int errorCode)982     private void reportError(IDumpstateListener listener, int errorCode) {
983         try {
984             listener.onError(errorCode);
985         } catch (RemoteException e) {
986             // Something went wrong in binder or app process. There's nothing to do here.
987             Slog.w(TAG, "onError() transaction threw RemoteException: " + e.getMessage());
988         }
989     }
990 
logAndThrow(String message)991     private void logAndThrow(String message) {
992         Slog.w(TAG, message);
993         throw new IllegalArgumentException(message);
994     }
995 
996     private final class DumpstateListener extends IDumpstateListener.Stub
997             implements DeathRecipient {
998 
999         private static int sNextId;
1000 
1001         private final int mId = ++sNextId; // used for debugging purposes only
1002         private final IDumpstateListener mListener;
1003         private final IDumpstate mDs;
1004         private final Pair<Integer, String> mCaller;
1005         private final boolean mReportFinishedFile;
1006         private int mProgress; // used for debugging purposes only
1007         private boolean mDone;
1008         private boolean mKeepBugreportOnRetrieval;
1009 
1010         private boolean mConsentGranted;
1011 
1012         private boolean mIsDeferredReport;
1013 
DumpstateListener(IDumpstateListener listener, IDumpstate ds, Pair<Integer, String> caller, boolean reportFinishedFile, boolean consentGranted, boolean isDeferredReport)1014         DumpstateListener(IDumpstateListener listener, IDumpstate ds,
1015                 Pair<Integer, String> caller, boolean reportFinishedFile,
1016                 boolean consentGranted, boolean isDeferredReport) {
1017             this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false,
1018                     consentGranted, isDeferredReport);
1019         }
1020 
DumpstateListener(IDumpstateListener listener, IDumpstate ds, Pair<Integer, String> caller, boolean reportFinishedFile, boolean keepBugreportOnRetrieval, boolean consentGranted, boolean isDeferredReport)1021         DumpstateListener(IDumpstateListener listener, IDumpstate ds,
1022                 Pair<Integer, String> caller, boolean reportFinishedFile,
1023                 boolean keepBugreportOnRetrieval, boolean consentGranted,
1024                 boolean isDeferredReport) {
1025             if (DEBUG) {
1026                 Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
1027             }
1028             mListener = listener;
1029             mDs = ds;
1030             mCaller = caller;
1031             mReportFinishedFile = reportFinishedFile;
1032             mKeepBugreportOnRetrieval = keepBugreportOnRetrieval;
1033             mConsentGranted = consentGranted;
1034             mIsDeferredReport = isDeferredReport;
1035             try {
1036                 mDs.asBinder().linkToDeath(this, 0);
1037             } catch (RemoteException e) {
1038                 Slog.e(TAG, "Unable to register Death Recipient for IDumpstate", e);
1039             }
1040         }
1041 
1042         @Override
onProgress(int progress)1043         public void onProgress(int progress) throws RemoteException {
1044             if (DEBUG) {
1045                 Slogf.d(TAG, "onProgress: %d", progress);
1046             }
1047             mProgress = progress;
1048             mListener.onProgress(progress);
1049         }
1050 
1051         @Override
onError(int errorCode)1052         public void onError(int errorCode) throws RemoteException {
1053             Slogf.e(TAG, "onError(): %d", errorCode);
1054             synchronized (mLock) {
1055                 releaseItselfLocked();
1056                 reportFinishedLocked("ErroCode: " + errorCode);
1057             }
1058             mListener.onError(errorCode);
1059         }
1060 
1061         @Override
onFinished(String bugreportFile)1062         public void onFinished(String bugreportFile) throws RemoteException {
1063             Slogf.i(TAG, "onFinished(): %s", bugreportFile);
1064             synchronized (mLock) {
1065                 releaseItselfLocked();
1066                 reportFinishedLocked("File: " + bugreportFile);
1067             }
1068             if (mReportFinishedFile) {
1069                 mBugreportFileManager.addBugreportFileForCaller(
1070                         mCaller, bugreportFile, mKeepBugreportOnRetrieval);
1071             } else if (DEBUG) {
1072                 Slog.d(TAG, "Not reporting finished file");
1073             }
1074             mBugreportFileManager.logConsentGrantedForCaller(
1075                     mCaller.second, mConsentGranted, mIsDeferredReport);
1076             mListener.onFinished(bugreportFile);
1077         }
1078 
1079         @Override
onScreenshotTaken(boolean success)1080         public void onScreenshotTaken(boolean success) throws RemoteException {
1081             if (DEBUG) {
1082                 Slogf.d(TAG, "onScreenshotTaken(): %b", success);
1083             }
1084             mListener.onScreenshotTaken(success);
1085         }
1086 
1087         @Override
onUiIntensiveBugreportDumpsFinished()1088         public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
1089             if (DEBUG) {
1090                 Slogf.d(TAG, "onUiIntensiveBugreportDumpsFinished()");
1091             }
1092             mListener.onUiIntensiveBugreportDumpsFinished();
1093         }
1094 
1095         @Override
binderDied()1096         public void binderDied() {
1097             try {
1098                 // Allow a small amount of time for any error or finished callbacks to be made.
1099                 // This ensures that the listener does not receive an erroneous runtime error
1100                 // callback.
1101                 Thread.sleep(1000);
1102             } catch (InterruptedException ignored) {
1103             }
1104             synchronized (mLock) {
1105                 if (!mDone) {
1106                     // If we have not gotten a "done" callback this must be a crash.
1107                     Slog.e(TAG, "IDumpstate likely crashed. Notifying listener");
1108                     try {
1109                         mListener.onError(IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
1110                     } catch (RemoteException ignored) {
1111                         // If listener is not around, there isn't anything to do here.
1112                     }
1113                 }
1114             }
1115             mDs.asBinder().unlinkToDeath(this, 0);
1116         }
1117 
1118         @Override
toString()1119         public String toString() {
1120             return "DumpstateListener[id=" + mId + ", progress=" + mProgress + "]";
1121         }
1122 
1123         @GuardedBy("mLock")
reportFinishedLocked(String message)1124         private void reportFinishedLocked(String message) {
1125             mNumberFinishedBugreports++;
1126             mFinishedBugreports.log("Caller: " + callerToString(mCaller) + " " + message);
1127         }
1128 
dump(PrintWriter pw)1129         private void dump(PrintWriter pw) {
1130             pw.println("DumpstateListener:");
1131             pw.printf("  id: %d\n", mId);
1132             pw.printf("  caller: %s\n", callerToString(mCaller));
1133             pw.printf("  reports finished file: %b\n", mReportFinishedFile);
1134             pw.printf("  progress: %d\n", mProgress);
1135             pw.printf("  done: %b\n", mDone);
1136         }
1137 
1138         @GuardedBy("mLock")
releaseItselfLocked()1139         private void releaseItselfLocked() {
1140             mDone = true;
1141             if (mCurrentDumpstateListener == this) {
1142                 if (DEBUG) {
1143                     Slogf.d(TAG, "releaseItselfLocked(): releasing %s", this);
1144                 }
1145                 mCurrentDumpstateListener = null;
1146             } else {
1147                 Slogf.w(TAG, "releaseItselfLocked(): " + this + " is finished, but current listener"
1148                         + " is " + mCurrentDumpstateListener);
1149             }
1150         }
1151     }
1152 }
1153