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