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 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.media.AudioManager; 31 import android.media.AudioManager.AudioPlaybackCallback; 32 import android.media.AudioPlaybackConfiguration; 33 import android.os.Handler; 34 import android.os.PowerManager; 35 import android.provider.Settings; 36 import android.util.Slog; 37 import android.util.SparseIntArray; 38 import android.view.Display; 39 import android.view.DisplayInfo; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.util.FrameworkStatsLog; 44 import com.android.server.display.utils.DebugUtils; 45 46 import java.util.List; 47 48 49 /** 50 * Manages metrics logging for external display. 51 */ 52 public final class ExternalDisplayStatsService { 53 private static final String TAG = "ExternalDisplayStatsService"; 54 // To enable these logs, run: 55 // 'adb shell setprop persist.log.tag.ExternalDisplayStatsService DEBUG && adb reboot' 56 private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); 57 58 private static final int INVALID_DISPLAYS_COUNT = -1; 59 private static final int DISCONNECTED_STATE = 60 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__DISCONNECTED; 61 private static final int CONNECTED_STATE = 62 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__CONNECTED; 63 private static final int MIRRORING_STATE = 64 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__MIRRORING; 65 private static final int EXTENDED_STATE = 66 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__EXTENDED; 67 private static final int PRESENTATION_WHILE_MIRRORING = 68 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__PRESENTATION_WHILE_MIRRORING; 69 private static final int PRESENTATION_WHILE_EXTENDED = 70 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__PRESENTATION_WHILE_EXTENDED; 71 private static final int PRESENTATION_ENDED = 72 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__PRESENTATION_ENDED; 73 private static final int KEYGUARD = 74 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__KEYGUARD; 75 private static final int DISABLED_STATE = 76 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__DISABLED; 77 private static final int AUDIO_SINK_CHANGED = 78 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__AUDIO_SINK_CHANGED; 79 private static final int ERROR_HOTPLUG_CONNECTION = 80 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__ERROR_HOTPLUG_CONNECTION; 81 private static final int ERROR_DISPLAYPORT_LINK_FAILED = 82 FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__ERROR_DISPLAYPORT_LINK_FAILED; 83 private static final int ERROR_CABLE_NOT_CAPABLE_DISPLAYPORT = 84 FrameworkStatsLog 85 .EXTERNAL_DISPLAY_STATE_CHANGED__STATE__ERROR_CABLE_NOT_CAPABLE_DISPLAYPORT; 86 87 private final Injector mInjector; 88 89 @GuardedBy("mExternalDisplayStates") 90 private final SparseIntArray mExternalDisplayStates = new SparseIntArray(); 91 92 /** 93 * Count of interactive external displays or INVALID_DISPLAYS_COUNT, modified only from Handler 94 */ 95 private int mInteractiveExternalDisplays; 96 97 /** 98 * Guards init deinit, modified only from Handler 99 */ 100 private boolean mIsInitialized; 101 102 /** 103 * Whether audio plays on external display, modified only from Handler 104 */ 105 private boolean mIsExternalDisplayUsedForAudio; 106 107 private final AudioPlaybackCallback mAudioPlaybackCallback = new AudioPlaybackCallback() { 108 private final Runnable mLogStateAfterAudioSinkEnabled = 109 () -> logStateAfterAudioSinkChanged(true); 110 private final Runnable mLogStateAfterAudioSinkDisabled = 111 () -> logStateAfterAudioSinkChanged(false); 112 113 @Override 114 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { 115 super.onPlaybackConfigChanged(configs); 116 scheduleAudioSinkChange(isExternalDisplayUsedForAudio(configs)); 117 } 118 119 private boolean isExternalDisplayUsedForAudio(List<AudioPlaybackConfiguration> configs) { 120 for (var config : configs) { 121 var info = config.getAudioDeviceInfo(); 122 if (config.isActive() && info != null 123 && info.isSink() 124 && (info.getType() == TYPE_HDMI 125 || info.getType() == TYPE_HDMI_ARC 126 || info.getType() == TYPE_USB_DEVICE)) { 127 if (DEBUG) { 128 Slog.d(TAG, "isExternalDisplayUsedForAudio:" 129 + " use " + info.getProductName() 130 + " isActive=" + config.isActive() 131 + " isSink=" + info.isSink() 132 + " type=" + info.getType()); 133 } 134 return true; 135 } 136 if (DEBUG) { 137 // info is null if the device is not available at the time of query. 138 if (info != null) { 139 Slog.d(TAG, "isExternalDisplayUsedForAudio:" 140 + " drop " + info.getProductName() 141 + " isActive=" + config.isActive() 142 + " isSink=" + info.isSink() 143 + " type=" + info.getType()); 144 } 145 } 146 } 147 return false; 148 } 149 150 private void scheduleAudioSinkChange(final boolean isAudioOnExternalDisplay) { 151 if (DEBUG) { 152 Slog.d(TAG, "scheduleAudioSinkChange:" 153 + " mIsExternalDisplayUsedForAudio=" 154 + mIsExternalDisplayUsedForAudio 155 + " isAudioOnExternalDisplay=" 156 + isAudioOnExternalDisplay); 157 } 158 mInjector.getHandler().removeCallbacks(mLogStateAfterAudioSinkEnabled); 159 mInjector.getHandler().removeCallbacks(mLogStateAfterAudioSinkDisabled); 160 final var callback = isAudioOnExternalDisplay ? mLogStateAfterAudioSinkEnabled 161 : mLogStateAfterAudioSinkDisabled; 162 if (isAudioOnExternalDisplay) { 163 mInjector.getHandler().postDelayed(callback, /*delayMillis=*/ 10000L); 164 } else { 165 mInjector.getHandler().post(callback); 166 } 167 } 168 }; 169 170 private final BroadcastReceiver mInteractivityReceiver = new BroadcastReceiver() { 171 /** 172 * Verifies that there is a change to the mInteractiveExternalDisplays and logs the change. 173 * Executed within a handler - no need to keep lock on mInteractiveExternalDisplays update. 174 */ 175 @Override 176 public void onReceive(Context context, Intent intent) { 177 int interactiveDisplaysCount = 0; 178 synchronized (mExternalDisplayStates) { 179 if (mExternalDisplayStates.size() == 0) { 180 return; 181 } 182 for (var i = 0; i < mExternalDisplayStates.size(); i++) { 183 if (mInjector.isInteractive(mExternalDisplayStates.keyAt(i))) { 184 interactiveDisplaysCount++; 185 } 186 } 187 } 188 189 // For the first time, mInteractiveExternalDisplays is INVALID_DISPLAYS_COUNT(-1) 190 // which is always not equal to interactiveDisplaysCount. 191 if (mInteractiveExternalDisplays == interactiveDisplaysCount) { 192 return; 193 } else if (0 == interactiveDisplaysCount) { 194 logExternalDisplayIdleStarted(); 195 } else if (INVALID_DISPLAYS_COUNT != mInteractiveExternalDisplays) { 196 // Log Only if mInteractiveExternalDisplays was previously initialised. 197 // Otherwise no need to log that idle has ended, as we assume it never started. 198 // This is because, currently for enabling external display, the display must be 199 // non-idle for the user to press the Mirror/Dismiss dialog button. 200 logExternalDisplayIdleEnded(); 201 } 202 mInteractiveExternalDisplays = interactiveDisplaysCount; 203 } 204 }; 205 ExternalDisplayStatsService(Context context, Handler handler)206 ExternalDisplayStatsService(Context context, Handler handler) { 207 this(new Injector(context, handler)); 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 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 522 KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio); 523 if (DEBUG) { 524 final int displayId = mExternalDisplayStates.keyAt(i); 525 final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 526 Slog.d(TAG, "logExternalDisplayIdleStarted" 527 + " displayId=" + displayId 528 + " currentState=" + state 529 + " countOfExternalDisplays=" + (i + 1) 530 + " state=" + KEYGUARD 531 + " mIsExternalDisplayUsedForAudio=" 532 + mIsExternalDisplayUsedForAudio); 533 } 534 } 535 } 536 } 537 logExternalDisplayIdleEnded()538 private void logExternalDisplayIdleEnded() { 539 synchronized (mExternalDisplayStates) { 540 for (var i = 0; i < mExternalDisplayStates.size(); i++) { 541 final int displayId = mExternalDisplayStates.keyAt(i); 542 final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE); 543 if (state == DISCONNECTED_STATE) { 544 return; 545 } 546 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 547 state, i + 1, mIsExternalDisplayUsedForAudio); 548 if (DEBUG) { 549 Slog.d(TAG, "logExternalDisplayIdleEnded" 550 + " displayId=" + displayId 551 + " state=" + state 552 + " countOfExternalDisplays=" + (i + 1) 553 + " mIsExternalDisplayUsedForAudio=" 554 + mIsExternalDisplayUsedForAudio); 555 } 556 } 557 } 558 } 559 560 /** 561 * Executed within Handler 562 */ logStateAfterAudioSinkChanged(boolean enabled)563 private void logStateAfterAudioSinkChanged(boolean enabled) { 564 if (mIsExternalDisplayUsedForAudio == enabled) { 565 return; 566 } 567 mIsExternalDisplayUsedForAudio = enabled; 568 int countOfExternalDisplays; 569 synchronized (mExternalDisplayStates) { 570 countOfExternalDisplays = mExternalDisplayStates.size(); 571 } 572 mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED, 573 AUDIO_SINK_CHANGED, countOfExternalDisplays, 574 mIsExternalDisplayUsedForAudio); 575 if (DEBUG) { 576 Slog.d(TAG, "logStateAfterAudioSinkChanged" 577 + " countOfExternalDisplays)=" 578 + countOfExternalDisplays 579 + " mIsExternalDisplayUsedForAudio=" 580 + mIsExternalDisplayUsedForAudio); 581 } 582 } 583 584 /** 585 * Implements necessary functionality for {@link ExternalDisplayStatsService} 586 */ 587 static class Injector { 588 @NonNull 589 private final Context mContext; 590 @NonNull 591 private final Handler mHandler; 592 @Nullable 593 private AudioManager mAudioManager; 594 @Nullable 595 private PowerManager mPowerManager; 596 Injector(@onNull Context context, @NonNull Handler handler)597 Injector(@NonNull Context context, @NonNull Handler handler) { 598 mContext = context; 599 mHandler = handler; 600 } 601 isExtendedDisplayEnabled()602 boolean isExtendedDisplayEnabled() { 603 try { 604 return 0 != Settings.Global.getInt( 605 mContext.getContentResolver(), 606 DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0); 607 } catch (Throwable e) { 608 // Some services might not be initialised yet to be able to call getInt 609 return false; 610 } 611 } 612 registerInteractivityReceiver(BroadcastReceiver interactivityReceiver, IntentFilter filter)613 void registerInteractivityReceiver(BroadcastReceiver interactivityReceiver, 614 IntentFilter filter) { 615 mContext.registerReceiver(interactivityReceiver, filter, /*broadcastPermission=*/ null, 616 mHandler, Context.RECEIVER_NOT_EXPORTED); 617 } 618 unregisterInteractivityReceiver(BroadcastReceiver interactivityReceiver)619 void unregisterInteractivityReceiver(BroadcastReceiver interactivityReceiver) { 620 mContext.unregisterReceiver(interactivityReceiver); 621 } 622 registerAudioPlaybackCallback( AudioPlaybackCallback audioPlaybackCallback)623 void registerAudioPlaybackCallback( 624 AudioPlaybackCallback audioPlaybackCallback) { 625 if (mAudioManager == null) { 626 mAudioManager = mContext.getSystemService(AudioManager.class); 627 } 628 if (mAudioManager != null) { 629 mAudioManager.registerAudioPlaybackCallback(audioPlaybackCallback, mHandler); 630 } 631 } 632 unregisterAudioPlaybackCallback( AudioPlaybackCallback audioPlaybackCallback)633 void unregisterAudioPlaybackCallback( 634 AudioPlaybackCallback audioPlaybackCallback) { 635 if (mAudioManager == null) { 636 mAudioManager = mContext.getSystemService(AudioManager.class); 637 } 638 if (mAudioManager != null) { 639 mAudioManager.unregisterAudioPlaybackCallback(audioPlaybackCallback); 640 } 641 } 642 isInteractive(int displayId)643 boolean isInteractive(int displayId) { 644 if (mPowerManager == null) { 645 mPowerManager = mContext.getSystemService(PowerManager.class); 646 } 647 // By default it is interactive, unless power manager is initialised and says it is not. 648 return mPowerManager == null || mPowerManager.isInteractive(displayId); 649 } 650 651 @NonNull getHandler()652 Handler getHandler() { 653 return mHandler; 654 } 655 writeLog(int externalDisplayStateChanged, int event, int numberOfDisplays, boolean isExternalDisplayUsedForAudio)656 void writeLog(int externalDisplayStateChanged, int event, int numberOfDisplays, 657 boolean isExternalDisplayUsedForAudio) { 658 FrameworkStatsLog.write(externalDisplayStateChanged, event, numberOfDisplays, 659 isExternalDisplayUsedForAudio); 660 } 661 } 662 } 663