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