1 /* 2 * Copyright (C) 2017 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.car; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.car.Car; 22 import android.car.diagnostic.CarDiagnosticEvent; 23 import android.car.diagnostic.CarDiagnosticManager; 24 import android.car.diagnostic.ICarDiagnostic; 25 import android.car.diagnostic.ICarDiagnosticEventListener; 26 import android.content.Context; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.util.ArrayMap; 30 import android.util.Log; 31 import com.android.car.hal.DiagnosticHalService.DiagnosticCapabilities; 32 import com.android.car.internal.CarPermission; 33 import com.android.car.Listeners.ClientWithRate; 34 import com.android.car.hal.DiagnosticHalService; 35 import com.android.internal.annotations.GuardedBy; 36 import java.io.PrintWriter; 37 import java.util.Arrays; 38 import java.util.ConcurrentModificationException; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.LinkedList; 42 import java.util.List; 43 import java.util.Objects; 44 import java.util.Set; 45 import java.util.concurrent.locks.ReentrantLock; 46 47 /** @hide */ 48 public class CarDiagnosticService extends ICarDiagnostic.Stub 49 implements CarServiceBase, DiagnosticHalService.DiagnosticListener { 50 /** lock to access diagnostic structures */ 51 private final ReentrantLock mDiagnosticLock = new ReentrantLock(); 52 /** hold clients callback */ 53 @GuardedBy("mDiagnosticLock") 54 private final LinkedList<DiagnosticClient> mClients = new LinkedList<>(); 55 56 /** key: diagnostic type. */ 57 @GuardedBy("mDiagnosticLock") 58 private final HashMap<Integer, Listeners<DiagnosticClient>> mDiagnosticListeners = 59 new HashMap<>(); 60 61 /** the latest live frame data. */ 62 @GuardedBy("mDiagnosticLock") 63 private final LiveFrameRecord mLiveFrameDiagnosticRecord = new LiveFrameRecord(mDiagnosticLock); 64 65 /** the latest freeze frame data (key: DTC) */ 66 @GuardedBy("mDiagnosticLock") 67 private final FreezeFrameRecord mFreezeFrameDiagnosticRecords = new FreezeFrameRecord( 68 mDiagnosticLock); 69 70 private final DiagnosticHalService mDiagnosticHal; 71 72 private final Context mContext; 73 74 private final CarPermission mDiagnosticReadPermission; 75 76 private final CarPermission mDiagnosticClearPermission; 77 78 public CarDiagnosticService(Context context, DiagnosticHalService diagnosticHal) { 79 mContext = context; 80 mDiagnosticHal = diagnosticHal; 81 mDiagnosticReadPermission = new CarPermission(mContext, 82 Car.PERMISSION_CAR_DIAGNOSTIC_READ_ALL); 83 mDiagnosticClearPermission = new CarPermission(mContext, 84 Car.PERMISSION_CAR_DIAGNOSTIC_CLEAR); 85 } 86 87 @Override 88 public void init() { 89 mDiagnosticLock.lock(); 90 try { 91 mDiagnosticHal.setDiagnosticListener(this); 92 setInitialLiveFrame(); 93 setInitialFreezeFrames(); 94 } finally { 95 mDiagnosticLock.unlock(); 96 } 97 } 98 99 @Nullable 100 private CarDiagnosticEvent setInitialLiveFrame() { 101 CarDiagnosticEvent liveFrame = null; 102 if(mDiagnosticHal.getDiagnosticCapabilities().isLiveFrameSupported()) { 103 liveFrame = setRecentmostLiveFrame(mDiagnosticHal.getCurrentLiveFrame()); 104 } 105 return liveFrame; 106 } 107 108 private void setInitialFreezeFrames() { 109 if(mDiagnosticHal.getDiagnosticCapabilities().isFreezeFrameSupported() && 110 mDiagnosticHal.getDiagnosticCapabilities().isFreezeFrameInfoSupported()) { 111 long[] timestamps = mDiagnosticHal.getFreezeFrameTimestamps(); 112 if (timestamps != null) { 113 for (long timestamp : timestamps) { 114 setRecentmostFreezeFrame(mDiagnosticHal.getFreezeFrame(timestamp)); 115 } 116 } 117 } 118 } 119 120 @Nullable 121 private CarDiagnosticEvent setRecentmostLiveFrame(final CarDiagnosticEvent event) { 122 if (event != null) { 123 return mLiveFrameDiagnosticRecord.update(event.checkLiveFrame()); 124 } 125 return null; 126 } 127 128 @Nullable 129 private CarDiagnosticEvent setRecentmostFreezeFrame(final CarDiagnosticEvent event) { 130 if (event != null) { 131 return mFreezeFrameDiagnosticRecords.update(event.checkFreezeFrame()); 132 } 133 return null; 134 } 135 136 @Override 137 public void release() { 138 mDiagnosticLock.lock(); 139 try { 140 mDiagnosticListeners.forEach( 141 (Integer frameType, Listeners diagnosticListeners) -> 142 diagnosticListeners.release()); 143 mDiagnosticListeners.clear(); 144 mLiveFrameDiagnosticRecord.disableIfNeeded(); 145 mFreezeFrameDiagnosticRecords.disableIfNeeded(); 146 mClients.clear(); 147 } finally { 148 mDiagnosticLock.unlock(); 149 } 150 } 151 152 private void processDiagnosticData(List<CarDiagnosticEvent> events) { 153 ArrayMap<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> eventsByClient = 154 new ArrayMap<>(); 155 156 Listeners<DiagnosticClient> listeners = null; 157 158 mDiagnosticLock.lock(); 159 for (CarDiagnosticEvent event : events) { 160 if (event.isLiveFrame()) { 161 // record recent-most live frame information 162 setRecentmostLiveFrame(event); 163 listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_LIVE); 164 } else if (event.isFreezeFrame()) { 165 setRecentmostFreezeFrame(event); 166 listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FREEZE); 167 } else { 168 Log.w( 169 CarLog.TAG_DIAGNOSTIC, 170 String.format("received unknown diagnostic event: %s", event)); 171 continue; 172 } 173 174 if (null != listeners) { 175 for (ClientWithRate<DiagnosticClient> clientWithRate : listeners.getClients()) { 176 DiagnosticClient client = clientWithRate.getClient(); 177 List<CarDiagnosticEvent> clientEvents = eventsByClient.computeIfAbsent(client, 178 (DiagnosticClient diagnosticClient) -> new LinkedList<>()); 179 clientEvents.add(event); 180 } 181 } 182 } 183 mDiagnosticLock.unlock(); 184 185 for (ArrayMap.Entry<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> entry : 186 eventsByClient.entrySet()) { 187 CarDiagnosticService.DiagnosticClient client = entry.getKey(); 188 List<CarDiagnosticEvent> clientEvents = entry.getValue(); 189 190 client.dispatchDiagnosticUpdate(clientEvents); 191 } 192 } 193 194 /** Received diagnostic data from car. */ 195 @Override 196 public void onDiagnosticEvents(List<CarDiagnosticEvent> events) { 197 processDiagnosticData(events); 198 } 199 200 @Override 201 public boolean registerOrUpdateDiagnosticListener(int frameType, int rate, 202 ICarDiagnosticEventListener listener) { 203 boolean shouldStartDiagnostics = false; 204 CarDiagnosticService.DiagnosticClient diagnosticClient = null; 205 Integer oldRate = null; 206 Listeners<DiagnosticClient> diagnosticListeners = null; 207 mDiagnosticLock.lock(); 208 try { 209 mDiagnosticReadPermission.assertGranted(); 210 diagnosticClient = findDiagnosticClientLocked(listener); 211 Listeners.ClientWithRate<DiagnosticClient> diagnosticClientWithRate = null; 212 if (diagnosticClient == null) { 213 diagnosticClient = new DiagnosticClient(listener); 214 try { 215 listener.asBinder().linkToDeath(diagnosticClient, 0); 216 } catch (RemoteException e) { 217 Log.w( 218 CarLog.TAG_DIAGNOSTIC, 219 String.format( 220 "received RemoteException trying to register listener for %s", 221 frameType)); 222 return false; 223 } 224 mClients.add(diagnosticClient); 225 } 226 diagnosticListeners = mDiagnosticListeners.get(frameType); 227 if (diagnosticListeners == null) { 228 diagnosticListeners = new Listeners<>(rate); 229 mDiagnosticListeners.put(frameType, diagnosticListeners); 230 shouldStartDiagnostics = true; 231 } else { 232 oldRate = diagnosticListeners.getRate(); 233 diagnosticClientWithRate = 234 diagnosticListeners.findClientWithRate(diagnosticClient); 235 } 236 if (diagnosticClientWithRate == null) { 237 diagnosticClientWithRate = 238 new ClientWithRate<>(diagnosticClient, rate); 239 diagnosticListeners.addClientWithRate(diagnosticClientWithRate); 240 } else { 241 diagnosticClientWithRate.setRate(rate); 242 } 243 if (diagnosticListeners.getRate() > rate) { 244 diagnosticListeners.setRate(rate); 245 shouldStartDiagnostics = true; 246 } 247 diagnosticClient.addDiagnostic(frameType); 248 } finally { 249 mDiagnosticLock.unlock(); 250 } 251 Log.i( 252 CarLog.TAG_DIAGNOSTIC, 253 String.format( 254 "shouldStartDiagnostics = %s for %s at rate %d", 255 shouldStartDiagnostics, frameType, rate)); 256 // start diagnostic outside lock as it can take time. 257 if (shouldStartDiagnostics) { 258 if (!startDiagnostic(frameType, rate)) { 259 // failed. so remove from active diagnostic list. 260 Log.w(CarLog.TAG_DIAGNOSTIC, "startDiagnostic failed"); 261 mDiagnosticLock.lock(); 262 try { 263 diagnosticClient.removeDiagnostic(frameType); 264 if (oldRate != null) { 265 diagnosticListeners.setRate(oldRate); 266 } else { 267 mDiagnosticListeners.remove(frameType); 268 } 269 } finally { 270 mDiagnosticLock.unlock(); 271 } 272 return false; 273 } 274 } 275 return true; 276 } 277 278 private boolean startDiagnostic(int frameType, int rate) { 279 Log.i(CarLog.TAG_DIAGNOSTIC, String.format("starting diagnostic %s at rate %d", 280 frameType, rate)); 281 DiagnosticHalService diagnosticHal = getDiagnosticHal(); 282 if (diagnosticHal != null) { 283 if (!diagnosticHal.isReady()) { 284 Log.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready"); 285 return false; 286 } 287 switch (frameType) { 288 case CarDiagnosticManager.FRAME_TYPE_LIVE: 289 if (mLiveFrameDiagnosticRecord.isEnabled()) { 290 return true; 291 } 292 if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_LIVE, 293 rate)) { 294 mLiveFrameDiagnosticRecord.enable(); 295 return true; 296 } 297 break; 298 case CarDiagnosticManager.FRAME_TYPE_FREEZE: 299 if (mFreezeFrameDiagnosticRecords.isEnabled()) { 300 return true; 301 } 302 if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_FREEZE, 303 rate)) { 304 mFreezeFrameDiagnosticRecords.enable(); 305 return true; 306 } 307 break; 308 } 309 } 310 return false; 311 } 312 313 @Override 314 public void unregisterDiagnosticListener( 315 int frameType, ICarDiagnosticEventListener listener) { 316 boolean shouldStopDiagnostic = false; 317 boolean shouldRestartDiagnostic = false; 318 int newRate = 0; 319 mDiagnosticLock.lock(); 320 try { 321 DiagnosticClient diagnosticClient = findDiagnosticClientLocked(listener); 322 if (diagnosticClient == null) { 323 Log.i( 324 CarLog.TAG_DIAGNOSTIC, 325 String.format( 326 "trying to unregister diagnostic client %s for %s which is not registered", 327 listener, frameType)); 328 // never registered or already unregistered. 329 return; 330 } 331 diagnosticClient.removeDiagnostic(frameType); 332 if (diagnosticClient.getNumberOfActiveDiagnostic() == 0) { 333 diagnosticClient.release(); 334 mClients.remove(diagnosticClient); 335 } 336 Listeners<DiagnosticClient> diagnosticListeners = mDiagnosticListeners.get(frameType); 337 if (diagnosticListeners == null) { 338 // diagnostic not active 339 return; 340 } 341 ClientWithRate<DiagnosticClient> clientWithRate = 342 diagnosticListeners.findClientWithRate(diagnosticClient); 343 if (clientWithRate == null) { 344 return; 345 } 346 diagnosticListeners.removeClientWithRate(clientWithRate); 347 if (diagnosticListeners.getNumberOfClients() == 0) { 348 shouldStopDiagnostic = true; 349 mDiagnosticListeners.remove(frameType); 350 } else if (diagnosticListeners.updateRate()) { // rate changed 351 newRate = diagnosticListeners.getRate(); 352 shouldRestartDiagnostic = true; 353 } 354 } finally { 355 mDiagnosticLock.unlock(); 356 } 357 Log.i( 358 CarLog.TAG_DIAGNOSTIC, 359 String.format( 360 "shouldStopDiagnostic = %s, shouldRestartDiagnostic = %s for type %s", 361 shouldStopDiagnostic, shouldRestartDiagnostic, frameType)); 362 if (shouldStopDiagnostic) { 363 stopDiagnostic(frameType); 364 } else if (shouldRestartDiagnostic) { 365 startDiagnostic(frameType, newRate); 366 } 367 } 368 369 private void stopDiagnostic(int frameType) { 370 DiagnosticHalService diagnosticHal = getDiagnosticHal(); 371 if (diagnosticHal == null || !diagnosticHal.isReady()) { 372 Log.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready"); 373 return; 374 } 375 switch (frameType) { 376 case CarDiagnosticManager.FRAME_TYPE_LIVE: 377 if (mLiveFrameDiagnosticRecord.disableIfNeeded()) 378 diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_LIVE); 379 break; 380 case CarDiagnosticManager.FRAME_TYPE_FREEZE: 381 if (mFreezeFrameDiagnosticRecords.disableIfNeeded()) 382 diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FREEZE); 383 break; 384 } 385 } 386 387 private DiagnosticHalService getDiagnosticHal() { 388 return mDiagnosticHal; 389 } 390 391 // Expose DiagnosticCapabilities 392 public boolean isLiveFrameSupported() { 393 return getDiagnosticHal().getDiagnosticCapabilities().isLiveFrameSupported(); 394 } 395 396 public boolean isFreezeFrameNotificationSupported() { 397 return getDiagnosticHal().getDiagnosticCapabilities().isFreezeFrameSupported(); 398 } 399 400 public boolean isGetFreezeFrameSupported() { 401 DiagnosticCapabilities diagnosticCapabilities = 402 getDiagnosticHal().getDiagnosticCapabilities(); 403 return diagnosticCapabilities.isFreezeFrameInfoSupported() && 404 diagnosticCapabilities.isFreezeFrameSupported(); 405 } 406 407 public boolean isClearFreezeFramesSupported() { 408 DiagnosticCapabilities diagnosticCapabilities = 409 getDiagnosticHal().getDiagnosticCapabilities(); 410 return diagnosticCapabilities.isFreezeFrameClearSupported() && 411 diagnosticCapabilities.isFreezeFrameSupported(); 412 } 413 414 public boolean isSelectiveClearFreezeFramesSupported() { 415 DiagnosticCapabilities diagnosticCapabilities = 416 getDiagnosticHal().getDiagnosticCapabilities(); 417 return isClearFreezeFramesSupported() && 418 diagnosticCapabilities.isSelectiveClearFreezeFramesSupported(); 419 } 420 421 // ICarDiagnostic implementations 422 423 @Override 424 public CarDiagnosticEvent getLatestLiveFrame() { 425 mLiveFrameDiagnosticRecord.lock(); 426 CarDiagnosticEvent liveFrame = mLiveFrameDiagnosticRecord.getLastEvent(); 427 mLiveFrameDiagnosticRecord.unlock(); 428 return liveFrame; 429 } 430 431 @Override 432 public long[] getFreezeFrameTimestamps() { 433 mFreezeFrameDiagnosticRecords.lock(); 434 long[] timestamps = mFreezeFrameDiagnosticRecords.getFreezeFrameTimestamps(); 435 mFreezeFrameDiagnosticRecords.unlock(); 436 return timestamps; 437 } 438 439 @Override 440 @Nullable 441 public CarDiagnosticEvent getFreezeFrame(long timestamp) { 442 mFreezeFrameDiagnosticRecords.lock(); 443 CarDiagnosticEvent freezeFrame = mFreezeFrameDiagnosticRecords.getEvent(timestamp); 444 mFreezeFrameDiagnosticRecords.unlock(); 445 return freezeFrame; 446 } 447 448 @Override 449 public boolean clearFreezeFrames(long... timestamps) { 450 mDiagnosticClearPermission.assertGranted(); 451 if (!isClearFreezeFramesSupported()) 452 return false; 453 if (timestamps != null && timestamps.length != 0) { 454 if (!isSelectiveClearFreezeFramesSupported()) { 455 return false; 456 } 457 } 458 mFreezeFrameDiagnosticRecords.lock(); 459 mDiagnosticHal.clearFreezeFrames(timestamps); 460 mFreezeFrameDiagnosticRecords.clearEvents(); 461 mFreezeFrameDiagnosticRecords.unlock(); 462 return true; 463 } 464 465 /** 466 * Find DiagnosticClient from client list and return it. This should be called with mClients 467 * locked. 468 * 469 * @param listener 470 * @return null if not found. 471 */ 472 @GuardedBy("mDiagnosticLock") 473 private CarDiagnosticService.DiagnosticClient findDiagnosticClientLocked( 474 ICarDiagnosticEventListener listener) { 475 IBinder binder = listener.asBinder(); 476 for (DiagnosticClient diagnosticClient : mClients) { 477 if (diagnosticClient.isHoldingListenerBinder(binder)) { 478 return diagnosticClient; 479 } 480 } 481 return null; 482 } 483 484 private void removeClient(DiagnosticClient diagnosticClient) { 485 mDiagnosticLock.lock(); 486 try { 487 for (int diagnostic : diagnosticClient.getDiagnosticArray()) { 488 unregisterDiagnosticListener( 489 diagnostic, diagnosticClient.getICarDiagnosticEventListener()); 490 } 491 mClients.remove(diagnosticClient); 492 } finally { 493 mDiagnosticLock.unlock(); 494 } 495 } 496 497 /** internal instance for pending client request */ 498 private class DiagnosticClient implements Listeners.IListener { 499 /** callback for diagnostic events */ 500 private final ICarDiagnosticEventListener mListener; 501 502 private final Set<Integer> mActiveDiagnostics = new HashSet<>(); 503 504 /** when false, it is already released */ 505 private volatile boolean mActive = true; 506 507 DiagnosticClient(ICarDiagnosticEventListener listener) { 508 this.mListener = listener; 509 } 510 511 @Override 512 public boolean equals(Object o) { 513 return o instanceof DiagnosticClient 514 && mListener.asBinder() 515 == ((DiagnosticClient) o).mListener.asBinder(); 516 } 517 518 boolean isHoldingListenerBinder(IBinder listenerBinder) { 519 return mListener.asBinder() == listenerBinder; 520 } 521 522 void addDiagnostic(int frameType) { 523 mActiveDiagnostics.add(frameType); 524 } 525 526 void removeDiagnostic(int frameType) { 527 mActiveDiagnostics.remove(frameType); 528 } 529 530 int getNumberOfActiveDiagnostic() { 531 return mActiveDiagnostics.size(); 532 } 533 534 int[] getDiagnosticArray() { 535 return mActiveDiagnostics.stream().mapToInt(Integer::intValue).toArray(); 536 } 537 538 ICarDiagnosticEventListener getICarDiagnosticEventListener() { 539 return mListener; 540 } 541 542 /** Client dead. should remove all diagnostic requests from client */ 543 @Override 544 public void binderDied() { 545 mListener.asBinder().unlinkToDeath(this, 0); 546 removeClient(this); 547 } 548 549 void dispatchDiagnosticUpdate(List<CarDiagnosticEvent> events) { 550 if (events.size() != 0 && mActive) { 551 try { 552 mListener.onDiagnosticEvents(events); 553 } catch (RemoteException e) { 554 //ignore. crash will be handled by death handler 555 } 556 } 557 } 558 559 @Override 560 public void release() { 561 if (mActive) { 562 mListener.asBinder().unlinkToDeath(this, 0); 563 mActiveDiagnostics.clear(); 564 mActive = false; 565 } 566 } 567 } 568 569 private static abstract class DiagnosticRecord { 570 private final ReentrantLock mLock; 571 protected boolean mEnabled = false; 572 573 DiagnosticRecord(ReentrantLock lock) { 574 mLock = lock; 575 } 576 577 void lock() { 578 mLock.lock(); 579 } 580 581 void unlock() { 582 mLock.unlock(); 583 } 584 585 boolean isEnabled() { 586 return mEnabled; 587 } 588 589 void enable() { 590 mEnabled = true; 591 } 592 593 abstract boolean disableIfNeeded(); 594 abstract CarDiagnosticEvent update(CarDiagnosticEvent newEvent); 595 } 596 597 private static class LiveFrameRecord extends DiagnosticRecord { 598 /** Store the most recent live-frame. */ 599 CarDiagnosticEvent mLastEvent = null; 600 601 LiveFrameRecord(ReentrantLock lock) { 602 super(lock); 603 } 604 605 @Override 606 boolean disableIfNeeded() { 607 if (!mEnabled) return false; 608 mEnabled = false; 609 mLastEvent = null; 610 return true; 611 } 612 613 @Override 614 CarDiagnosticEvent update(@NonNull CarDiagnosticEvent newEvent) { 615 Objects.requireNonNull(newEvent); 616 if((null == mLastEvent) || mLastEvent.isEarlierThan(newEvent)) 617 mLastEvent = newEvent; 618 return mLastEvent; 619 } 620 621 CarDiagnosticEvent getLastEvent() { 622 return mLastEvent; 623 } 624 } 625 626 private static class FreezeFrameRecord extends DiagnosticRecord { 627 /** Store the timestamp --> freeze frame mapping. */ 628 HashMap<Long, CarDiagnosticEvent> mEvents = new HashMap<>(); 629 630 FreezeFrameRecord(ReentrantLock lock) { 631 super(lock); 632 } 633 634 @Override 635 boolean disableIfNeeded() { 636 if (!mEnabled) return false; 637 mEnabled = false; 638 clearEvents(); 639 return true; 640 } 641 642 void clearEvents() { 643 mEvents.clear(); 644 } 645 646 @Override 647 CarDiagnosticEvent update(@NonNull CarDiagnosticEvent newEvent) { 648 mEvents.put(newEvent.timestamp, newEvent); 649 return newEvent; 650 } 651 652 long[] getFreezeFrameTimestamps() { 653 return mEvents.keySet().stream().mapToLong(Long::longValue).toArray(); 654 } 655 656 CarDiagnosticEvent getEvent(long timestamp) { 657 return mEvents.get(timestamp); 658 } 659 660 Iterable<CarDiagnosticEvent> getEvents() { 661 return mEvents.values(); 662 } 663 } 664 665 @Override 666 public void dump(PrintWriter writer) { 667 writer.println("*CarDiagnosticService*"); 668 writer.println("**last events for diagnostics**"); 669 if (null != mLiveFrameDiagnosticRecord.getLastEvent()) { 670 writer.println("last live frame event: "); 671 writer.println(mLiveFrameDiagnosticRecord.getLastEvent()); 672 } 673 writer.println("freeze frame events: "); 674 mFreezeFrameDiagnosticRecords.getEvents().forEach(writer::println); 675 writer.println("**clients**"); 676 try { 677 for (DiagnosticClient client : mClients) { 678 if (client != null) { 679 try { 680 writer.println( 681 "binder:" 682 + client.mListener 683 + " active diagnostics:" 684 + Arrays.toString(client.getDiagnosticArray())); 685 } catch (ConcurrentModificationException e) { 686 writer.println("concurrent modification happened"); 687 } 688 } else { 689 writer.println("null client"); 690 } 691 } 692 } catch (ConcurrentModificationException e) { 693 writer.println("concurrent modification happened"); 694 } 695 writer.println("**diagnostic listeners**"); 696 try { 697 for (int diagnostic : mDiagnosticListeners.keySet()) { 698 Listeners diagnosticListeners = mDiagnosticListeners.get(diagnostic); 699 if (diagnosticListeners != null) { 700 writer.println( 701 " Diagnostic:" 702 + diagnostic 703 + " num client:" 704 + diagnosticListeners.getNumberOfClients() 705 + " rate:" 706 + diagnosticListeners.getRate()); 707 } 708 } 709 } catch (ConcurrentModificationException e) { 710 writer.println("concurrent modification happened"); 711 } 712 } 713 } 714