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