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