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