1 /* 2 * Copyright (C) 2014 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.hdmi; 18 19 import android.annotation.CallSuper; 20 import android.content.ActivityNotFoundException; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.hardware.hdmi.HdmiControlManager; 25 import android.hardware.hdmi.HdmiDeviceInfo; 26 import android.hardware.hdmi.IHdmiControlCallback; 27 import android.hardware.tv.cec.V1_0.SendMessageResult; 28 import android.os.Binder; 29 import android.os.Handler; 30 import android.os.PowerManager; 31 import android.os.PowerManager.WakeLock; 32 import android.os.SystemProperties; 33 import android.sysprop.HdmiProperties; 34 import android.util.Slog; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.internal.app.LocalePicker; 38 import com.android.internal.app.LocalePicker.LocaleInfo; 39 import com.android.internal.util.IndentingPrintWriter; 40 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 41 import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 42 43 import java.io.UnsupportedEncodingException; 44 import java.util.List; 45 import java.util.Locale; 46 47 /** 48 * Represent a logical device of type Playback residing in Android system. 49 */ 50 public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { 51 private static final String TAG = "HdmiCecLocalDevicePlayback"; 52 53 // How long to wait after hotplug out before possibly going to Standby. 54 @VisibleForTesting 55 static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000; 56 57 // Used to keep the device awake while it is the active source. For devices that 58 // cannot wake up via CEC commands, this address the inconvenience of having to 59 // turn them on. True by default, and can be disabled (i.e. device can go to sleep 60 // in active device status) by explicitly setting the system property 61 // persist.sys.hdmi.keep_awake to false. 62 // Lazily initialized - should call getWakeLock() to get the instance. 63 private ActiveWakeLock mWakeLock; 64 65 // Handler for queueing a delayed Standby runnable after hotplug out. 66 private Handler mDelayedStandbyHandler; 67 68 // Determines what action should be taken upon receiving Routing Control messages. 69 @VisibleForTesting 70 protected HdmiProperties.playback_device_action_on_routing_control_values 71 mPlaybackDeviceActionOnRoutingControl = HdmiProperties 72 .playback_device_action_on_routing_control() 73 .orElse(HdmiProperties.playback_device_action_on_routing_control_values.NONE); 74 HdmiCecLocalDevicePlayback(HdmiControlService service)75 HdmiCecLocalDevicePlayback(HdmiControlService service) { 76 super(service, HdmiDeviceInfo.DEVICE_PLAYBACK); 77 78 mDelayedStandbyHandler = new Handler(service.getServiceLooper()); 79 mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); 80 } 81 82 @Override 83 @ServiceThreadOnly onAddressAllocated(int logicalAddress, int reason)84 protected void onAddressAllocated(int logicalAddress, int reason) { 85 assertRunOnServiceThread(); 86 if (reason == mService.INITIATED_BY_ENABLE_CEC) { 87 mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(), 88 getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST, 89 "HdmiCecLocalDevicePlayback#onAddressAllocated()"); 90 } 91 mService.sendCecCommand( 92 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 93 getDeviceInfo().getLogicalAddress(), 94 mService.getPhysicalAddress(), 95 mDeviceType)); 96 mService.sendCecCommand( 97 HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 98 getDeviceInfo().getLogicalAddress(), mService.getVendorId())); 99 // Actively send out an OSD name to the TV to update the TV panel in case the TV 100 // does not query the OSD name on time. This is not a required behavior by the spec. 101 // It is used for some TVs that need the OSD name update but don't query it themselves. 102 buildAndSendSetOsdName(Constants.ADDR_TV); 103 if (mService.audioSystem() == null) { 104 // If current device is not a functional audio system device, 105 // send message to potential audio system device in the system to get the system 106 // audio mode status. If no response, set to false. 107 mService.sendCecCommand( 108 HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus( 109 getDeviceInfo().getLogicalAddress(), Constants.ADDR_AUDIO_SYSTEM), 110 new SendMessageCallback() { 111 @Override 112 public void onSendCompleted(int error) { 113 if (error != SendMessageResult.SUCCESS) { 114 HdmiLogger.debug( 115 "AVR did not respond to <Give System Audio Mode Status>"); 116 mService.setSystemAudioActivated(false); 117 } 118 } 119 }); 120 } 121 launchDeviceDiscovery(); 122 startQueuedActions(); 123 } 124 125 @ServiceThreadOnly launchDeviceDiscovery()126 private void launchDeviceDiscovery() { 127 assertRunOnServiceThread(); 128 clearDeviceInfoList(); 129 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 130 new DeviceDiscoveryAction.DeviceDiscoveryCallback() { 131 @Override 132 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { 133 for (HdmiDeviceInfo info : deviceInfos) { 134 mService.getHdmiCecNetwork().addCecDevice(info); 135 } 136 137 // Since we removed all devices when it starts and device discovery action 138 // does not poll local devices, we should put device info of local device 139 // manually here. 140 for (HdmiCecLocalDevice device : mService.getAllCecLocalDevices()) { 141 mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo()); 142 } 143 144 List<HotplugDetectionAction> hotplugActions = 145 getActions(HotplugDetectionAction.class); 146 if (hotplugActions.isEmpty()) { 147 addAndStartAction( 148 new HotplugDetectionAction(HdmiCecLocalDevicePlayback.this)); 149 } 150 } 151 }); 152 addAndStartAction(action); 153 } 154 155 @Override 156 @ServiceThreadOnly getPreferredAddress()157 protected int getPreferredAddress() { 158 assertRunOnServiceThread(); 159 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK, 160 Constants.ADDR_UNREGISTERED); 161 } 162 163 @Override 164 @ServiceThreadOnly setPreferredAddress(int addr)165 protected void setPreferredAddress(int addr) { 166 assertRunOnServiceThread(); 167 mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK, 168 String.valueOf(addr)); 169 } 170 171 /** 172 * Performs the action 'device select' or 'one touch play' initiated by a Playback device. 173 * 174 * @param id id of HDMI device to select 175 * @param callback callback object to report the result with 176 */ 177 @ServiceThreadOnly deviceSelect(int id, IHdmiControlCallback callback)178 void deviceSelect(int id, IHdmiControlCallback callback) { 179 assertRunOnServiceThread(); 180 if (id == getDeviceInfo().getId()) { 181 mService.oneTouchPlay(callback); 182 return; 183 } 184 HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id); 185 if (targetDevice == null) { 186 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 187 return; 188 } 189 int targetAddress = targetDevice.getLogicalAddress(); 190 if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) { 191 return; 192 } 193 if (!mService.isCecControlEnabled()) { 194 setActiveSource(targetDevice, "HdmiCecLocalDevicePlayback#deviceSelect()"); 195 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 196 return; 197 } 198 removeAction(DeviceSelectActionFromPlayback.class); 199 addAndStartAction(new DeviceSelectActionFromPlayback(this, targetDevice, callback)); 200 } 201 202 @Override 203 @ServiceThreadOnly onHotplug(int portId, boolean connected)204 void onHotplug(int portId, boolean connected) { 205 assertRunOnServiceThread(); 206 mCecMessageCache.flushAll(); 207 208 if (connected) { 209 mDelayedStandbyHandler.removeCallbacksAndMessages(null); 210 } else { 211 // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3 212 getWakeLock().release(); 213 mService.getHdmiCecNetwork().removeDevicesConnectedToPort(portId); 214 215 mDelayedStandbyHandler.removeCallbacksAndMessages(null); 216 mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(), 217 STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); 218 } 219 } 220 221 /** 222 * Runnable for going to Standby if the device has been inactive for a certain amount of time. 223 * Posts a new instance of itself as a delayed message if the device was active. 224 */ 225 private class DelayedStandbyRunnable implements Runnable { 226 @Override run()227 public void run() { 228 if (mService.getPowerManagerInternal().wasDeviceIdleFor( 229 STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS)) { 230 mService.standby(); 231 } else { 232 mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(), 233 STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); 234 } 235 } 236 } 237 238 @Override 239 @ServiceThreadOnly onStandby(boolean initiatedByCec, int standbyAction)240 protected void onStandby(boolean initiatedByCec, int standbyAction) { 241 assertRunOnServiceThread(); 242 if (!mService.isCecControlEnabled()) { 243 return; 244 } 245 boolean wasActiveSource = isActiveSource(); 246 // Invalidate the internal active source record when going to standby 247 mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS, 248 "HdmiCecLocalDevicePlayback#onStandby()"); 249 if (!wasActiveSource) { 250 return; 251 } 252 if (initiatedByCec) { 253 mService.sendCecCommand( 254 HdmiCecMessageBuilder.buildInactiveSource( 255 getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress())); 256 return; 257 } 258 switch (standbyAction) { 259 case HdmiControlService.STANDBY_SCREEN_OFF: 260 // Get latest setting value 261 @HdmiControlManager.PowerControlMode 262 String powerControlMode = mService.getHdmiCecConfig().getStringValue( 263 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE); 264 switch (powerControlMode) { 265 case HdmiControlManager.POWER_CONTROL_MODE_TV: 266 mService.sendCecCommand( 267 HdmiCecMessageBuilder.buildStandby( 268 getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV)); 269 break; 270 case HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM: 271 mService.sendCecCommand( 272 HdmiCecMessageBuilder.buildStandby( 273 getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV)); 274 mService.sendCecCommand( 275 HdmiCecMessageBuilder.buildStandby( 276 getDeviceInfo().getLogicalAddress(), 277 Constants.ADDR_AUDIO_SYSTEM)); 278 break; 279 case HdmiControlManager.POWER_CONTROL_MODE_BROADCAST: 280 mService.sendCecCommand( 281 HdmiCecMessageBuilder.buildStandby( 282 getDeviceInfo().getLogicalAddress(), 283 Constants.ADDR_BROADCAST)); 284 break; 285 case HdmiControlManager.POWER_CONTROL_MODE_NONE: 286 mService.sendCecCommand( 287 HdmiCecMessageBuilder.buildInactiveSource( 288 getDeviceInfo().getLogicalAddress(), 289 mService.getPhysicalAddress())); 290 break; 291 } 292 break; 293 case HdmiControlService.STANDBY_SHUTDOWN: 294 // ACTION_SHUTDOWN is taken as a signal to power off all the devices. 295 mService.sendCecCommand( 296 HdmiCecMessageBuilder.buildStandby( 297 getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST)); 298 break; 299 } 300 } 301 302 @Override 303 @ServiceThreadOnly onInitializeCecComplete(int initiatedBy)304 protected void onInitializeCecComplete(int initiatedBy) { 305 if (initiatedBy != HdmiControlService.INITIATED_BY_SCREEN_ON) { 306 return; 307 } 308 @HdmiControlManager.PowerControlMode 309 String powerControlMode = mService.getHdmiCecConfig().getStringValue( 310 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE); 311 if (powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_NONE)) { 312 return; 313 } 314 oneTouchPlay(new IHdmiControlCallback.Stub() { 315 @Override 316 public void onComplete(int result) { 317 if (result != HdmiControlManager.RESULT_SUCCESS) { 318 Slog.w(TAG, "Failed to complete One Touch Play. result=" + result); 319 } 320 } 321 }); 322 } 323 324 @Override 325 @CallSuper 326 @ServiceThreadOnly 327 @VisibleForTesting setActiveSource(int logicalAddress, int physicalAddress, String caller)328 protected void setActiveSource(int logicalAddress, int physicalAddress, String caller) { 329 assertRunOnServiceThread(); 330 super.setActiveSource(logicalAddress, physicalAddress, caller); 331 if (isActiveSource()) { 332 getWakeLock().acquire(); 333 } else { 334 getWakeLock().release(); 335 } 336 } 337 338 @ServiceThreadOnly getWakeLock()339 private ActiveWakeLock getWakeLock() { 340 assertRunOnServiceThread(); 341 if (mWakeLock == null) { 342 if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) { 343 mWakeLock = new SystemWakeLock(); 344 } else { 345 // Create a stub lock object that doesn't do anything about wake lock, 346 // hence allows the device to go to sleep even if it's the active source. 347 mWakeLock = new ActiveWakeLock() { 348 @Override 349 public void acquire() { } 350 @Override 351 public void release() { } 352 @Override 353 public boolean isHeld() { return false; } 354 }; 355 HdmiLogger.debug("No wakelock is used to keep the display on."); 356 } 357 } 358 return mWakeLock; 359 } 360 361 @Override canGoToStandby()362 protected boolean canGoToStandby() { 363 return !getWakeLock().isHeld(); 364 } 365 366 @Override 367 @ServiceThreadOnly onActiveSourceLost()368 protected void onActiveSourceLost() { 369 assertRunOnServiceThread(); 370 mService.pauseActiveMediaSessions(); 371 switch (mService.getHdmiCecConfig().getStringValue( 372 HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) { 373 case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW: 374 mService.standby(); 375 return; 376 case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE: 377 return; 378 } 379 } 380 381 @ServiceThreadOnly 382 @Constants.HandleMessageResult handleUserControlPressed(HdmiCecMessage message)383 protected int handleUserControlPressed(HdmiCecMessage message) { 384 assertRunOnServiceThread(); 385 wakeUpIfActiveSource(); 386 return super.handleUserControlPressed(message); 387 } 388 389 @ServiceThreadOnly 390 @Constants.HandleMessageResult handleSetMenuLanguage(HdmiCecMessage message)391 protected int handleSetMenuLanguage(HdmiCecMessage message) { 392 assertRunOnServiceThread(); 393 if (mService.getHdmiCecConfig().getIntValue( 394 HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE) 395 == HdmiControlManager.SET_MENU_LANGUAGE_DISABLED) { 396 return Constants.ABORT_UNRECOGNIZED_OPCODE; 397 } 398 399 try { 400 String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII"); 401 Locale currentLocale = mService.getContext().getResources().getConfiguration().locale; 402 String curIso3Language = mService.localeToMenuLanguage(currentLocale); 403 HdmiLogger.debug("handleSetMenuLanguage " + iso3Language + " cur:" + curIso3Language); 404 if (curIso3Language.equals(iso3Language)) { 405 // Do not switch language if the new language is the same as the current one. 406 // This helps avoid accidental country variant switching from en_US to en_AU 407 // due to the limitation of CEC. See the warning below. 408 return Constants.HANDLED; 409 } 410 411 // Don't use Locale.getAvailableLocales() since it returns a locale 412 // which is not available on Settings. 413 final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales( 414 mService.getContext(), false); 415 for (LocaleInfo localeInfo : localeInfos) { 416 if (mService.localeToMenuLanguage(localeInfo.getLocale()).equals(iso3Language)) { 417 // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires 418 // additional country variant to pinpoint the locale. This keeps the right 419 // locale from being chosen. 'eng' in the CEC command, for instance, 420 // will always be mapped to en-AU among other variants like en-US, en-GB, 421 // an en-IN, which may not be the expected one. 422 startSetMenuLanguageActivity(localeInfo.getLocale()); 423 return Constants.HANDLED; 424 } 425 } 426 Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language); 427 return Constants.ABORT_INVALID_OPERAND; 428 } catch (UnsupportedEncodingException e) { 429 Slog.w(TAG, "Can't handle <Set Menu Language>", e); 430 return Constants.ABORT_INVALID_OPERAND; 431 } 432 } 433 startSetMenuLanguageActivity(Locale locale)434 private void startSetMenuLanguageActivity(Locale locale) { 435 final long identity = Binder.clearCallingIdentity(); 436 try { 437 Context context = mService.getContext(); 438 Intent intent = new Intent(); 439 intent.putExtra(HdmiControlManager.EXTRA_LOCALE, locale.toLanguageTag()); 440 intent.setComponent( 441 ComponentName.unflattenFromString(context.getResources().getString( 442 com.android.internal.R.string.config_hdmiCecSetMenuLanguageActivity))); 443 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 444 context.startActivityAsUser(intent, context.getUser()); 445 } catch (ActivityNotFoundException e) { 446 Slog.e(TAG, "unable to start HdmiCecSetMenuLanguageActivity"); 447 } finally { 448 Binder.restoreCallingIdentity(identity); 449 } 450 } 451 452 @Override 453 @Constants.HandleMessageResult handleSetSystemAudioMode(HdmiCecMessage message)454 protected int handleSetSystemAudioMode(HdmiCecMessage message) { 455 // System Audio Mode only turns on/off when Audio System broadcasts on/off message. 456 // For device with type 4 and 5, it can set system audio mode on/off 457 // when there is another audio system device connected into the system first. 458 if (message.getDestination() != Constants.ADDR_BROADCAST 459 || message.getSource() != Constants.ADDR_AUDIO_SYSTEM 460 || mService.audioSystem() != null) { 461 return Constants.HANDLED; 462 } 463 boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message); 464 if (mService.isSystemAudioActivated() != setSystemAudioModeOn) { 465 mService.setSystemAudioActivated(setSystemAudioModeOn); 466 } 467 return Constants.HANDLED; 468 } 469 470 @Override 471 @Constants.HandleMessageResult handleSystemAudioModeStatus(HdmiCecMessage message)472 protected int handleSystemAudioModeStatus(HdmiCecMessage message) { 473 // Only directly addressed System Audio Mode Status message can change internal 474 // system audio mode status. 475 if (message.getDestination() == getDeviceInfo().getLogicalAddress() 476 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM) { 477 boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message); 478 if (mService.isSystemAudioActivated() != setSystemAudioModeOn) { 479 mService.setSystemAudioActivated(setSystemAudioModeOn); 480 } 481 } 482 return Constants.HANDLED; 483 } 484 485 @Override 486 @ServiceThreadOnly 487 @Constants.HandleMessageResult handleRoutingChange(HdmiCecMessage message)488 protected int handleRoutingChange(HdmiCecMessage message) { 489 assertRunOnServiceThread(); 490 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2); 491 handleRoutingChangeAndInformation(physicalAddress, message); 492 return Constants.HANDLED; 493 } 494 495 @Override 496 @ServiceThreadOnly 497 @Constants.HandleMessageResult handleRoutingInformation(HdmiCecMessage message)498 protected int handleRoutingInformation(HdmiCecMessage message) { 499 assertRunOnServiceThread(); 500 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 501 handleRoutingChangeAndInformation(physicalAddress, message); 502 return Constants.HANDLED; 503 } 504 505 @Override 506 @ServiceThreadOnly handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message)507 protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) { 508 assertRunOnServiceThread(); 509 if (physicalAddress != mService.getPhysicalAddress()) { 510 setActiveSource(physicalAddress, 511 "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()"); 512 return; 513 } 514 if (!isActiveSource()) { 515 // If routing is changed to the device while Active Source, don't invalidate the 516 // Active Source 517 setActiveSource(physicalAddress, 518 "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()"); 519 } 520 switch (mPlaybackDeviceActionOnRoutingControl) { 521 case WAKE_UP_AND_SEND_ACTIVE_SOURCE: 522 setAndBroadcastActiveSource(message, physicalAddress, 523 "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()"); 524 break; 525 case WAKE_UP_ONLY: 526 mService.wakeUp(); 527 break; 528 case NONE: 529 break; 530 } 531 } 532 533 /** 534 * Called after logical address allocation is finished, allowing a local device to react to 535 * messages in the buffer before they are processed. This method may be used to cancel deferred 536 * actions. 537 */ 538 @Override preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages)539 protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) { 540 for (HdmiCecMessage message: bufferedMessages) { 541 // Prevent the device from broadcasting <Active Source> message if the active path 542 // changed during address allocation. 543 if (message.getOpcode() == Constants.MESSAGE_ROUTING_CHANGE 544 || message.getOpcode() == Constants.MESSAGE_SET_STREAM_PATH 545 || message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { 546 removeAction(ActiveSourceAction.class); 547 removeAction(OneTouchPlayAction.class); 548 return; 549 } 550 } 551 } 552 553 @Override findKeyReceiverAddress()554 protected int findKeyReceiverAddress() { 555 return Constants.ADDR_TV; 556 } 557 558 @Override findAudioReceiverAddress()559 protected int findAudioReceiverAddress() { 560 if (mService.isSystemAudioActivated()) { 561 return Constants.ADDR_AUDIO_SYSTEM; 562 } 563 return Constants.ADDR_TV; 564 } 565 566 @Override 567 @ServiceThreadOnly disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)568 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 569 assertRunOnServiceThread(); 570 removeAction(DeviceDiscoveryAction.class); 571 removeAction(HotplugDetectionAction.class); 572 removeAction(NewDeviceAction.class); 573 super.disableDevice(initiatedByCec, callback); 574 clearDeviceInfoList(); 575 checkIfPendingActionsCleared(); 576 } 577 578 @Override dump(final IndentingPrintWriter pw)579 protected void dump(final IndentingPrintWriter pw) { 580 super.dump(pw); 581 pw.println("isActiveSource(): " + isActiveSource()); 582 } 583 584 // Wrapper interface over PowerManager.WakeLock 585 private interface ActiveWakeLock { acquire()586 void acquire(); release()587 void release(); isHeld()588 boolean isHeld(); 589 } 590 591 private class SystemWakeLock implements ActiveWakeLock { 592 private final WakeLock mWakeLock; SystemWakeLock()593 public SystemWakeLock() { 594 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 595 mWakeLock.setReferenceCounted(false); 596 } 597 598 @Override acquire()599 public void acquire() { 600 mWakeLock.acquire(); 601 HdmiLogger.debug("active source: %b. Wake lock acquired", isActiveSource()); 602 } 603 604 @Override release()605 public void release() { 606 mWakeLock.release(); 607 HdmiLogger.debug("Wake lock released"); 608 } 609 610 @Override isHeld()611 public boolean isHeld() { 612 return mWakeLock.isHeld(); 613 } 614 } 615 } 616