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