1 /* 2 * Copyright (C) 2023 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.display; 18 19 import static android.media.AudioDeviceInfo.TYPE_HDMI; 20 import static android.media.AudioDeviceInfo.TYPE_HDMI_ARC; 21 import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.media.AudioManager; 30 import android.media.AudioManager.AudioPlaybackCallback; 31 import android.media.AudioPlaybackConfiguration; 32 import android.os.Handler; 33 import android.os.PowerManager; 34 import android.util.Slog; 35 import android.util.SparseIntArray; 36 import android.view.Display; 37 import android.view.DisplayInfo; 38 39 import com.android.internal.annotations.GuardedBy; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.util.FrameworkStatsLog; 42 import com.android.server.display.utils.DebugUtils; 43 44 import java.util.List; 45 import java.util.function.BooleanSupplier; 46 47 48 /** 49 * Manages metrics logging for external display. 50 */ 51 public final class ExternalDisplayStatsService { 52 private static final String TAG = "ExternalDisplayStatsService"; 53 // To enable these logs, run: 54 // 'adb shell setprop persist.log.tag.ExternalDisplayStatsService DEBUG && adb reboot' 55 private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); 56 57 private static final int INVALID_DISPLAYS_COUNT = -1; 58 private static final int DISCONNECTED_STATE = 59 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__DISCONNECTED; 60 private static final int CONNECTED_STATE = 61 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__CONNECTED; 62 private static final int MIRRORING_STATE = 63 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__MIRRORING; 64 private static final int EXTENDED_STATE = 65 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__EXTENDED; 66 private static final int PRESENTATION_WHILE_MIRRORING = 67 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__PRESENTATION_WHILE_MIRRORING; 68 private static final int PRESENTATION_WHILE_EXTENDED = 69 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__PRESENTATION_WHILE_EXTENDED; 70 private static final int PRESENTATION_ENDED = 71 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__PRESENTATION_ENDED; 72 private static final int KEYGUARD = 73 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__KEYGUARD; 74 private static final int DISABLED_STATE = 75 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__DISABLED; 76 private static final int AUDIO_SINK_CHANGED = 77 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__AUDIO_SINK_CHANGED; 78 private static final int ERROR_HOTPLUG_CONNECTION = 79 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__ERROR_HOTPLUG_CONNECTION; 80 private static final int ERROR_DISPLAYPORT_LINK_FAILED = 81 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__ERROR_DISPLAYPORT_LINK_FAILED; 82 private static final int ERROR_CABLE_NOT_CAPABLE_DISPLAYPORT = 83 FrameworkStatsLog 84 .EXTERNAL_DISPLAY_STATE_CHANGED__STATE__ERROR_CABLE_NOT_CAPABLE_DISPLAYPORT; 85 86 private final Injector mInjector; 87 88 @GuardedBy("mExternalDisplayStates") 89 private final SparseIntArray mExternalDisplayStates = new SparseIntArray(); 90 91 /** 92 * Count of interactive external displays or INVALID_DISPLAYS_COUNT, modified only from Handler 93 */ 94 private int mInteractiveExternalDisplays; 95 96 /** 97 * Guards init deinit, modified only from Handler 98 */ 99 private boolean mIsInitialized; 100 101 /** 102 * Whether audio plays on external display, modified only from Handler 103 */ 104 private boolean mIsExternalDisplayUsedForAudio; 105 106 private final AudioPlaybackCallback mAudioPlaybackCallback = new AudioPlaybackCallback() { 107 private final Runnable mLogStateAfterAudioSinkEnabled = 108 () -> logStateAfterAudioSinkChanged(true); 109 private final Runnable mLogStateAfterAudioSinkDisabled = 110 () -> logStateAfterAudioSinkChanged(false); 111 112 @Override 113 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { 114 super.onPlaybackConfigChanged(configs); 115 scheduleAudioSinkChange(isExternalDisplayUsedForAudio(configs)); 116 } 117 118 private boolean isExternalDisplayUsedForAudio(List<AudioPlaybackConfiguration> configs) { 119 for (var config : configs) { 120 var info = config.getAudioDeviceInfo(); 121 if (config.isActive() && info != null 122 && info.isSink() 123 && (info.getType() == TYPE_HDMI 124 || info.getType() == TYPE_HDMI_ARC 125 || info.getType() == TYPE_USB_DEVICE)) { 126 if (DEBUG) { 127 Slog.d(TAG, "isExternalDisplayUsedForAudio:" 128 + " use " + info.getProductName() 129 + " isActive=" + config.isActive() 130 + " isSink=" + info.isSink() 131 + " type=" + info.getType()); 132 } 133 return true; 134 } 135 if (DEBUG) { 136 // info is null if the device is not available at the time of query. 137 if (info != null) { 138 Slog.d(TAG, "isExternalDisplayUsedForAudio:" 139 + " drop " + info.getProductName() 140 + " isActive=" + config.isActive() 141 + " isSink=" + info.isSink() 142 + " type=" + info.getType()); 143 } 144 } 145 } 146 return false; 147 } 148 149 private void scheduleAudioSinkChange(final boolean isAudioOnExternalDisplay) { 150 if (DEBUG) { 151 Slog.d(TAG, "scheduleAudioSinkChange:" 152 + " mIsExternalDisplayUsedForAudio=" 153 + mIsExternalDisplayUsedForAudio 154 + " isAudioOnExternalDisplay=" 155 + isAudioOnExternalDisplay); 156 } 157 mInjector.getHandler().removeCallbacks(mLogStateAfterAudioSinkEnabled); 158 mInjector.getHandler().removeCallbacks(mLogStateAfterAudioSinkDisabled); 159 final var callback = isAudioOnExternalDisplay ? mLogStateAfterAudioSinkEnabled 160 : mLogStateAfterAudioSinkDisabled; 161 if (isAudioOnExternalDisplay) { 162 mInjector.getHandler().postDelayed(callback, /*delayMillis=*/ 10000L); 163 } else { 164 mInjector.getHandler().post(callback); 165 } 166 } 167 }; 168 169 private final BroadcastReceiver mInteractivityReceiver = new BroadcastReceiver() { 170 /** 171 * Verifies that there is a change to the mInteractiveExternalDisplays and logs the change. 172 * Executed within a handler - no need to keep lock on mInteractiveExternalDisplays update. 173 */ 174 @Override 175 public void onReceive(Context context, Intent intent) { 176 int interactiveDisplaysCount = 0; 177 synchronized (mExternalDisplayStates) { 178 if (mExternalDisplayStates.size() == 0) { 179 return; 180 } 181 for (var i = 0; i < mExternalDisplayStates.size(); i++) { 182 if (mInjector.isInteractive(mExternalDisplayStates.keyAt(i))) { 183 interactiveDisplaysCount++; 184 } 185 } 186 } 187 188 // For the first time, mInteractiveExternalDisplays is INVALID_DISPLAYS_COUNT(-1) 189 // which is always not equal to interactiveDisplaysCount. 190 if (mInteractiveExternalDisplays == interactiveDisplaysCount) { 191 return; 192 } else if (0 == interactiveDisplaysCount) { 193 logExternalDisplayIdleStarted(); 194 } else if (INVALID_DISPLAYS_COUNT != mInteractiveExternalDisplays) { 195 // Log Only if mInteractiveExternalDisplays was previously initialised. 196 // Otherwise no need to log that idle has ended, as we assume it never started. 197 // This is because, currently for enabling external display, the display must be 198 // non-idle for the user to press the Mirror/Dismiss dialog button. 199 logExternalDisplayIdleEnded(); 200 } 201 mInteractiveExternalDisplays = interactiveDisplaysCount; 202 } 203 }; 204 ExternalDisplayStatsService(Context context, Handler handler, BooleanSupplier isExtendedDisplayEnabled)205 ExternalDisplayStatsService(Context context, Handler handler, 206 BooleanSupplier isExtendedDisplayEnabled) { 207 this(new Injector(context, handler, isExtendedDisplayEnabled)); 208 } 209 210 @VisibleForTesting ExternalDisplayStatsService(Injector injector)211 ExternalDisplayStatsService(Injector injector) { 212 mInjector = injector; 213 } 214 215 /** 216 * Write log on hotplug connection error 217 */ onHotplugConnectionError()218 public void onHotplugConnectionError() { 219 logExternalDisplayError(ERROR_HOTPLUG_CONNECTION); 220 } 221 222 /** 223 * Write log on DisplayPort link training failure 224 */ onDisplayPortLinkTrainingFailure()225 public void onDisplayPortLinkTrainingFailure() { 226 logExternalDisplayError(ERROR_DISPLAYPORT_LINK_FAILED); 227 } 228 229 /** 230 * Write log on USB cable not capable DisplayPort 231 */ onCableNotCapableDisplayPort()232 public void onCableNotCapableDisplayPort() { 233 logExternalDisplayError(ERROR_CABLE_NOT_CAPABLE_DISPLAYPORT); 234 } 235 onDisplayConnected(final LogicalDisplay display)236 void onDisplayConnected(final LogicalDisplay display) { 237 DisplayInfo displayInfo = display.getDisplayInfoLocked(); 238 if (displayInfo == null || displayInfo.type != Display.TYPE_EXTERNAL) { 239 return; 240 } 241 logStateConnected(display.getDisplayIdLocked()); 242 } 243 onDisplayAdded(int displayId)244 void onDisplayAdded(int displayId) { 245 if (mInjector.isExtendedDisplayEnabled()) { 246 logStateExtended(displayId); 247 } else { 248 logStateMirroring(displayId); 249 } 250 } 251 onDisplayDisabled(int displayId)252 void onDisplayDisabled(int displayId) { 253 logStateDisabled(displayId); 254 } 255 onDisplayDisconnected(int displayId)256 void onDisplayDisconnected(int displayId) { 257 logStateDisconnected(displayId); 258 } 259 260 /** 261 * Callback triggered upon presentation window gets added. 262 */ onPresentationWindowAdded(int displayId)263 void onPresentationWindowAdded(int displayId) { 264 logExternalDisplayPresentationStarted(displayId); 265 } 266 267 /** 268 * Callback triggered upon presentation window gets removed. 269 */ onPresentationWindowRemoved(int displayId)270 void onPresentationWindowRemoved(int displayId) { 271 logExternalDisplayPresentationEnded(displayId); 272 } 273 274 @VisibleForTesting isInteractiveExternalDisplays()275 boolean isInteractiveExternalDisplays() { 276 return mInteractiveExternalDisplays != 0; 277 } 278 279 @VisibleForTesting isExternalDisplayUsedForAudio()280 boolean isExternalDisplayUsedForAudio() { 281 return mIsExternalDisplayUsedForAudio; 282 } 283 logExternalDisplayError(int errorType)284 private void logExternalDisplayError(int errorType) { 285 final int countOfExternalDisplays; 286 synchronized (mExternalDisplayStates) { 287 countOfExternalDisplays = mExternalDisplayStates.size(); 288 } 289 290 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 291 errorType, countOfExternalDisplays, 292 mIsExternalDisplayUsedForAudio); 293 if (DEBUG) { 294 Slog.d(TAG, "logExternalDisplayError" 295 + " countOfExternalDisplays=" + countOfExternalDisplays 296 + " errorType=" + errorType 297 + " mIsExternalDisplayUsedForAudio=" 298 + mIsExternalDisplayUsedForAudio); 299 } 300 } 301 scheduleInit()302 private void scheduleInit() { 303 mInjector.getHandler().post(() -> { 304 if (mIsInitialized) { 305 Slog.e(TAG, "scheduleInit is called but already initialized"); 306 return; 307 } 308 mIsInitialized = true; 309 var filter = new IntentFilter(); 310 filter.addAction(Intent.ACTION_SCREEN_OFF); 311 filter.addAction(Intent.ACTION_SCREEN_ON); 312 mInteractiveExternalDisplays = INVALID_DISPLAYS_COUNT; 313 mIsExternalDisplayUsedForAudio = false; 314 mInjector.registerInteractivityReceiver(mInteractivityReceiver, filter); 315 mInjector.registerAudioPlaybackCallback(mAudioPlaybackCallback); 316 }); 317 } 318 scheduleDeinit()319 private void scheduleDeinit() { 320 mInjector.getHandler().post(() -> { 321 if (!mIsInitialized) { 322 Slog.e(TAG, "scheduleDeinit is called but never initialized"); 323 return; 324 } 325 mIsInitialized = false; 326 mInjector.unregisterInteractivityReceiver(mInteractivityReceiver); 327 mInjector.unregisterAudioPlaybackCallback(mAudioPlaybackCallback); 328 }); 329 } 330 logStateConnected(final int displayId)331 private void logStateConnected(final int displayId) { 332 final int countOfExternalDisplays, state; 333 synchronized (mExternalDisplayStates) { 334 state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 335 if (state != DISCONNECTED_STATE) { 336 return; 337 } 338 mExternalDisplayStates.put(displayId, CONNECTED_STATE); 339 countOfExternalDisplays = mExternalDisplayStates.size(); 340 } 341 342 if (countOfExternalDisplays == 1) { 343 scheduleInit(); 344 } 345 346 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 347 CONNECTED_STATE, countOfExternalDisplays, mIsExternalDisplayUsedForAudio); 348 if (DEBUG) { 349 Slog.d(TAG, "logStateConnected" 350 + " displayId=" + displayId 351 + " countOfExternalDisplays=" + countOfExternalDisplays 352 + " currentState=" + state 353 + " state=" + CONNECTED_STATE 354 + " mIsExternalDisplayUsedForAudio=" 355 + mIsExternalDisplayUsedForAudio); 356 } 357 } 358 logStateDisconnected(final int displayId)359 private void logStateDisconnected(final int displayId) { 360 final int countOfExternalDisplays, state; 361 synchronized (mExternalDisplayStates) { 362 state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 363 if (state == DISCONNECTED_STATE) { 364 if (DEBUG) { 365 Slog.d(TAG, "logStateDisconnected" 366 + " displayId=" + displayId 367 + " already disconnected"); 368 } 369 return; 370 } 371 countOfExternalDisplays = mExternalDisplayStates.size(); 372 mExternalDisplayStates.delete(displayId); 373 } 374 375 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 376 DISCONNECTED_STATE, countOfExternalDisplays, 377 mIsExternalDisplayUsedForAudio); 378 379 if (DEBUG) { 380 Slog.d(TAG, "logStateDisconnected" 381 + " displayId=" + displayId 382 + " countOfExternalDisplays=" + countOfExternalDisplays 383 + " currentState=" + state 384 + " state=" + DISCONNECTED_STATE 385 + " mIsExternalDisplayUsedForAudio=" 386 + mIsExternalDisplayUsedForAudio); 387 } 388 389 if (countOfExternalDisplays == 1) { 390 scheduleDeinit(); 391 } 392 } 393 logStateMirroring(final int displayId)394 private void logStateMirroring(final int displayId) { 395 synchronized (mExternalDisplayStates) { 396 final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 397 if (state == DISCONNECTED_STATE || state == MIRRORING_STATE) { 398 return; 399 } 400 for (var i = 0; i < mExternalDisplayStates.size(); i++) { 401 if (mExternalDisplayStates.keyAt(i) != displayId) { 402 continue; 403 } 404 mExternalDisplayStates.put(displayId, MIRRORING_STATE); 405 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 406 MIRRORING_STATE, i + 1, mIsExternalDisplayUsedForAudio); 407 if (DEBUG) { 408 Slog.d(TAG, "logStateMirroring" 409 + " displayId=" + displayId 410 + " countOfExternalDisplays=" + (i + 1) 411 + " currentState=" + state 412 + " state=" + MIRRORING_STATE 413 + " mIsExternalDisplayUsedForAudio=" 414 + mIsExternalDisplayUsedForAudio); 415 } 416 } 417 } 418 } 419 logStateExtended(final int displayId)420 private void logStateExtended(final int displayId) { 421 synchronized (mExternalDisplayStates) { 422 final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 423 if (state == DISCONNECTED_STATE || state == EXTENDED_STATE) { 424 return; 425 } 426 for (var i = 0; i < mExternalDisplayStates.size(); i++) { 427 if (mExternalDisplayStates.keyAt(i) != displayId) { 428 continue; 429 } 430 mExternalDisplayStates.put(displayId, EXTENDED_STATE); 431 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 432 EXTENDED_STATE, i + 1, mIsExternalDisplayUsedForAudio); 433 if (DEBUG) { 434 Slog.d(TAG, "logStateExtended" 435 + " displayId=" + displayId 436 + " countOfExternalDisplays=" + (i + 1) 437 + " currentState=" + state 438 + " state=" + EXTENDED_STATE 439 + " mIsExternalDisplayUsedForAudio=" 440 + mIsExternalDisplayUsedForAudio); 441 } 442 } 443 } 444 } 445 logStateDisabled(final int displayId)446 private void logStateDisabled(final int displayId) { 447 synchronized (mExternalDisplayStates) { 448 final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 449 if (state == DISCONNECTED_STATE || state == DISABLED_STATE) { 450 return; 451 } 452 for (var i = 0; i < mExternalDisplayStates.size(); i++) { 453 if (mExternalDisplayStates.keyAt(i) != displayId) { 454 continue; 455 } 456 mExternalDisplayStates.put(displayId, DISABLED_STATE); 457 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 458 DISABLED_STATE, i + 1, mIsExternalDisplayUsedForAudio); 459 if (DEBUG) { 460 Slog.d(TAG, "logStateDisabled" 461 + " displayId=" + displayId 462 + " countOfExternalDisplays=" + (i + 1) 463 + " currentState=" + state 464 + " state=" + DISABLED_STATE 465 + " mIsExternalDisplayUsedForAudio=" 466 + mIsExternalDisplayUsedForAudio); 467 } 468 } 469 } 470 } 471 logExternalDisplayPresentationStarted(int displayId)472 private void logExternalDisplayPresentationStarted(int displayId) { 473 final int countOfExternalDisplays, state; 474 synchronized (mExternalDisplayStates) { 475 state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 476 if (state == DISCONNECTED_STATE) { 477 return; 478 } 479 countOfExternalDisplays = mExternalDisplayStates.size(); 480 } 481 482 final var newState = mInjector.isExtendedDisplayEnabled() ? PRESENTATION_WHILE_EXTENDED 483 : PRESENTATION_WHILE_MIRRORING; 484 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 485 newState, countOfExternalDisplays, 486 mIsExternalDisplayUsedForAudio); 487 if (DEBUG) { 488 Slog.d(TAG, "logExternalDisplayPresentationStarted" 489 + " state=" + state 490 + " newState=" + newState 491 + " mIsExternalDisplayUsedForAudio=" 492 + mIsExternalDisplayUsedForAudio); 493 } 494 } 495 logExternalDisplayPresentationEnded(int displayId)496 private void logExternalDisplayPresentationEnded(int displayId) { 497 final int countOfExternalDisplays, state; 498 synchronized (mExternalDisplayStates) { 499 state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 500 if (state == DISCONNECTED_STATE) { 501 return; 502 } 503 countOfExternalDisplays = mExternalDisplayStates.size(); 504 } 505 506 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 507 PRESENTATION_ENDED, countOfExternalDisplays, 508 mIsExternalDisplayUsedForAudio); 509 if (DEBUG) { 510 Slog.d(TAG, "logExternalDisplayPresentationEnded" 511 + " state=" + state 512 + " countOfExternalDisplays=" + countOfExternalDisplays 513 + " mIsExternalDisplayUsedForAudio=" 514 + mIsExternalDisplayUsedForAudio); 515 } 516 } 517 logExternalDisplayIdleStarted()518 private void logExternalDisplayIdleStarted() { 519 synchronized (mExternalDisplayStates) { 520 for (var i = 0; i < mExternalDisplayStates.size(); i++) { 521 final int displayId = mExternalDisplayStates.keyAt(i); 522 final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 523 // Don't try to stop "connected" session by keyguard event. 524 // There is no purpose to measure how long keyguard is shown while external 525 // display is connected but not used for mirroring or extended display. 526 // Therefore there no need to log this event. 527 if (state != DISCONNECTED_STATE && state != CONNECTED_STATE) { 528 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 529 KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio); 530 if (DEBUG) { 531 Slog.d(TAG, "logExternalDisplayIdleStarted" 532 + " displayId=" + displayId 533 + " currentState=" + state 534 + " countOfExternalDisplays=" + (i + 1) 535 + " state=" + KEYGUARD 536 + " mIsExternalDisplayUsedForAudio=" 537 + mIsExternalDisplayUsedForAudio); 538 } 539 } 540 } 541 } 542 } 543 logExternalDisplayIdleEnded()544 private void logExternalDisplayIdleEnded() { 545 synchronized (mExternalDisplayStates) { 546 for (var i = 0; i < mExternalDisplayStates.size(); i++) { 547 final int displayId = mExternalDisplayStates.keyAt(i); 548 final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 549 // No need to restart "connected" session after keyguard is stopped. 550 // This is because the connection is continuous even if keyguard is shown. 551 // In case in the future keyguard needs to be measured also while display 552 // is not used, then a 'keyguard finished' event needs to be emitted in this case. 553 if (state == DISCONNECTED_STATE || state == CONNECTED_STATE) { 554 return; 555 } 556 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 557 state, i + 1, mIsExternalDisplayUsedForAudio); 558 if (DEBUG) { 559 Slog.d(TAG, "logExternalDisplayIdleEnded" 560 + " displayId=" + displayId 561 + " state=" + state 562 + " countOfExternalDisplays=" + (i + 1) 563 + " mIsExternalDisplayUsedForAudio=" 564 + mIsExternalDisplayUsedForAudio); 565 } 566 } 567 } 568 } 569 570 /** 571 * Executed within Handler 572 */ logStateAfterAudioSinkChanged(boolean enabled)573 private void logStateAfterAudioSinkChanged(boolean enabled) { 574 if (mIsExternalDisplayUsedForAudio == enabled) { 575 return; 576 } 577 mIsExternalDisplayUsedForAudio = enabled; 578 int countOfExternalDisplays; 579 synchronized (mExternalDisplayStates) { 580 countOfExternalDisplays = mExternalDisplayStates.size(); 581 } 582 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 583 AUDIO_SINK_CHANGED, countOfExternalDisplays, 584 mIsExternalDisplayUsedForAudio); 585 if (DEBUG) { 586 Slog.d(TAG, "logStateAfterAudioSinkChanged" 587 + " countOfExternalDisplays)=" 588 + countOfExternalDisplays 589 + " mIsExternalDisplayUsedForAudio=" 590 + mIsExternalDisplayUsedForAudio); 591 } 592 } 593 594 /** 595 * Implements necessary functionality for {@link ExternalDisplayStatsService} 596 */ 597 static class Injector { 598 @NonNull 599 private final Context mContext; 600 @NonNull 601 private final Handler mHandler; 602 private final BooleanSupplier mIsExtendedDisplayEnabled; 603 @Nullable 604 private AudioManager mAudioManager; 605 @Nullable 606 private PowerManager mPowerManager; 607 Injector(@onNull Context context, @NonNull Handler handler, BooleanSupplier isExtendedDisplayEnabled)608 Injector(@NonNull Context context, @NonNull Handler handler, 609 BooleanSupplier isExtendedDisplayEnabled) { 610 mContext = context; 611 mHandler = handler; 612 mIsExtendedDisplayEnabled = isExtendedDisplayEnabled; 613 } 614 isExtendedDisplayEnabled()615 boolean isExtendedDisplayEnabled() { 616 return mIsExtendedDisplayEnabled.getAsBoolean(); 617 } 618 registerInteractivityReceiver(BroadcastReceiver interactivityReceiver, IntentFilter filter)619 void registerInteractivityReceiver(BroadcastReceiver interactivityReceiver, 620 IntentFilter filter) { 621 mContext.registerReceiver(interactivityReceiver, filter, /*broadcastPermission=*/ null, 622 mHandler, Context.RECEIVER_NOT_EXPORTED); 623 } 624 unregisterInteractivityReceiver(BroadcastReceiver interactivityReceiver)625 void unregisterInteractivityReceiver(BroadcastReceiver interactivityReceiver) { 626 mContext.unregisterReceiver(interactivityReceiver); 627 } 628 registerAudioPlaybackCallback( AudioPlaybackCallback audioPlaybackCallback)629 void registerAudioPlaybackCallback( 630 AudioPlaybackCallback audioPlaybackCallback) { 631 if (mAudioManager == null) { 632 mAudioManager = mContext.getSystemService(AudioManager.class); 633 } 634 if (mAudioManager != null) { 635 mAudioManager.registerAudioPlaybackCallback(audioPlaybackCallback, mHandler); 636 } 637 } 638 unregisterAudioPlaybackCallback( AudioPlaybackCallback audioPlaybackCallback)639 void unregisterAudioPlaybackCallback( 640 AudioPlaybackCallback audioPlaybackCallback) { 641 if (mAudioManager == null) { 642 mAudioManager = mContext.getSystemService(AudioManager.class); 643 } 644 if (mAudioManager != null) { 645 mAudioManager.unregisterAudioPlaybackCallback(audioPlaybackCallback); 646 } 647 } 648 isInteractive(int displayId)649 boolean isInteractive(int displayId) { 650 if (mPowerManager == null) { 651 mPowerManager = mContext.getSystemService(PowerManager.class); 652 } 653 // By default it is interactive, unless power manager is initialised and says it is not. 654 return mPowerManager == null || mPowerManager.isInteractive(displayId); 655 } 656 657 @NonNull getHandler()658 Handler getHandler() { 659 return mHandler; 660 } 661 writeLog(int externalDisplayStateChanged, int event, int numberOfDisplays, boolean isExternalDisplayUsedForAudio)662 void writeLog(int externalDisplayStateChanged, int event, int numberOfDisplays, 663 boolean isExternalDisplayUsedForAudio) { 664 FrameworkStatsLog.write(externalDisplayStateChanged, event, numberOfDisplays, 665 isExternalDisplayUsedForAudio); 666 } 667 } 668 } 669