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