OSDN Git Service

Merge "CEC: Add CEC device upon receiving <Report Physical Address>" into lmp-mr1-dev
[android-x86/frameworks-base.git] / services / core / java / com / android / server / hdmi / HdmiCecLocalDeviceTv.java
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 static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE;
20 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION;
21 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE;
22 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
23 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
24 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
25 import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT;
26 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED;
27 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
28 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
29 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
30 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
31 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
32
33 import android.annotation.Nullable;
34 import android.content.Context;
35 import android.hardware.hdmi.HdmiControlManager;
36 import android.hardware.hdmi.HdmiDeviceInfo;
37 import android.hardware.hdmi.HdmiPortInfo;
38 import android.hardware.hdmi.HdmiRecordSources;
39 import android.hardware.hdmi.HdmiTimerRecordSources;
40 import android.hardware.hdmi.IHdmiControlCallback;
41 import android.media.AudioManager;
42 import android.media.AudioSystem;
43 import android.media.tv.TvInputInfo;
44 import android.media.tv.TvInputManager;
45 import android.media.tv.TvInputManager.TvInputCallback;
46 import android.os.RemoteException;
47 import android.os.SystemProperties;
48 import android.provider.Settings.Global;
49 import android.util.ArraySet;
50 import android.util.Slog;
51 import android.util.SparseArray;
52
53 import com.android.internal.annotations.GuardedBy;
54 import com.android.internal.util.IndentingPrintWriter;
55 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
56 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
57 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
58 import com.android.server.SystemService;
59
60 import java.io.UnsupportedEncodingException;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collection;
64 import java.util.Collections;
65 import java.util.Iterator;
66 import java.util.List;
67 import java.util.HashMap;
68
69 /**
70  * Represent a logical device of type TV residing in Android system.
71  */
72 final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
73     private static final String TAG = "HdmiCecLocalDeviceTv";
74
75     // Whether ARC is available or not. "true" means that ARC is established between TV and
76     // AVR as audio receiver.
77     @ServiceThreadOnly
78     private boolean mArcEstablished = false;
79
80     // Whether ARC feature is enabled or not. The default value is true.
81     // TODO: once adding system setting for it, read the value to it.
82     private boolean mArcFeatureEnabled = true;
83
84     // Whether System audio mode is activated or not.
85     // This becomes true only when all system audio sequences are finished.
86     @GuardedBy("mLock")
87     private boolean mSystemAudioActivated = false;
88
89     // The previous port id (input) before switching to the new one. This is remembered in order to
90     // be able to switch to it upon receiving <Inactive Source> from currently active source.
91     // This remains valid only when the active source was switched via one touch play operation
92     // (either by TV or source device). Manual port switching invalidates this value to
93     // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
94     @GuardedBy("mLock")
95     private int mPrevPortId;
96
97     @GuardedBy("mLock")
98     private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
99
100     @GuardedBy("mLock")
101     private boolean mSystemAudioMute = false;
102
103     // Copy of mDeviceInfos to guarantee thread-safety.
104     @GuardedBy("mLock")
105     private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
106     // All external cec input(source) devices. Does not include system audio device.
107     @GuardedBy("mLock")
108     private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
109
110     // Map-like container of all cec devices including local ones.
111     // device id is used as key of container.
112     // This is not thread-safe. For external purpose use mSafeDeviceInfos.
113     private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
114
115     // If true, TV going to standby mode puts other devices also to standby.
116     private boolean mAutoDeviceOff;
117
118     // If true, TV wakes itself up when receiving <Text/Image View On>.
119     private boolean mAutoWakeup;
120
121     // List of the logical address of local CEC devices. Unmodifiable, thread-safe.
122     private List<Integer> mLocalDeviceAddresses;
123
124     private final HdmiCecStandbyModeHandler mStandbyHandler;
125
126     // If true, do not do routing control/send active source for internal source.
127     // Set to true when the device was woken up by <Text/Image View On>.
128     private boolean mSkipRoutingControl;
129
130     // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
131     // other CEC devices since they might not have logical address.
132     private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
133
134     // Message buffer used to buffer selected messages to process later. <Active Source>
135     // from a source device, for instance, needs to be buffered if the device is not
136     // discovered yet. The buffered commands are taken out and when they are ready to
137     // handle.
138     private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
139
140     // Defines the callback invoked when TV input framework is updated with input status.
141     // We are interested in the notification for HDMI input addition event, in order to
142     // process any CEC commands that arrived before the input is added.
143     private final TvInputCallback mTvInputCallback = new TvInputCallback() {
144         @Override
145         public void onInputAdded(String inputId) {
146             TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
147             HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
148             if (info == null) return;
149             addTvInput(inputId, info.getId());
150             if (info.isCecDevice()) {
151                 processDelayedActiveSource(info.getLogicalAddress());
152             }
153         }
154
155         @Override
156         public void onInputRemoved(String inputId) {
157             removeTvInput(inputId);
158         }
159     };
160
161     // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to
162     // accept input switching request from HDMI devices. Requests for which the corresponding
163     // input ID is not yet registered by TV input framework need to be buffered for delayed
164     // processing.
165     private final HashMap<String, Integer> mTvInputs = new HashMap<>();
166
167     @ServiceThreadOnly
168     private void addTvInput(String inputId, int deviceId) {
169         assertRunOnServiceThread();
170         mTvInputs.put(inputId, deviceId);
171     }
172
173     @ServiceThreadOnly
174     private void removeTvInput(String inputId) {
175         assertRunOnServiceThread();
176         mTvInputs.remove(inputId);
177     }
178
179     @Override
180     @ServiceThreadOnly
181     protected boolean isInputReady(int deviceId) {
182         assertRunOnServiceThread();
183         return mTvInputs.containsValue(deviceId);
184     }
185
186     HdmiCecLocalDeviceTv(HdmiControlService service) {
187         super(service, HdmiDeviceInfo.DEVICE_TV);
188         mPrevPortId = Constants.INVALID_PORT_ID;
189         mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
190                 true);
191         mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
192         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
193     }
194
195     @Override
196     @ServiceThreadOnly
197     protected void onAddressAllocated(int logicalAddress, int reason) {
198         assertRunOnServiceThread();
199         mService.registerTvInputCallback(mTvInputCallback);
200         mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
201                 mAddress, mService.getPhysicalAddress(), mDeviceType));
202         mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
203                 mAddress, mService.getVendorId()));
204         mCecSwitches.add(mService.getPhysicalAddress());  // TV is a CEC switch too.
205         mTvInputs.clear();
206         mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
207         launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
208                 reason != HdmiControlService.INITIATED_BY_BOOT_UP);
209         mLocalDeviceAddresses = initLocalDeviceAddresses();
210         launchDeviceDiscovery();
211         startQueuedActions();
212     }
213
214
215     @ServiceThreadOnly
216     private List<Integer> initLocalDeviceAddresses() {
217         assertRunOnServiceThread();
218         List<Integer> addresses = new ArrayList<>();
219         for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
220             addresses.add(device.getDeviceInfo().getLogicalAddress());
221         }
222         return Collections.unmodifiableList(addresses);
223     }
224
225     @Override
226     protected int getPreferredAddress() {
227         return Constants.ADDR_TV;
228     }
229
230     @Override
231     protected void setPreferredAddress(int addr) {
232         Slog.w(TAG, "Preferred addres will not be stored for TV");
233     }
234
235     @Override
236     @ServiceThreadOnly
237     boolean dispatchMessage(HdmiCecMessage message) {
238         assertRunOnServiceThread();
239         if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) {
240             return true;
241         }
242         return super.onMessage(message);
243     }
244
245     /**
246      * Performs the action 'device select', or 'one touch play' initiated by TV.
247      *
248      * @param id id of HDMI device to select
249      * @param callback callback object to report the result with
250      */
251     @ServiceThreadOnly
252     void deviceSelect(int id, IHdmiControlCallback callback) {
253         assertRunOnServiceThread();
254         HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
255         if (targetDevice == null) {
256             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
257             return;
258         }
259         int targetAddress = targetDevice.getLogicalAddress();
260         ActiveSource active = getActiveSource();
261         if (active.isValid() && targetAddress == active.logicalAddress) {
262             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
263             return;
264         }
265         if (targetAddress == Constants.ADDR_INTERNAL) {
266             handleSelectInternalSource();
267             // Switching to internal source is always successful even when CEC control is disabled.
268             setActiveSource(targetAddress, mService.getPhysicalAddress());
269             setActivePath(mService.getPhysicalAddress());
270             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
271             return;
272         }
273         if (!mService.isControlEnabled()) {
274             setActiveSource(targetDevice);
275             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
276             return;
277         }
278         removeAction(DeviceSelectAction.class);
279         addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
280     }
281
282     @ServiceThreadOnly
283     private void handleSelectInternalSource() {
284         assertRunOnServiceThread();
285         // Seq #18
286         if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) {
287             updateActiveSource(mAddress, mService.getPhysicalAddress());
288             if (mSkipRoutingControl) {
289                 mSkipRoutingControl = false;
290                 return;
291             }
292             HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
293                     mAddress, mService.getPhysicalAddress());
294             mService.sendCecCommand(activeSource);
295         }
296     }
297
298     @ServiceThreadOnly
299     void updateActiveSource(int logicalAddress, int physicalAddress) {
300         assertRunOnServiceThread();
301         updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress));
302     }
303
304     @ServiceThreadOnly
305     void updateActiveSource(ActiveSource newActive) {
306         assertRunOnServiceThread();
307         // Seq #14
308         if (mActiveSource.equals(newActive)) {
309             return;
310         }
311         setActiveSource(newActive);
312         int logicalAddress = newActive.logicalAddress;
313         if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
314             if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
315                 setPrevPortId(getActivePortId());
316             }
317             // TODO: Show the OSD banner related to the new active source device.
318         } else {
319             // TODO: If displayed, remove the OSD banner related to the previous
320             //       active source device.
321         }
322     }
323
324     int getPortId(int physicalAddress) {
325         return mService.pathToPortId(physicalAddress);
326     }
327
328     /**
329      * Returns the previous port id kept to handle input switching on <Inactive Source>.
330      */
331     int getPrevPortId() {
332         synchronized (mLock) {
333             return mPrevPortId;
334         }
335     }
336
337     /**
338      * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
339      * taken for <Inactive Source>.
340      */
341     void setPrevPortId(int portId) {
342         synchronized (mLock) {
343             mPrevPortId = portId;
344         }
345     }
346
347     @ServiceThreadOnly
348     void updateActiveInput(int path, boolean notifyInputChange) {
349         assertRunOnServiceThread();
350         // Seq #15
351         setPrevPortId(getActivePortId());
352         setActivePath(path);
353         // TODO: Handle PAP/PIP case.
354         // Show OSD port change banner
355         if (notifyInputChange) {
356             ActiveSource activeSource = getActiveSource();
357             HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
358             if (info == null) {
359                 info = mService.getDeviceInfoByPort(getActivePortId());
360                 if (info == null) {
361                     // No CEC/MHL device is present at the port. Attempt to switch to
362                     // the hardware port itself for non-CEC devices that may be connected.
363                     info = new HdmiDeviceInfo(path, getActivePortId());
364                 }
365             }
366             mService.invokeInputChangeListener(info);
367         }
368     }
369
370     @ServiceThreadOnly
371     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
372         assertRunOnServiceThread();
373         // Seq #20
374         if (!mService.isValidPortId(portId)) {
375             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
376             return;
377         }
378         if (portId == getActivePortId()) {
379             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
380             return;
381         }
382         mActiveSource.invalidate();
383         if (!mService.isControlEnabled()) {
384             setActivePortId(portId);
385             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
386             return;
387         }
388         int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
389                 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
390         setActivePath(oldPath);
391         if (mSkipRoutingControl) {
392             mSkipRoutingControl = false;
393             return;
394         }
395         int newPath = mService.portIdToPath(portId);
396         startRoutingControl(oldPath, newPath, true, callback);
397     }
398
399     @ServiceThreadOnly
400     void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus,
401             IHdmiControlCallback callback) {
402         assertRunOnServiceThread();
403         if (oldPath == newPath) {
404             return;
405         }
406         HdmiCecMessage routingChange =
407                 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
408         mService.sendCecCommand(routingChange);
409         removeAction(RoutingControlAction.class);
410         addAndStartAction(
411                 new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback));
412     }
413
414     @ServiceThreadOnly
415     int getPowerStatus() {
416         assertRunOnServiceThread();
417         return mService.getPowerStatus();
418     }
419
420     /**
421      * Sends key to a target CEC device.
422      *
423      * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
424      * @param isPressed true if this is key press event
425      */
426     @Override
427     @ServiceThreadOnly
428     protected void sendKeyEvent(int keyCode, boolean isPressed) {
429         assertRunOnServiceThread();
430         if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
431             Slog.w(TAG, "Unsupported key: " + keyCode);
432             return;
433         }
434         List<SendKeyAction> action = getActions(SendKeyAction.class);
435         if (!action.isEmpty()) {
436             action.get(0).processKeyEvent(keyCode, isPressed);
437         } else {
438             if (isPressed) {
439                 int logicalAddress = findKeyReceiverAddress();
440                 if (logicalAddress != Constants.ADDR_INVALID) {
441                     addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
442                     return;
443                 }
444             }
445             Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed);
446         }
447     }
448
449     private int findKeyReceiverAddress() {
450         if (getActiveSource().isValid()) {
451             return getActiveSource().logicalAddress;
452         }
453         HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath());
454         if (info != null) {
455             return info.getLogicalAddress();
456         }
457         return Constants.ADDR_INVALID;
458     }
459
460     private static void invokeCallback(IHdmiControlCallback callback, int result) {
461         if (callback == null) {
462             return;
463         }
464         try {
465             callback.onComplete(result);
466         } catch (RemoteException e) {
467             Slog.e(TAG, "Invoking callback failed:" + e);
468         }
469     }
470
471     @Override
472     @ServiceThreadOnly
473     protected boolean handleActiveSource(HdmiCecMessage message) {
474         assertRunOnServiceThread();
475         int logicalAddress = message.getSource();
476         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
477         HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
478         if (info == null) {
479             if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
480                 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
481                 mDelayedMessageBuffer.add(message);
482             }
483         } else if (!isInputReady(info.getId())) {
484             HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
485             mDelayedMessageBuffer.add(message);
486         } else {
487             ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
488             ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
489         }
490         return true;
491     }
492
493     @Override
494     @ServiceThreadOnly
495     protected boolean handleInactiveSource(HdmiCecMessage message) {
496         assertRunOnServiceThread();
497         // Seq #10
498
499         // Ignore <Inactive Source> from non-active source device.
500         if (getActiveSource().logicalAddress != message.getSource()) {
501             return true;
502         }
503         if (isProhibitMode()) {
504             return true;
505         }
506         int portId = getPrevPortId();
507         if (portId != Constants.INVALID_PORT_ID) {
508             // TODO: Do this only if TV is not showing multiview like PIP/PAP.
509
510             HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
511             if (inactiveSource == null) {
512                 return true;
513             }
514             if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
515                 return true;
516             }
517             // TODO: Switch the TV freeze mode off
518
519             doManualPortSwitching(portId, null);
520             setPrevPortId(Constants.INVALID_PORT_ID);
521         } else {
522             // No HDMI port to switch to was found. Notify the input change listers to
523             // switch to the lastly shown internal input.
524             mActiveSource.invalidate();
525             setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
526             mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
527         }
528         return true;
529     }
530
531     @Override
532     @ServiceThreadOnly
533     protected boolean handleRequestActiveSource(HdmiCecMessage message) {
534         assertRunOnServiceThread();
535         // Seq #19
536         if (mAddress == getActiveSource().logicalAddress) {
537             mService.sendCecCommand(
538                     HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
539         }
540         return true;
541     }
542
543     @Override
544     @ServiceThreadOnly
545     protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
546         assertRunOnServiceThread();
547         if (!broadcastMenuLanguage(mService.getLanguage())) {
548             Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
549         }
550         return true;
551     }
552
553     @ServiceThreadOnly
554     boolean broadcastMenuLanguage(String language) {
555         assertRunOnServiceThread();
556         HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
557                 mAddress, language);
558         if (command != null) {
559             mService.sendCecCommand(command);
560             return true;
561         }
562         return false;
563     }
564
565     @Override
566     @ServiceThreadOnly
567     protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
568         assertRunOnServiceThread();
569         int path = HdmiUtils.twoBytesToInt(message.getParams());
570         int address = message.getSource();
571         int type = message.getParams()[2];
572
573         if (updateCecSwitchInfo(address, type, path)) return true;
574
575         // Ignore if [Device Discovery Action] is going on.
576         if (hasAction(DeviceDiscoveryAction.class)) {
577             Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
578             return true;
579         }
580
581         if (!isInDeviceList(address, path)) {
582             handleNewDeviceAtTheTailOfActivePath(path);
583         }
584
585         // Add the device ahead with default information to handle <Active Source>
586         // promptly, rather than waiting till the new device action is finished.
587         HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo(address, path, getPortId(path), type,
588                 Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address));
589         addCecDevice(deviceInfo);
590         startNewDeviceAction(ActiveSource.of(address, path), type);
591         return true;
592     }
593
594     @Override
595     protected boolean handleReportPowerStatus(HdmiCecMessage command) {
596         int newStatus = command.getParams()[0] & 0xFF;
597         updateDevicePowerStatus(command.getSource(), newStatus);
598         return true;
599     }
600
601     @Override
602     protected boolean handleTimerStatus(HdmiCecMessage message) {
603         // Do nothing.
604         return true;
605     }
606
607     @Override
608     protected boolean handleRecordStatus(HdmiCecMessage message) {
609         // Do nothing.
610         return true;
611     }
612
613     boolean updateCecSwitchInfo(int address, int type, int path) {
614         if (address == Constants.ADDR_UNREGISTERED
615                 && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
616             mCecSwitches.add(path);
617             updateSafeDeviceInfoList();
618             return true;  // Pure switch does not need further processing. Return here.
619         }
620         if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
621             mCecSwitches.add(path);
622         }
623         return false;
624     }
625
626     void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
627         for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
628             // If there is new device action which has the same logical address and path
629             // ignore new request.
630             // NewDeviceAction is created whenever it receives <Report Physical Address>.
631             // And there is a chance starting NewDeviceAction for the same source.
632             // Usually, new device sends <Report Physical Address> when it's plugged
633             // in. However, TV can detect a new device from HotPlugDetectionAction,
634             // which sends <Give Physical Address> to the source for newly detected
635             // device.
636             if (action.isActionOf(activeSource)) {
637                 return;
638             }
639         }
640
641         addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
642                 activeSource.physicalAddress, deviceType));
643     }
644
645     private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
646         // Seq #22
647         if (isTailOfActivePath(path, getActivePath())) {
648             int newPath = mService.portIdToPath(getActivePortId());
649             setActivePath(newPath);
650             startRoutingControl(getActivePath(), newPath, false, null);
651             return true;
652         }
653         return false;
654     }
655
656     /**
657      * Whether the given path is located in the tail of current active path.
658      *
659      * @param path to be tested
660      * @param activePath current active path
661      * @return true if the given path is located in the tail of current active path; otherwise,
662      *         false
663      */
664     static boolean isTailOfActivePath(int path, int activePath) {
665         // If active routing path is internal source, return false.
666         if (activePath == 0) {
667             return false;
668         }
669         for (int i = 12; i >= 0; i -= 4) {
670             int curActivePath = (activePath >> i) & 0xF;
671             if (curActivePath == 0) {
672                 return true;
673             } else {
674                 int curPath = (path >> i) & 0xF;
675                 if (curPath != curActivePath) {
676                     return false;
677                 }
678             }
679         }
680         return false;
681     }
682
683     @Override
684     @ServiceThreadOnly
685     protected boolean handleRoutingChange(HdmiCecMessage message) {
686         assertRunOnServiceThread();
687         // Seq #21
688         byte[] params = message.getParams();
689         int currentPath = HdmiUtils.twoBytesToInt(params);
690         if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
691             mActiveSource.invalidate();
692             removeAction(RoutingControlAction.class);
693             int newPath = HdmiUtils.twoBytesToInt(params, 2);
694             addAndStartAction(new RoutingControlAction(this, newPath, true, null));
695         }
696         return true;
697     }
698
699     @Override
700     @ServiceThreadOnly
701     protected boolean handleReportAudioStatus(HdmiCecMessage message) {
702         assertRunOnServiceThread();
703
704         byte params[] = message.getParams();
705         int mute = params[0] & 0x80;
706         int volume = params[0] & 0x7F;
707         setAudioStatus(mute == 0x80, volume);
708         return true;
709     }
710
711     @Override
712     @ServiceThreadOnly
713     protected boolean handleTextViewOn(HdmiCecMessage message) {
714         assertRunOnServiceThread();
715         if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
716             mService.wakeUp();
717         }
718         return true;
719     }
720
721     @Override
722     @ServiceThreadOnly
723     protected boolean handleImageViewOn(HdmiCecMessage message) {
724         assertRunOnServiceThread();
725         // Currently, it's the same as <Text View On>.
726         return handleTextViewOn(message);
727     }
728
729     @Override
730     @ServiceThreadOnly
731     protected boolean handleSetOsdName(HdmiCecMessage message) {
732         int source = message.getSource();
733         HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
734         // If the device is not in device list, ignore it.
735         if (deviceInfo == null) {
736             Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
737             return true;
738         }
739         String osdName = null;
740         try {
741             osdName = new String(message.getParams(), "US-ASCII");
742         } catch (UnsupportedEncodingException e) {
743             Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
744             return true;
745         }
746
747         if (deviceInfo.getDisplayName().equals(osdName)) {
748             Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
749             return true;
750         }
751
752         addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
753                 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
754                 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
755         return true;
756     }
757
758     @ServiceThreadOnly
759     private void launchDeviceDiscovery() {
760         assertRunOnServiceThread();
761         clearDeviceInfoList();
762         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
763                 new DeviceDiscoveryCallback() {
764                     @Override
765                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
766                         for (HdmiDeviceInfo info : deviceInfos) {
767                             addCecDevice(info);
768                         }
769
770                         // Since we removed all devices when it's start and
771                         // device discovery action does not poll local devices,
772                         // we should put device info of local device manually here
773                         for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
774                             addCecDevice(device.getDeviceInfo());
775                         }
776
777                         addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
778                         addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
779
780                         // If there is AVR, initiate System Audio Auto initiation action,
781                         // which turns on and off system audio according to last system
782                         // audio setting.
783                         HdmiDeviceInfo avr = getAvrDeviceInfo();
784                         if (avr != null) {
785                             onNewAvrAdded(avr);
786                         }
787                     }
788                 });
789         addAndStartAction(action);
790     }
791
792     @ServiceThreadOnly
793     void onNewAvrAdded(HdmiDeviceInfo avr) {
794         assertRunOnServiceThread();
795         if (getSystemAudioModeSetting() && !isSystemAudioActivated()) {
796             addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
797         }
798         if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) {
799             startArcAction(true);
800         }
801     }
802
803     // Clear all device info.
804     @ServiceThreadOnly
805     private void clearDeviceInfoList() {
806         assertRunOnServiceThread();
807         for (HdmiDeviceInfo info : mSafeExternalInputs) {
808             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
809         }
810         mDeviceInfos.clear();
811         updateSafeDeviceInfoList();
812     }
813
814     @ServiceThreadOnly
815     // Seq #32
816     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
817         assertRunOnServiceThread();
818         if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
819             setSystemAudioMode(false, true);
820             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
821             return;
822         }
823         HdmiDeviceInfo avr = getAvrDeviceInfo();
824         if (avr == null) {
825             setSystemAudioMode(false, true);
826             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
827             return;
828         }
829
830         addAndStartAction(
831                 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
832     }
833
834     // # Seq 25
835     void setSystemAudioMode(boolean on, boolean updateSetting) {
836         HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on);
837
838         if (updateSetting) {
839             mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on);
840         }
841         updateAudioManagerForSystemAudio(on);
842         synchronized (mLock) {
843             if (mSystemAudioActivated != on) {
844                 mSystemAudioActivated = on;
845                 mService.announceSystemAudioModeChange(on);
846             }
847         }
848     }
849
850     private void updateAudioManagerForSystemAudio(boolean on) {
851         int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
852         HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
853     }
854
855     boolean isSystemAudioActivated() {
856         if (!hasSystemAudioDevice()) {
857             return false;
858         }
859         synchronized (mLock) {
860             return mSystemAudioActivated;
861         }
862     }
863
864     boolean getSystemAudioModeSetting() {
865         return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false);
866     }
867
868     /**
869      * Change ARC status into the given {@code enabled} status.
870      *
871      * @return {@code true} if ARC was in "Enabled" status
872      */
873     @ServiceThreadOnly
874     boolean setArcStatus(boolean enabled) {
875         assertRunOnServiceThread();
876
877         HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
878         boolean oldStatus = mArcEstablished;
879         // 1. Enable/disable ARC circuit.
880         mService.setAudioReturnChannel(getAvrDeviceInfo().getPortId(), enabled);
881         // 2. Notify arc status to audio service.
882         notifyArcStatusToAudioService(enabled);
883         // 3. Update arc status;
884         mArcEstablished = enabled;
885         return oldStatus;
886     }
887
888     @ServiceThreadOnly
889     private void updateArcFeatureStatus(int portId, boolean isConnected) {
890         assertRunOnServiceThread();
891         // HEAC 2.4, HEACT 5-15
892         // Should not activate ARC if +5V status is false.
893         HdmiPortInfo portInfo = mService.getPortInfo(portId);
894         if (portInfo.isArcSupported()) {
895             changeArcFeatureEnabled(isConnected);
896         }
897     }
898
899     private void notifyArcStatusToAudioService(boolean enabled) {
900         // Note that we don't set any name to ARC.
901         mService.getAudioManager().setWiredDeviceConnectionState(
902                 AudioSystem.DEVICE_OUT_HDMI_ARC,
903                 enabled ? 1 : 0, "");
904     }
905
906     /**
907      * Returns whether ARC is enabled or not.
908      */
909     @ServiceThreadOnly
910     boolean isArcEstabilished() {
911         assertRunOnServiceThread();
912         return mArcFeatureEnabled && mArcEstablished;
913     }
914
915     @ServiceThreadOnly
916     void changeArcFeatureEnabled(boolean enabled) {
917         assertRunOnServiceThread();
918
919         if (mArcFeatureEnabled != enabled) {
920             mArcFeatureEnabled = enabled;
921             if (enabled) {
922                 if (!mArcEstablished) {
923                     startArcAction(true);
924                 }
925             } else {
926                 if (mArcEstablished) {
927                     startArcAction(false);
928                 }
929             }
930         }
931     }
932
933     @ServiceThreadOnly
934     boolean isArcFeatureEnabled() {
935         assertRunOnServiceThread();
936         return mArcFeatureEnabled;
937     }
938
939     @ServiceThreadOnly
940     void startArcAction(boolean enabled) {
941         assertRunOnServiceThread();
942         HdmiDeviceInfo info = getAvrDeviceInfo();
943         if (info == null) {
944             Slog.w(TAG, "Failed to start arc action; No AVR device.");
945             return;
946         }
947         if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
948             Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
949             if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
950                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
951             }
952             return;
953         }
954
955         // Terminate opposite action and start action if not exist.
956         if (enabled) {
957             removeAction(RequestArcTerminationAction.class);
958             if (!hasAction(RequestArcInitiationAction.class)) {
959                 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
960             }
961         } else {
962             removeAction(RequestArcInitiationAction.class);
963             if (!hasAction(RequestArcTerminationAction.class)) {
964                 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
965             }
966         }
967     }
968
969     private boolean isDirectConnectAddress(int physicalAddress) {
970         return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
971     }
972
973     void setAudioStatus(boolean mute, int volume) {
974         synchronized (mLock) {
975             mSystemAudioMute = mute;
976             mSystemAudioVolume = volume;
977             int maxVolume = mService.getAudioManager().getStreamMaxVolume(
978                     AudioManager.STREAM_MUSIC);
979             mService.setAudioStatus(mute,
980                     VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
981             displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
982                     mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
983         }
984     }
985
986     @ServiceThreadOnly
987     void changeVolume(int curVolume, int delta, int maxVolume) {
988         assertRunOnServiceThread();
989         if (delta == 0 || !isSystemAudioActivated()) {
990             return;
991         }
992
993         int targetVolume = curVolume + delta;
994         int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
995         synchronized (mLock) {
996             // If new volume is the same as current system audio volume, just ignore it.
997             // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
998             if (cecVolume == mSystemAudioVolume) {
999                 // Update tv volume with system volume value.
1000                 mService.setAudioStatus(false,
1001                         VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
1002                 return;
1003             }
1004         }
1005
1006         List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
1007         if (actions.isEmpty()) {
1008             addAndStartAction(new VolumeControlAction(this,
1009                     getAvrDeviceInfo().getLogicalAddress(), delta > 0));
1010         } else {
1011             actions.get(0).handleVolumeChange(delta > 0);
1012         }
1013     }
1014
1015     @ServiceThreadOnly
1016     void changeMute(boolean mute) {
1017         assertRunOnServiceThread();
1018         HdmiLogger.debug("[A]:Change mute:%b", mute);
1019         synchronized (mLock) {
1020             if (mSystemAudioMute == mute) {
1021                 HdmiLogger.debug("No need to change mute.");
1022                 return;
1023             }
1024         }
1025         if (!isSystemAudioActivated()) {
1026             HdmiLogger.debug("[A]:System audio is not activated.");
1027             return;
1028         }
1029
1030         // Remove existing volume action.
1031         removeAction(VolumeControlAction.class);
1032         sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
1033                 mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION :
1034                         HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
1035     }
1036
1037     @Override
1038     @ServiceThreadOnly
1039     protected boolean handleInitiateArc(HdmiCecMessage message) {
1040         assertRunOnServiceThread();
1041
1042         if (!canStartArcUpdateAction(message.getSource(), true)) {
1043             if (getAvrDeviceInfo() == null) {
1044                 // AVR may not have been discovered yet. Delay the message processing.
1045                 mDelayedMessageBuffer.add(message);
1046                 return true;
1047             }
1048             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1049             if (!isConnectedToArcPort(message.getSource())) {
1050                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1051             }
1052             return true;
1053         }
1054
1055         // In case where <Initiate Arc> is started by <Request ARC Initiation>
1056         // need to clean up RequestArcInitiationAction.
1057         removeAction(RequestArcInitiationAction.class);
1058         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1059                 message.getSource(), true);
1060         addAndStartAction(action);
1061         return true;
1062     }
1063
1064     private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) {
1065         HdmiDeviceInfo avr = getAvrDeviceInfo();
1066         if (avr != null
1067                 && (avrAddress == avr.getLogicalAddress())
1068                 && isConnectedToArcPort(avr.getPhysicalAddress())
1069                 && isDirectConnectAddress(avr.getPhysicalAddress())) {
1070             if (shouldCheckArcFeatureEnabled) {
1071                 return isArcFeatureEnabled();
1072             } else {
1073                 return true;
1074             }
1075         } else {
1076             return false;
1077         }
1078     }
1079
1080     @Override
1081     @ServiceThreadOnly
1082     protected boolean handleTerminateArc(HdmiCecMessage message) {
1083         assertRunOnServiceThread();
1084         // In cast of termination, do not check ARC configuration in that AVR device
1085         // might be removed already.
1086
1087         // In case where <Terminate Arc> is started by <Request ARC Termination>
1088         // need to clean up RequestArcInitiationAction.
1089         removeAction(RequestArcTerminationAction.class);
1090         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1091                 message.getSource(), false);
1092         addAndStartAction(action);
1093         return true;
1094     }
1095
1096     @Override
1097     @ServiceThreadOnly
1098     protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
1099         assertRunOnServiceThread();
1100         if (!isMessageForSystemAudio(message)) {
1101             if (getAvrDeviceInfo() == null) {
1102                 // AVR may not have been discovered yet. Delay the message processing.
1103                 mDelayedMessageBuffer.add(message);
1104                 return true;
1105             }
1106             HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
1107             mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
1108             return true;
1109         }
1110         SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
1111                 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
1112         addAndStartAction(action);
1113         return true;
1114     }
1115
1116     @Override
1117     @ServiceThreadOnly
1118     protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
1119         assertRunOnServiceThread();
1120         if (!isMessageForSystemAudio(message)) {
1121             HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
1122             // Ignore this message.
1123             return true;
1124         }
1125         setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true);
1126         return true;
1127     }
1128
1129     // Seq #53
1130     @Override
1131     @ServiceThreadOnly
1132     protected boolean handleRecordTvScreen(HdmiCecMessage message) {
1133         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1134         if (!actions.isEmpty()) {
1135             // Assumes only one OneTouchRecordAction.
1136             OneTouchRecordAction action = actions.get(0);
1137             if (action.getRecorderAddress() != message.getSource()) {
1138                 announceOneTouchRecordResult(
1139                         message.getSource(),
1140                         HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1141             }
1142             return super.handleRecordTvScreen(message);
1143         }
1144
1145         int recorderAddress = message.getSource();
1146         byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
1147         int reason = startOneTouchRecord(recorderAddress, recordSource);
1148         if (reason != Constants.ABORT_NO_ERROR) {
1149             mService.maySendFeatureAbortCommand(message, reason);
1150         }
1151         return true;
1152     }
1153
1154     @Override
1155     protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
1156         byte[] params = message.getParams();
1157         int timerClearedStatusData = params[0] & 0xFF;
1158         announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1159         return true;
1160     }
1161
1162     void announceOneTouchRecordResult(int recorderAddress, int result) {
1163         mService.invokeOneTouchRecordResult(recorderAddress, result);
1164     }
1165
1166     void announceTimerRecordingResult(int recorderAddress, int result) {
1167         mService.invokeTimerRecordingResult(recorderAddress, result);
1168     }
1169
1170     void announceClearTimerRecordingResult(int recorderAddress, int result) {
1171         mService.invokeClearTimerRecordingResult(recorderAddress, result);
1172     }
1173
1174     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
1175         return mService.isControlEnabled()
1176                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1177                 && (message.getDestination() == Constants.ADDR_TV
1178                         || message.getDestination() == Constants.ADDR_BROADCAST)
1179                 && getAvrDeviceInfo() != null;
1180     }
1181
1182     /**
1183      * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
1184      * logical address as new device info's.
1185      *
1186      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1187      *
1188      * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
1189      * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
1190      *         that has the same logical address as new one has.
1191      */
1192     @ServiceThreadOnly
1193     private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
1194         assertRunOnServiceThread();
1195         HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
1196         if (oldDeviceInfo != null) {
1197             removeDeviceInfo(deviceInfo.getId());
1198         }
1199         mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
1200         updateSafeDeviceInfoList();
1201         return oldDeviceInfo;
1202     }
1203
1204     /**
1205      * Remove a device info corresponding to the given {@code logicalAddress}.
1206      * It returns removed {@link HdmiDeviceInfo} if exists.
1207      *
1208      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1209      *
1210      * @param id id of device to be removed
1211      * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
1212      */
1213     @ServiceThreadOnly
1214     private HdmiDeviceInfo removeDeviceInfo(int id) {
1215         assertRunOnServiceThread();
1216         HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
1217         if (deviceInfo != null) {
1218             mDeviceInfos.remove(id);
1219         }
1220         updateSafeDeviceInfoList();
1221         return deviceInfo;
1222     }
1223
1224     /**
1225      * Return a list of all {@link HdmiDeviceInfo}.
1226      *
1227      * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1228      * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
1229      * does not include local device.
1230      */
1231     @ServiceThreadOnly
1232     List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
1233         assertRunOnServiceThread();
1234         if (includeLocalDevice) {
1235             return HdmiUtils.sparseArrayToList(mDeviceInfos);
1236         } else {
1237             ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1238             for (int i = 0; i < mDeviceInfos.size(); ++i) {
1239                 HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1240                 if (!isLocalDeviceAddress(info.getLogicalAddress())) {
1241                     infoList.add(info);
1242                 }
1243             }
1244             return infoList;
1245         }
1246     }
1247
1248     /**
1249      * Return external input devices.
1250      */
1251     List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
1252         return mSafeExternalInputs;
1253     }
1254
1255     @ServiceThreadOnly
1256     private void updateSafeDeviceInfoList() {
1257         assertRunOnServiceThread();
1258         List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
1259         List<HdmiDeviceInfo> externalInputs = getInputDevices();
1260         synchronized (mLock) {
1261             mSafeAllDeviceInfos = copiedDevices;
1262             mSafeExternalInputs = externalInputs;
1263         }
1264     }
1265
1266     /**
1267      * Return a list of external cec input (source) devices.
1268      *
1269      * <p>Note that this effectively excludes non-source devices like system audio,
1270      * secondary TV.
1271      */
1272     private List<HdmiDeviceInfo> getInputDevices() {
1273         ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1274         for (int i = 0; i < mDeviceInfos.size(); ++i) {
1275             HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
1276             if (isLocalDeviceAddress(info.getLogicalAddress())) {
1277                 continue;
1278             }
1279             if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
1280                 infoList.add(info);
1281             }
1282         }
1283         return infoList;
1284     }
1285
1286     // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
1287     // Returns true if the policy is set to true, and the device to check does not have
1288     // a parent CEC device (which should be the CEC-enabled switch) in the list.
1289     private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
1290         return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
1291                 && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
1292     }
1293
1294     private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
1295         for (int switchPath : switches) {
1296             if (isParentPath(switchPath, path)) {
1297                 return true;
1298             }
1299         }
1300         return false;
1301     }
1302
1303     private static boolean isParentPath(int parentPath, int childPath) {
1304         // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
1305         // If child's last non-zero nibble is removed, the result equals to the parent.
1306         for (int i = 0; i <= 12; i += 4) {
1307             int nibble = (childPath >> i) & 0xF;
1308             if (nibble != 0) {
1309                 int parentNibble = (parentPath >> i) & 0xF;
1310                 return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
1311             }
1312         }
1313         return false;
1314     }
1315
1316     private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
1317         if (!hideDevicesBehindLegacySwitch(info)) {
1318             mService.invokeDeviceEventListeners(info, status);
1319         }
1320     }
1321
1322     private boolean isLocalDeviceAddress(int address) {
1323         return mLocalDeviceAddresses.contains(address);
1324     }
1325
1326     @ServiceThreadOnly
1327     HdmiDeviceInfo getAvrDeviceInfo() {
1328         assertRunOnServiceThread();
1329         return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1330     }
1331
1332     /**
1333      * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
1334      *
1335      * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
1336      *
1337      * @param logicalAddress logical address of the device to be retrieved
1338      * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1339      *         Returns null if no logical address matched
1340      */
1341     @ServiceThreadOnly
1342     HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
1343         assertRunOnServiceThread();
1344         return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
1345     }
1346
1347     boolean hasSystemAudioDevice() {
1348         return getSafeAvrDeviceInfo() != null;
1349     }
1350
1351     HdmiDeviceInfo getSafeAvrDeviceInfo() {
1352         return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1353     }
1354
1355     /**
1356      * Thread safe version of {@link #getCecDeviceInfo(int)}.
1357      *
1358      * @param logicalAddress logical address to be retrieved
1359      * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
1360      *         Returns null if no logical address matched
1361      */
1362     HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
1363         synchronized (mLock) {
1364             for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1365                 if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
1366                     return info;
1367                 }
1368             }
1369             return null;
1370         }
1371     }
1372
1373     List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
1374         ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
1375         for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1376             if (isLocalDeviceAddress(info.getLogicalAddress())) {
1377                 continue;
1378             }
1379             infoList.add(info);
1380         }
1381         return infoList;
1382     }
1383
1384     /**
1385      * Called when a device is newly added or a new device is detected or
1386      * existing device is updated.
1387      *
1388      * @param info device info of a new device.
1389      */
1390     @ServiceThreadOnly
1391     final void addCecDevice(HdmiDeviceInfo info) {
1392         assertRunOnServiceThread();
1393         HdmiDeviceInfo old = addDeviceInfo(info);
1394         if (info.getLogicalAddress() == mAddress) {
1395             // The addition of TV device itself should not be notified.
1396             return;
1397         }
1398         if (old == null) {
1399             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1400         } else if (!old.equals(info)) {
1401             invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1402             invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
1403         }
1404     }
1405
1406     /**
1407      * Called when a device is removed or removal of device is detected.
1408      *
1409      * @param address a logical address of a device to be removed
1410      */
1411     @ServiceThreadOnly
1412     final void removeCecDevice(int address) {
1413         assertRunOnServiceThread();
1414         HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
1415
1416         mCecMessageCache.flushMessagesFrom(address);
1417         invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
1418     }
1419
1420     @ServiceThreadOnly
1421     void handleRemoveActiveRoutingPath(int path) {
1422         assertRunOnServiceThread();
1423         // Seq #23
1424         if (isTailOfActivePath(path, getActivePath())) {
1425             int newPath = mService.portIdToPath(getActivePortId());
1426             startRoutingControl(getActivePath(), newPath, true, null);
1427         }
1428     }
1429
1430     /**
1431      * Launch routing control process.
1432      *
1433      * @param routingForBootup true if routing control is initiated due to One Touch Play
1434      *        or TV power on
1435      */
1436     @ServiceThreadOnly
1437     void launchRoutingControl(boolean routingForBootup) {
1438         assertRunOnServiceThread();
1439         // Seq #24
1440         if (getActivePortId() != Constants.INVALID_PORT_ID) {
1441             if (!routingForBootup && !isProhibitMode()) {
1442                 int newPath = mService.portIdToPath(getActivePortId());
1443                 setActivePath(newPath);
1444                 startRoutingControl(getActivePath(), newPath, routingForBootup, null);
1445             }
1446         } else {
1447             int activePath = mService.getPhysicalAddress();
1448             setActivePath(activePath);
1449             if (!routingForBootup
1450                     && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
1451                 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
1452                         activePath));
1453             }
1454         }
1455     }
1456
1457     /**
1458      * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
1459      * the given routing path. CEC devices use routing path for its physical address to
1460      * describe the hierarchy of the devices in the network.
1461      *
1462      * @param path routing path or physical address
1463      * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
1464      */
1465     @ServiceThreadOnly
1466     final HdmiDeviceInfo getDeviceInfoByPath(int path) {
1467         assertRunOnServiceThread();
1468         for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
1469             if (info.getPhysicalAddress() == path) {
1470                 return info;
1471             }
1472         }
1473         return null;
1474     }
1475
1476     /**
1477      * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
1478      * the given routing path. This is the version accessible safely from threads
1479      * other than service thread.
1480      *
1481      * @param path routing path or physical address
1482      * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
1483      */
1484     HdmiDeviceInfo getSafeDeviceInfoByPath(int path) {
1485         synchronized (mLock) {
1486             for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1487                 if (info.getPhysicalAddress() == path) {
1488                     return info;
1489                 }
1490             }
1491             return null;
1492         }
1493     }
1494
1495     /**
1496      * Whether a device of the specified physical address and logical address exists
1497      * in a device info list. However, both are minimal condition and it could
1498      * be different device from the original one.
1499      *
1500      * @param logicalAddress logical address of a device to be searched
1501      * @param physicalAddress physical address of a device to be searched
1502      * @return true if exist; otherwise false
1503      */
1504     @ServiceThreadOnly
1505     boolean isInDeviceList(int logicalAddress, int physicalAddress) {
1506         assertRunOnServiceThread();
1507         HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
1508         if (device == null) {
1509             return false;
1510         }
1511         return device.getPhysicalAddress() == physicalAddress;
1512     }
1513
1514     @Override
1515     @ServiceThreadOnly
1516     void onHotplug(int portId, boolean connected) {
1517         assertRunOnServiceThread();
1518
1519         if (!connected) {
1520             removeCecSwitches(portId);
1521         }
1522         // Tv device will have permanent HotplugDetectionAction.
1523         List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1524         if (!hotplugActions.isEmpty()) {
1525             // Note that hotplug action is single action running on a machine.
1526             // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1527             // It covers seq #40, #43.
1528             hotplugActions.get(0).pollAllDevicesNow();
1529         }
1530         updateArcFeatureStatus(portId, connected);
1531     }
1532
1533     private void removeCecSwitches(int portId) {
1534         Iterator<Integer> it = mCecSwitches.iterator();
1535         while (!it.hasNext()) {
1536             int path = it.next();
1537             if (pathToPortId(path) == portId) {
1538                 it.remove();
1539             }
1540         }
1541     }
1542
1543     @ServiceThreadOnly
1544     void setAutoDeviceOff(boolean enabled) {
1545         assertRunOnServiceThread();
1546         mAutoDeviceOff = enabled;
1547     }
1548
1549     @ServiceThreadOnly
1550     void setAutoWakeup(boolean enabled) {
1551         assertRunOnServiceThread();
1552         mAutoWakeup = enabled;
1553     }
1554
1555     @ServiceThreadOnly
1556     boolean getAutoWakeup() {
1557         assertRunOnServiceThread();
1558         return mAutoWakeup;
1559     }
1560
1561     @Override
1562     @ServiceThreadOnly
1563     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1564         super.disableDevice(initiatedByCec, callback);
1565         assertRunOnServiceThread();
1566         mService.unregisterTvInputCallback(mTvInputCallback);
1567         // Remove any repeated working actions.
1568         // HotplugDetectionAction will be reinstated during the wake up process.
1569         // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1570         //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1571         removeAction(DeviceDiscoveryAction.class);
1572         removeAction(HotplugDetectionAction.class);
1573         removeAction(PowerStatusMonitorAction.class);
1574         // Remove recording actions.
1575         removeAction(OneTouchRecordAction.class);
1576         removeAction(TimerRecordingAction.class);
1577
1578         disableSystemAudioIfExist();
1579         disableArcIfExist();
1580         clearDeviceInfoList();
1581         checkIfPendingActionsCleared();
1582     }
1583
1584     @ServiceThreadOnly
1585     private void disableSystemAudioIfExist() {
1586         assertRunOnServiceThread();
1587         if (getAvrDeviceInfo() == null) {
1588             return;
1589         }
1590
1591         // Seq #31.
1592         removeAction(SystemAudioActionFromAvr.class);
1593         removeAction(SystemAudioActionFromTv.class);
1594         removeAction(SystemAudioAutoInitiationAction.class);
1595         removeAction(SystemAudioStatusAction.class);
1596         removeAction(VolumeControlAction.class);
1597
1598         // Turn off the mode but do not write it the settings, so that the next time TV powers on
1599         // the system audio mode setting can be restored automatically.
1600         setSystemAudioMode(false, false);
1601     }
1602
1603     @ServiceThreadOnly
1604     private void disableArcIfExist() {
1605         assertRunOnServiceThread();
1606         HdmiDeviceInfo avr = getAvrDeviceInfo();
1607         if (avr == null) {
1608             return;
1609         }
1610
1611         // Seq #44.
1612         removeAction(RequestArcInitiationAction.class);
1613         if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
1614             addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1615         }
1616     }
1617
1618     @Override
1619     @ServiceThreadOnly
1620     protected void onStandby(boolean initiatedByCec) {
1621         assertRunOnServiceThread();
1622         // Seq #11
1623         if (!mService.isControlEnabled()) {
1624             return;
1625         }
1626         if (!initiatedByCec && mAutoDeviceOff) {
1627             mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1628                     mAddress, Constants.ADDR_BROADCAST));
1629         }
1630     }
1631
1632     boolean isProhibitMode() {
1633         return mService.isProhibitMode();
1634     }
1635
1636     boolean isPowerStandbyOrTransient() {
1637         return mService.isPowerStandbyOrTransient();
1638     }
1639
1640     @ServiceThreadOnly
1641     void displayOsd(int messageId) {
1642         assertRunOnServiceThread();
1643         mService.displayOsd(messageId);
1644     }
1645
1646     @ServiceThreadOnly
1647     void displayOsd(int messageId, int extra) {
1648         assertRunOnServiceThread();
1649         mService.displayOsd(messageId, extra);
1650     }
1651
1652     // Seq #54 and #55
1653     @ServiceThreadOnly
1654     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1655         assertRunOnServiceThread();
1656         if (!mService.isControlEnabled()) {
1657             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1658             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1659             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1660         }
1661
1662         if (!checkRecorder(recorderAddress)) {
1663             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1664             announceOneTouchRecordResult(recorderAddress,
1665                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1666             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1667         }
1668
1669         if (!checkRecordSource(recordSource)) {
1670             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1671             announceOneTouchRecordResult(recorderAddress,
1672                     ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1673             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1674         }
1675
1676         addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1677         Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1678                 + Arrays.toString(recordSource));
1679         return Constants.ABORT_NO_ERROR;
1680     }
1681
1682     @ServiceThreadOnly
1683     void stopOneTouchRecord(int recorderAddress) {
1684         assertRunOnServiceThread();
1685         if (!mService.isControlEnabled()) {
1686             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1687             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1688             return;
1689         }
1690
1691         if (!checkRecorder(recorderAddress)) {
1692             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1693             announceOneTouchRecordResult(recorderAddress,
1694                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1695             return;
1696         }
1697
1698         // Remove one touch record action so that other one touch record can be started.
1699         removeAction(OneTouchRecordAction.class);
1700         mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
1701         Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1702     }
1703
1704     private boolean checkRecorder(int recorderAddress) {
1705         HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
1706         return (device != null)
1707                 && (HdmiUtils.getTypeFromAddress(recorderAddress)
1708                         == HdmiDeviceInfo.DEVICE_RECORDER);
1709     }
1710
1711     private boolean checkRecordSource(byte[] recordSource) {
1712         return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1713     }
1714
1715     @ServiceThreadOnly
1716     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1717         assertRunOnServiceThread();
1718         if (!mService.isControlEnabled()) {
1719             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1720             announceTimerRecordingResult(recorderAddress,
1721                     TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
1722             return;
1723         }
1724
1725         if (!checkRecorder(recorderAddress)) {
1726             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1727             announceTimerRecordingResult(recorderAddress,
1728                     TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
1729             return;
1730         }
1731
1732         if (!checkTimerRecordingSource(sourceType, recordSource)) {
1733             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1734             announceTimerRecordingResult(
1735                     recorderAddress,
1736                     TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
1737             return;
1738         }
1739
1740         addAndStartAction(
1741                 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1742         Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1743                 + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1744     }
1745
1746     private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1747         return (recordSource != null)
1748                 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1749     }
1750
1751     @ServiceThreadOnly
1752     void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1753         assertRunOnServiceThread();
1754         if (!mService.isControlEnabled()) {
1755             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1756             announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1757             return;
1758         }
1759
1760         if (!checkRecorder(recorderAddress)) {
1761             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1762             announceClearTimerRecordingResult(recorderAddress,
1763                     CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1764             return;
1765         }
1766
1767         if (!checkTimerRecordingSource(sourceType, recordSource)) {
1768             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1769             announceClearTimerRecordingResult(recorderAddress,
1770                     CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1771             return;
1772         }
1773
1774         sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1775     }
1776
1777     private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1778             byte[] recordSource) {
1779         HdmiCecMessage message = null;
1780         switch (sourceType) {
1781             case TIMER_RECORDING_TYPE_DIGITAL:
1782                 message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
1783                         recordSource);
1784                 break;
1785             case TIMER_RECORDING_TYPE_ANALOGUE:
1786                 message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
1787                         recordSource);
1788                 break;
1789             case TIMER_RECORDING_TYPE_EXTERNAL:
1790                 message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
1791                         recordSource);
1792                 break;
1793             default:
1794                 Slog.w(TAG, "Invalid source type:" + recorderAddress);
1795                 announceClearTimerRecordingResult(recorderAddress,
1796                         CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1797                 return;
1798
1799         }
1800         mService.sendCecCommand(message, new SendMessageCallback() {
1801             @Override
1802             public void onSendCompleted(int error) {
1803                 if (error != Constants.SEND_RESULT_SUCCESS) {
1804                     announceClearTimerRecordingResult(recorderAddress,
1805                             CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1806                 }
1807             }
1808         });
1809     }
1810
1811     void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
1812         HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
1813         if (info == null) {
1814             Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1815             return;
1816         }
1817
1818         if (info.getDevicePowerStatus() == newPowerStatus) {
1819             return;
1820         }
1821
1822         HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1823         // addDeviceInfo replaces old device info with new one if exists.
1824         addDeviceInfo(newInfo);
1825
1826         invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
1827     }
1828
1829     @Override
1830     protected boolean handleMenuStatus(HdmiCecMessage message) {
1831         // Do nothing and just return true not to prevent from responding <Feature Abort>.
1832         return true;
1833     }
1834
1835     @Override
1836     protected void sendStandby(int deviceId) {
1837         HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId);
1838         if (targetDevice == null) {
1839             return;
1840         }
1841         int targetAddress = targetDevice.getLogicalAddress();
1842         mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
1843     }
1844
1845     @ServiceThreadOnly
1846     void processAllDelayedMessages() {
1847         assertRunOnServiceThread();
1848         mDelayedMessageBuffer.processAllMessages();
1849     }
1850
1851     @ServiceThreadOnly
1852     void processDelayedMessages(int address) {
1853         assertRunOnServiceThread();
1854         mDelayedMessageBuffer.processMessagesForDevice(address);
1855     }
1856
1857     @ServiceThreadOnly
1858     void processDelayedActiveSource(int address) {
1859         assertRunOnServiceThread();
1860         mDelayedMessageBuffer.processActiveSource(address);
1861     }
1862
1863     @Override
1864     protected void dump(final IndentingPrintWriter pw) {
1865         super.dump(pw);
1866         pw.println("mArcEstablished: " + mArcEstablished);
1867         pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1868         pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
1869         pw.println("mSystemAudioMute: " + mSystemAudioMute);
1870         pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
1871         pw.println("mAutoWakeup: " + mAutoWakeup);
1872         pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1873         pw.println("mPrevPortId: " + mPrevPortId);
1874         pw.println("CEC devices:");
1875         pw.increaseIndent();
1876         for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1877             pw.println(info);
1878         }
1879         pw.decreaseIndent();
1880     }
1881 }