1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.hdmi;
18 
19 import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
20 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
21 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE;
22 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION;
23 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE;
24 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
25 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
26 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
27 import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT;
28 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED;
29 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
30 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
31 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
32 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
33 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
34 
35 import android.annotation.Nullable;
36 import android.hardware.hdmi.DeviceFeatures;
37 import android.hardware.hdmi.HdmiControlManager;
38 import android.hardware.hdmi.HdmiDeviceInfo;
39 import android.hardware.hdmi.HdmiPortInfo;
40 import android.hardware.hdmi.HdmiRecordSources;
41 import android.hardware.hdmi.HdmiTimerRecordSources;
42 import android.hardware.hdmi.IHdmiControlCallback;
43 import android.hardware.tv.cec.V1_0.SendMessageResult;
44 import android.media.AudioDescriptor;
45 import android.media.AudioDeviceAttributes;
46 import android.media.AudioDeviceInfo;
47 import android.media.AudioProfile;
48 import android.media.tv.TvInputInfo;
49 import android.media.tv.TvInputManager.TvInputCallback;
50 import android.os.Handler;
51 import android.util.Slog;
52 import android.util.SparseBooleanArray;
53 
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.util.IndentingPrintWriter;
57 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
58 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
59 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
60 
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.HashMap;
64 import java.util.List;
65 import java.util.stream.Collectors;
66 
67 /**
68  * Represent a logical device of type TV residing in Android system.
69  */
70 public final 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     // Stores whether ARC feature is enabled per port.
79     // True by default for all the ARC-enabled ports.
80     private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray();
81 
82     // Whether the System Audio Control feature is enabled or not. True by default.
83     @GuardedBy("mLock")
84     private boolean mSystemAudioControlFeatureEnabled;
85 
86     // The previous port id (input) before switching to the new one. This is remembered in order to
87     // be able to switch to it upon receiving <Inactive Source> from currently active source.
88     // This remains valid only when the active source was switched via one touch play operation
89     // (either by TV or source device). Manual port switching invalidates this value to
90     // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
91     @GuardedBy("mLock")
92     private int mPrevPortId;
93 
94     @GuardedBy("mLock")
95     private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
96 
97     @GuardedBy("mLock")
98     private boolean mSystemAudioMute = false;
99 
100     // If true, do not do routing control/send active source for internal source.
101     // Set to true for a short duration when the device is woken up by <Text/Image View On>.
102     private boolean mSkipRoutingControl;
103 
104     // Handler for posting a runnable to set `mSkipRoutingControl` to false after a delay
105     private final Handler mSkipRoutingControlHandler;
106 
107     // Runnable that sets `mSkipRoutingControl` to false
108     private final Runnable mResetSkipRoutingControlRunnable = () -> mSkipRoutingControl = false;
109 
110     // Message buffer used to buffer selected messages to process later. <Active Source>
111     // from a source device, for instance, needs to be buffered if the device is not
112     // discovered yet. The buffered commands are taken out and when they are ready to
113     // handle.
114     private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this);
115 
116     // Defines the callback invoked when TV input framework is updated with input status.
117     // We are interested in the notification for HDMI input addition event, in order to
118     // process any CEC commands that arrived before the input is added.
119     private final TvInputCallback mTvInputCallback = new TvInputCallback() {
120         @Override
121         public void onInputAdded(String inputId) {
122             TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
123             if (tvInfo == null) return;
124             HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
125             if (info == null) return;
126             addTvInput(inputId, info.getId());
127             if (info.isCecDevice()) {
128                 processDelayedActiveSource(info.getLogicalAddress());
129             }
130         }
131 
132         @Override
133         public void onInputRemoved(String inputId) {
134             removeTvInput(inputId);
135         }
136     };
137 
138     // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to
139     // accept input switching request from HDMI devices. Requests for which the corresponding
140     // input ID is not yet registered by TV input framework need to be buffered for delayed
141     // processing.
142     private final HashMap<String, Integer> mTvInputs = new HashMap<>();
143 
144     @ServiceThreadOnly
addTvInput(String inputId, int deviceId)145     private void addTvInput(String inputId, int deviceId) {
146         assertRunOnServiceThread();
147         mTvInputs.put(inputId, deviceId);
148     }
149 
150     @ServiceThreadOnly
removeTvInput(String inputId)151     private void removeTvInput(String inputId) {
152         assertRunOnServiceThread();
153         mTvInputs.remove(inputId);
154     }
155 
156     @Override
157     @ServiceThreadOnly
isInputReady(int deviceId)158     protected boolean isInputReady(int deviceId) {
159         assertRunOnServiceThread();
160         return mTvInputs.containsValue(deviceId);
161     }
162 
163     private SelectRequestBuffer mSelectRequestBuffer;
164 
HdmiCecLocalDeviceTv(HdmiControlService service)165     HdmiCecLocalDeviceTv(HdmiControlService service) {
166         super(service, HdmiDeviceInfo.DEVICE_TV);
167         mPrevPortId = Constants.INVALID_PORT_ID;
168         mSystemAudioControlFeatureEnabled = service.getHdmiCecConfig().getIntValue(
169                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)
170                     == HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED;
171         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
172         mSkipRoutingControlHandler = new Handler(service.getServiceLooper());
173     }
174 
175     @Override
176     @ServiceThreadOnly
onAddressAllocated(int logicalAddress, int reason)177     protected void onAddressAllocated(int logicalAddress, int reason) {
178         assertRunOnServiceThread();
179         List<HdmiPortInfo> ports = mService.getPortInfo();
180         for (HdmiPortInfo port : ports) {
181             mArcFeatureEnabled.put(port.getId(), port.isArcSupported());
182         }
183         mService.registerTvInputCallback(mTvInputCallback);
184         mService.sendCecCommand(
185                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
186                         getDeviceInfo().getLogicalAddress(),
187                         mService.getPhysicalAddress(),
188                         mDeviceType));
189         mService.sendCecCommand(
190                 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
191                         getDeviceInfo().getLogicalAddress(), mService.getVendorId()));
192         mService.getHdmiCecNetwork().addCecSwitch(
193                 mService.getHdmiCecNetwork().getPhysicalAddress());  // TV is a CEC switch too.
194         mTvInputs.clear();
195 
196         mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
197         mSkipRoutingControlHandler.removeCallbacks(mResetSkipRoutingControlRunnable);
198         if (mSkipRoutingControl) {
199             mSkipRoutingControlHandler.postDelayed(mResetSkipRoutingControlRunnable,
200                     HdmiConfig.TIMEOUT_MS);
201         }
202 
203         launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
204                 reason != HdmiControlService.INITIATED_BY_BOOT_UP);
205         resetSelectRequestBuffer();
206         launchDeviceDiscovery();
207         startQueuedActions();
208         if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
209             if (hasAction(RequestActiveSourceAction.class)) {
210                 Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting.");
211                 removeAction(RequestActiveSourceAction.class);
212             }
213             addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
214                 @Override
215                 public void onComplete(int result) {
216                     if (!mService.getLocalActiveSource().isValid()
217                             && result != HdmiControlManager.RESULT_SUCCESS) {
218                         mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
219                                 getDeviceInfo().getLogicalAddress(),
220                                 getDeviceInfo().getPhysicalAddress()));
221                         updateActiveSource(getDeviceInfo().getLogicalAddress(),
222                                 getDeviceInfo().getPhysicalAddress(),
223                                 "RequestActiveSourceAction#finishWithCallback()");
224                     }
225                 }
226             }));
227         }
228     }
229 
230     @ServiceThreadOnly
setSelectRequestBuffer(SelectRequestBuffer requestBuffer)231     public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) {
232         assertRunOnServiceThread();
233         mSelectRequestBuffer = requestBuffer;
234     }
235 
236     @ServiceThreadOnly
resetSelectRequestBuffer()237     private void resetSelectRequestBuffer() {
238         assertRunOnServiceThread();
239         setSelectRequestBuffer(SelectRequestBuffer.EMPTY_BUFFER);
240     }
241 
242     @Override
getPreferredAddress()243     protected int getPreferredAddress() {
244         return Constants.ADDR_TV;
245     }
246 
247     @Override
setPreferredAddress(int addr)248     protected void setPreferredAddress(int addr) {
249         Slog.w(TAG, "Preferred addres will not be stored for TV");
250     }
251 
252     @Override
253     @ServiceThreadOnly
254     @VisibleForTesting
255     @Constants.HandleMessageResult
dispatchMessage(HdmiCecMessage message)256     protected int dispatchMessage(HdmiCecMessage message) {
257         assertRunOnServiceThread();
258         if (mService.isPowerStandby() && !mService.isWakeUpMessageReceived()
259                 && mStandbyHandler.handleCommand(message)) {
260             return Constants.HANDLED;
261         }
262         return super.onMessage(message);
263     }
264 
265     /**
266      * Performs the action 'device select', or 'one touch play' initiated by TV.
267      *
268      * @param id id of HDMI device to select
269      * @param callback callback object to report the result with
270      */
271     @ServiceThreadOnly
deviceSelect(int id, IHdmiControlCallback callback)272     void deviceSelect(int id, IHdmiControlCallback callback) {
273         assertRunOnServiceThread();
274         HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id);
275         if (targetDevice == null) {
276             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
277             return;
278         }
279         int targetAddress = targetDevice.getLogicalAddress();
280         if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
281             return;
282         }
283         removeAction(RequestActiveSourceAction.class);
284         if (targetAddress == Constants.ADDR_INTERNAL) {
285             handleSelectInternalSource();
286             // Switching to internal source is always successful even when CEC control is disabled.
287             setActiveSource(targetAddress, mService.getPhysicalAddress(),
288                     "HdmiCecLocalDeviceTv#deviceSelect()");
289             setActivePath(mService.getPhysicalAddress());
290             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
291             return;
292         }
293         if (!mService.isCecControlEnabled()) {
294             setActiveSource(targetDevice, "HdmiCecLocalDeviceTv#deviceSelect()");
295             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
296             return;
297         }
298         removeAction(DeviceSelectActionFromTv.class);
299         addAndStartAction(new DeviceSelectActionFromTv(this, targetDevice, callback));
300     }
301 
302     @ServiceThreadOnly
handleSelectInternalSource()303     private void handleSelectInternalSource() {
304         assertRunOnServiceThread();
305         // Seq #18
306         if (mService.isCecControlEnabled()
307                 && getActiveSource().logicalAddress != getDeviceInfo().getLogicalAddress()) {
308             updateActiveSource(
309                     getDeviceInfo().getLogicalAddress(),
310                     mService.getPhysicalAddress(),
311                     "HdmiCecLocalDeviceTv#handleSelectInternalSource()");
312             if (mSkipRoutingControl) {
313                 mSkipRoutingControl = false;
314                 return;
315             }
316             HdmiCecMessage activeSource =
317                     HdmiCecMessageBuilder.buildActiveSource(
318                             getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress());
319             mService.sendCecCommand(activeSource);
320         }
321     }
322 
323     @ServiceThreadOnly
updateActiveSource(int logicalAddress, int physicalAddress, String caller)324     void updateActiveSource(int logicalAddress, int physicalAddress, String caller) {
325         assertRunOnServiceThread();
326         updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress), caller);
327     }
328 
329     @ServiceThreadOnly
updateActiveSource(ActiveSource newActive, String caller)330     void updateActiveSource(ActiveSource newActive, String caller) {
331         assertRunOnServiceThread();
332         // Seq #14
333         if (getActiveSource().equals(newActive)) {
334             return;
335         }
336         setActiveSource(newActive, caller);
337         int logicalAddress = newActive.logicalAddress;
338         if (mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress) != null
339                 && logicalAddress != getDeviceInfo().getLogicalAddress()) {
340             if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
341                 setPrevPortId(getActivePortId());
342             }
343             // TODO: Show the OSD banner related to the new active source device.
344         } else {
345             // TODO: If displayed, remove the OSD banner related to the previous
346             //       active source device.
347         }
348     }
349 
350     /**
351      * Returns the previous port id kept to handle input switching on <Inactive Source>.
352      */
getPrevPortId()353     int getPrevPortId() {
354         synchronized (mLock) {
355             return mPrevPortId;
356         }
357     }
358 
359     /**
360      * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
361      * taken for <Inactive Source>.
362      */
setPrevPortId(int portId)363     void setPrevPortId(int portId) {
364         synchronized (mLock) {
365             mPrevPortId = portId;
366         }
367     }
368 
369     @ServiceThreadOnly
updateActiveInput(int path, boolean notifyInputChange)370     void updateActiveInput(int path, boolean notifyInputChange) {
371         assertRunOnServiceThread();
372         // Seq #15
373         setActivePath(path);
374         // TODO: Handle PAP/PIP case.
375         // Show OSD port change banner
376         if (notifyInputChange) {
377             ActiveSource activeSource = getActiveSource();
378             HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(
379                     activeSource.logicalAddress);
380             if (info == null) {
381                 info = mService.getDeviceInfoByPort(getActivePortId());
382                 if (info == null) {
383                     // No CEC/MHL device is present at the port. Attempt to switch to
384                     // the hardware port itself for non-CEC devices that may be connected.
385                     info = HdmiDeviceInfo.hardwarePort(path, getActivePortId());
386                 }
387             }
388             mService.invokeInputChangeListener(info);
389         }
390     }
391 
392     @ServiceThreadOnly
doManualPortSwitching(int portId, IHdmiControlCallback callback)393     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
394         assertRunOnServiceThread();
395         // Seq #20
396         if (!mService.isValidPortId(portId)) {
397             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
398             return;
399         }
400         if (portId == getActivePortId()) {
401             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
402             return;
403         }
404         getActiveSource().invalidate();
405         if (!mService.isCecControlEnabled()) {
406             setActivePortId(portId);
407             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
408             return;
409         }
410         int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
411                 && getActivePortId() != Constants.CEC_SWITCH_HOME
412                 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
413         setActivePath(oldPath);
414         if (mSkipRoutingControl) {
415             mSkipRoutingControl = false;
416             return;
417         }
418         int newPath = mService.portIdToPath(portId);
419         startRoutingControl(oldPath, newPath, callback);
420     }
421 
422     @ServiceThreadOnly
startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback)423     void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) {
424         assertRunOnServiceThread();
425         if (oldPath == newPath) {
426             return;
427         }
428         HdmiCecMessage routingChange =
429                 HdmiCecMessageBuilder.buildRoutingChange(
430                         getDeviceInfo().getLogicalAddress(), oldPath, newPath);
431         mService.sendCecCommand(routingChange);
432         removeAction(RoutingControlAction.class);
433         addAndStartAction(
434                 new RoutingControlAction(this, newPath, callback));
435     }
436 
437     @ServiceThreadOnly
getPowerStatus()438     int getPowerStatus() {
439         assertRunOnServiceThread();
440         return mService.getPowerStatus();
441     }
442 
443     @Override
findKeyReceiverAddress()444     protected int findKeyReceiverAddress() {
445         if (getActiveSource().isValid()) {
446             return getActiveSource().logicalAddress;
447         }
448         HdmiDeviceInfo info = mService.getHdmiCecNetwork().getDeviceInfoByPath(getActivePath());
449         if (info != null) {
450             return info.getLogicalAddress();
451         }
452         return Constants.ADDR_INVALID;
453     }
454 
455     @Override
findAudioReceiverAddress()456     protected int findAudioReceiverAddress() {
457         return Constants.ADDR_AUDIO_SYSTEM;
458     }
459 
460     @Override
461     @ServiceThreadOnly
462     @Constants.HandleMessageResult
handleActiveSource(HdmiCecMessage message)463     protected int handleActiveSource(HdmiCecMessage message) {
464         assertRunOnServiceThread();
465         int logicalAddress = message.getSource();
466         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
467         HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress);
468         if (info == null) {
469             if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) {
470                 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress);
471                 mDelayedMessageBuffer.add(message);
472             }
473         } else if (isInputReady(info.getId())
474                 || info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
475             mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress,
476                     HdmiControlManager.POWER_STATUS_ON);
477             ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
478             ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType());
479         } else {
480             HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId());
481             mDelayedMessageBuffer.add(message);
482         }
483         return Constants.HANDLED;
484     }
485 
486     @Override
487     @ServiceThreadOnly
488     @Constants.HandleMessageResult
handleStandby(HdmiCecMessage message)489     protected int handleStandby(HdmiCecMessage message) {
490         assertRunOnServiceThread();
491 
492         // Ignore <Standby> from non-active source device.
493         if (getActiveSource().logicalAddress != message.getSource()) {
494             Slog.d(TAG, "<Standby> was not sent by the current active source, ignoring."
495                     + " Current active source has logical address "
496                     + getActiveSource().logicalAddress);
497             return Constants.HANDLED;
498         }
499         return super.handleStandby(message);
500     }
501 
502     @Override
503     @ServiceThreadOnly
504     @Constants.HandleMessageResult
handleInactiveSource(HdmiCecMessage message)505     protected int handleInactiveSource(HdmiCecMessage message) {
506         assertRunOnServiceThread();
507         // Seq #10
508 
509         // Ignore <Inactive Source> from non-active source device.
510         if (getActiveSource().logicalAddress != message.getSource()) {
511             return Constants.HANDLED;
512         }
513         if (isProhibitMode()) {
514             return Constants.HANDLED;
515         }
516         int portId = getPrevPortId();
517         if (portId != Constants.INVALID_PORT_ID) {
518             // TODO: Do this only if TV is not showing multiview like PIP/PAP.
519 
520             HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo(
521                     message.getSource());
522             if (inactiveSource == null) {
523                 return Constants.HANDLED;
524             }
525             if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
526                 return Constants.HANDLED;
527             }
528             // TODO: Switch the TV freeze mode off
529 
530             doManualPortSwitching(portId, null);
531             setPrevPortId(Constants.INVALID_PORT_ID);
532         } else {
533             // No HDMI port to switch to was found. Notify the input change listers to
534             // switch to the lastly shown internal input.
535             getActiveSource().invalidate();
536             setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
537             mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE);
538         }
539         return Constants.HANDLED;
540     }
541 
542     @Override
543     @ServiceThreadOnly
544     @Constants.HandleMessageResult
handleRequestActiveSource(HdmiCecMessage message)545     protected int handleRequestActiveSource(HdmiCecMessage message) {
546         assertRunOnServiceThread();
547         // Seq #19
548         if (getDeviceInfo().getLogicalAddress() == getActiveSource().logicalAddress) {
549             mService.sendCecCommand(
550                     HdmiCecMessageBuilder.buildActiveSource(
551                             getDeviceInfo().getLogicalAddress(), getActivePath()));
552         }
553         return Constants.HANDLED;
554     }
555 
556     @Override
557     @ServiceThreadOnly
558     @Constants.HandleMessageResult
handleGetMenuLanguage(HdmiCecMessage message)559     protected int handleGetMenuLanguage(HdmiCecMessage message) {
560         assertRunOnServiceThread();
561         if (!broadcastMenuLanguage(mService.getLanguage())) {
562             Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
563         }
564         return Constants.HANDLED;
565     }
566 
567     @ServiceThreadOnly
broadcastMenuLanguage(String language)568     boolean broadcastMenuLanguage(String language) {
569         assertRunOnServiceThread();
570         HdmiCecMessage command =
571                 HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
572                         getDeviceInfo().getLogicalAddress(), language);
573         if (command != null) {
574             mService.sendCecCommand(command);
575             return true;
576         }
577         return false;
578     }
579 
580     @Override
581     @Constants.HandleMessageResult
handleReportPhysicalAddress(HdmiCecMessage message)582     protected int handleReportPhysicalAddress(HdmiCecMessage message) {
583         super.handleReportPhysicalAddress(message);
584         int path = HdmiUtils.twoBytesToInt(message.getParams());
585         int address = message.getSource();
586         int type = message.getParams()[2];
587 
588         if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) {
589             handleNewDeviceAtTheTailOfActivePath(path);
590         }
591         startNewDeviceAction(ActiveSource.of(address, path), type);
592         return Constants.HANDLED;
593     }
594 
595     @Override
596     @Constants.HandleMessageResult
handleTimerStatus(HdmiCecMessage message)597     protected int handleTimerStatus(HdmiCecMessage message) {
598         // Do nothing.
599         return Constants.HANDLED;
600     }
601 
602     @Override
603     @Constants.HandleMessageResult
handleRecordStatus(HdmiCecMessage message)604     protected int handleRecordStatus(HdmiCecMessage message) {
605         // Do nothing.
606         return Constants.HANDLED;
607     }
608 
startNewDeviceAction(ActiveSource activeSource, int deviceType)609     void startNewDeviceAction(ActiveSource activeSource, int deviceType) {
610         for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
611             // If there is new device action which has the same logical address and path
612             // ignore new request.
613             // NewDeviceAction is created whenever it receives <Report Physical Address>.
614             // And there is a chance starting NewDeviceAction for the same source.
615             // Usually, new device sends <Report Physical Address> when it's plugged
616             // in. However, TV can detect a new device from HotPlugDetectionAction,
617             // which sends <Give Physical Address> to the source for newly detected
618             // device.
619             if (action.isActionOf(activeSource)) {
620                 return;
621             }
622         }
623 
624         addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
625                 activeSource.physicalAddress, deviceType));
626     }
627 
handleNewDeviceAtTheTailOfActivePath(int path)628     private boolean handleNewDeviceAtTheTailOfActivePath(int path) {
629         // Seq #22
630         if (isTailOfActivePath(path, getActivePath())) {
631             int newPath = mService.portIdToPath(getActivePortId());
632             setActivePath(newPath);
633             startRoutingControl(getActivePath(), newPath, null);
634             return true;
635         }
636         return false;
637     }
638 
639     /**
640      * Whether the given path is located in the tail of current active path.
641      *
642      * @param path to be tested
643      * @param activePath current active path
644      * @return true if the given path is located in the tail of current active path; otherwise,
645      *         false
646      */
isTailOfActivePath(int path, int activePath)647     static boolean isTailOfActivePath(int path, int activePath) {
648         // If active routing path is internal source, return false.
649         if (activePath == 0) {
650             return false;
651         }
652         for (int i = 12; i >= 0; i -= 4) {
653             int curActivePath = (activePath >> i) & 0xF;
654             if (curActivePath == 0) {
655                 return true;
656             } else {
657                 int curPath = (path >> i) & 0xF;
658                 if (curPath != curActivePath) {
659                     return false;
660                 }
661             }
662         }
663         return false;
664     }
665 
666     @Override
667     @ServiceThreadOnly
668     @Constants.HandleMessageResult
handleRoutingChange(HdmiCecMessage message)669     protected int handleRoutingChange(HdmiCecMessage message) {
670         assertRunOnServiceThread();
671         // Seq #21
672         byte[] params = message.getParams();
673         int currentPath = HdmiUtils.twoBytesToInt(params);
674         if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
675             getActiveSource().invalidate();
676             removeAction(RoutingControlAction.class);
677             int newPath = HdmiUtils.twoBytesToInt(params, 2);
678             addAndStartAction(new RoutingControlAction(this, newPath, null));
679         }
680         return Constants.HANDLED;
681     }
682 
683     @Override
684     @ServiceThreadOnly
685     @Constants.HandleMessageResult
handleReportAudioStatus(HdmiCecMessage message)686     protected int handleReportAudioStatus(HdmiCecMessage message) {
687         assertRunOnServiceThread();
688         if (mService.getHdmiCecVolumeControl()
689                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
690             return Constants.ABORT_REFUSED;
691         }
692 
693         boolean mute = HdmiUtils.isAudioStatusMute(message);
694         int volume = HdmiUtils.getAudioStatusVolume(message);
695         setAudioStatus(mute, volume);
696         return Constants.HANDLED;
697     }
698 
699     @Override
700     @ServiceThreadOnly
701     @Constants.HandleMessageResult
handleTextViewOn(HdmiCecMessage message)702     protected int handleTextViewOn(HdmiCecMessage message) {
703         assertRunOnServiceThread();
704 
705         // Note that if the device is in sleep mode, the <Text View On> (and <Image View On>)
706         // command won't be handled here in most cases. A dedicated microcontroller should be in
707         // charge while the Android system is in sleep mode, and the command doesn't need to be
708         // passed up to this service.
709         // The only situations where the command reaches this handler are
710         // 1. if sleep mode is implemented in such a way that Android system is not really put to
711         // standby mode but only the display is set to blank. Then the command leads to
712         // turning on the display by the invocation of PowerManager.wakeUp().
713         // 2. if the device is in dream mode, not sleep mode. Then this command leads to
714         // waking up the device from dream mode by the invocation of PowerManager.wakeUp().
715         if (getAutoWakeup()) {
716             mService.wakeUp();
717         }
718         return Constants.HANDLED;
719     }
720 
721     @Override
722     @ServiceThreadOnly
723     @Constants.HandleMessageResult
handleImageViewOn(HdmiCecMessage message)724     protected int handleImageViewOn(HdmiCecMessage message) {
725         assertRunOnServiceThread();
726         // Currently, it's the same as <Text View On>.
727         return handleTextViewOn(message);
728     }
729 
730     @ServiceThreadOnly
launchDeviceDiscovery()731     private void launchDeviceDiscovery() {
732         assertRunOnServiceThread();
733         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
734                 new DeviceDiscoveryCallback() {
735                     @Override
736                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
737                         for (HdmiDeviceInfo info : deviceInfos) {
738                             mService.getHdmiCecNetwork().addCecDevice(info);
739                         }
740 
741                         mSelectRequestBuffer.process();
742                         resetSelectRequestBuffer();
743 
744                         List<HotplugDetectionAction> hotplugActions
745                                 = getActions(HotplugDetectionAction.class);
746                         if (hotplugActions.isEmpty()) {
747                             addAndStartAction(
748                                     new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
749                         }
750 
751                         List<PowerStatusMonitorAction> powerStatusActions
752                                 = getActions(PowerStatusMonitorAction.class);
753                         if (powerStatusActions.isEmpty()) {
754                             addAndStartAction(
755                                     new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
756                         }
757 
758                         HdmiDeviceInfo avr = getAvrDeviceInfo();
759                         if (avr != null) {
760                             onNewAvrAdded(avr);
761                         } else {
762                             setSystemAudioMode(false);
763                         }
764                     }
765                 });
766         addAndStartAction(action);
767     }
768 
769     @ServiceThreadOnly
onNewAvrAdded(HdmiDeviceInfo avr)770     void onNewAvrAdded(HdmiDeviceInfo avr) {
771         assertRunOnServiceThread();
772         addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
773         if (!isDirectConnectAddress(avr.getPhysicalAddress())) {
774             startArcAction(false);
775         } else if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
776                 && !hasAction(SetArcTransmissionStateAction.class)) {
777             startArcAction(true);
778         }
779     }
780 
781     @ServiceThreadOnly
782     // Seq #32
changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback)783     void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
784         assertRunOnServiceThread();
785         if (!mService.isCecControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
786             setSystemAudioMode(false);
787             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
788             return;
789         }
790         HdmiDeviceInfo avr = getAvrDeviceInfo();
791         if (avr == null) {
792             setSystemAudioMode(false);
793             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
794             return;
795         }
796 
797         addAndStartAction(
798                 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
799     }
800 
801     // # Seq 25
setSystemAudioMode(boolean on)802     void setSystemAudioMode(boolean on) {
803         if (!isSystemAudioControlFeatureEnabled() && on) {
804             HdmiLogger.debug("Cannot turn on system audio mode "
805                     + "because the System Audio Control feature is disabled.");
806             return;
807         }
808         HdmiLogger.debug("System Audio Mode change[old:%b new:%b]",
809                 mService.isSystemAudioActivated(), on);
810         updateAudioManagerForSystemAudio(on);
811         synchronized (mLock) {
812             if (mService.isSystemAudioActivated() != on) {
813                 mService.setSystemAudioActivated(on);
814                 mService.announceSystemAudioModeChange(on);
815             }
816             if (on && !mArcEstablished) {
817                 startArcAction(true);
818             } else if (!on) {
819                 startArcAction(false);
820             }
821         }
822     }
823 
updateAudioManagerForSystemAudio(boolean on)824     private void updateAudioManagerForSystemAudio(boolean on) {
825         int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
826         HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
827     }
828 
isSystemAudioActivated()829     boolean isSystemAudioActivated() {
830         if (!hasSystemAudioDevice()) {
831             return false;
832         }
833         return mService.isSystemAudioActivated();
834     }
835 
836     @ServiceThreadOnly
setSystemAudioControlFeatureEnabled(boolean enabled)837     void setSystemAudioControlFeatureEnabled(boolean enabled) {
838         assertRunOnServiceThread();
839         synchronized (mLock) {
840             mSystemAudioControlFeatureEnabled = enabled;
841         }
842         if (hasSystemAudioDevice()) {
843             changeSystemAudioMode(enabled, null);
844         }
845     }
846 
isSystemAudioControlFeatureEnabled()847     boolean isSystemAudioControlFeatureEnabled() {
848         synchronized (mLock) {
849             return mSystemAudioControlFeatureEnabled;
850         }
851     }
852 
853     @ServiceThreadOnly
enableArc(List<byte[]> supportedSads)854     void enableArc(List<byte[]> supportedSads) {
855         assertRunOnServiceThread();
856         HdmiLogger.debug("Set Arc Status[old:%b new:true]", mArcEstablished);
857 
858         enableAudioReturnChannel(true);
859         notifyArcStatusToAudioService(true, supportedSads);
860         mArcEstablished = true;
861     }
862 
863     @ServiceThreadOnly
disableArc()864     void disableArc() {
865         assertRunOnServiceThread();
866         HdmiLogger.debug("Set Arc Status[old:%b new:false]", mArcEstablished);
867 
868         enableAudioReturnChannel(false);
869         notifyArcStatusToAudioService(false, new ArrayList<>());
870         mArcEstablished = false;
871     }
872 
873     /**
874      * Switch hardware ARC circuit in the system.
875      */
876     @ServiceThreadOnly
enableAudioReturnChannel(boolean enabled)877     void enableAudioReturnChannel(boolean enabled) {
878         assertRunOnServiceThread();
879         HdmiDeviceInfo avr = getAvrDeviceInfo();
880         if (avr != null && avr.getPortId() != Constants.INVALID_PORT_ID) {
881             mService.enableAudioReturnChannel(avr.getPortId(), enabled);
882         }
883     }
884 
885     @ServiceThreadOnly
isConnected(int portId)886     boolean isConnected(int portId) {
887         assertRunOnServiceThread();
888         return mService.isConnected(portId);
889     }
890 
notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads)891     private void notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads) {
892         // Note that we don't set any name to ARC.
893         AudioDeviceAttributes attributes = new AudioDeviceAttributes(
894                 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_ARC, "", "",
895                 new ArrayList<AudioProfile>(), supportedSads.stream()
896                 .map(sad -> new AudioDescriptor(AudioDescriptor.STANDARD_EDID,
897                         AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, sad))
898                 .collect(Collectors.toList()));
899         mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0);
900     }
901 
902     /**
903      * Returns true if ARC is currently established on a certain port.
904      */
905     @ServiceThreadOnly
isArcEstablished()906     boolean isArcEstablished() {
907         assertRunOnServiceThread();
908         if (mArcEstablished) {
909             for (int i = 0; i < mArcFeatureEnabled.size(); i++) {
910                 if (mArcFeatureEnabled.valueAt(i)) return true;
911             }
912         }
913         return false;
914     }
915 
916     @ServiceThreadOnly
changeArcFeatureEnabled(int portId, boolean enabled)917     void changeArcFeatureEnabled(int portId, boolean enabled) {
918         assertRunOnServiceThread();
919         if (mArcFeatureEnabled.get(portId) == enabled) {
920             return;
921         }
922         mArcFeatureEnabled.put(portId, enabled);
923         HdmiDeviceInfo avr = getAvrDeviceInfo();
924         if (avr == null || avr.getPortId() != portId) {
925             return;
926         }
927         if (enabled && !mArcEstablished) {
928             startArcAction(true);
929         } else if (!enabled && mArcEstablished) {
930             startArcAction(false);
931         }
932     }
933 
934     @ServiceThreadOnly
isArcFeatureEnabled(int portId)935     boolean isArcFeatureEnabled(int portId) {
936         assertRunOnServiceThread();
937         return mArcFeatureEnabled.get(portId);
938     }
939 
940     @ServiceThreadOnly
startArcAction(boolean enabled)941     void startArcAction(boolean enabled) {
942         startArcAction(enabled, null);
943     }
944 
945     @ServiceThreadOnly
startArcAction(boolean enabled, IHdmiControlCallback callback)946     void startArcAction(boolean enabled, IHdmiControlCallback callback) {
947         assertRunOnServiceThread();
948         HdmiDeviceInfo info = getAvrDeviceInfo();
949         if (info == null) {
950             Slog.w(TAG, "Failed to start arc action; No AVR device.");
951             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
952             return;
953         }
954         if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
955             Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
956             if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
957                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
958             }
959             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
960             return;
961         }
962         if (enabled && mService.earcBlocksArcConnection()) {
963             Slog.i(TAG,
964                     "ARC connection blocked because eARC connection is established or being "
965                             + "established.");
966             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
967             return;
968         }
969 
970         // Terminate opposite action and create an action with callback.
971         if (enabled) {
972             removeAction(RequestArcTerminationAction.class);
973             if (hasAction(RequestArcInitiationAction.class)) {
974                 RequestArcInitiationAction existingInitiationAction =
975                         getActions(RequestArcInitiationAction.class).get(0);
976                 existingInitiationAction.addCallback(callback);
977             } else {
978                 addAndStartAction(
979                         new RequestArcInitiationAction(this, info.getLogicalAddress(), callback));
980             }
981         } else {
982             removeAction(RequestArcInitiationAction.class);
983             if (hasAction(RequestArcTerminationAction.class)) {
984                 RequestArcTerminationAction existingTerminationAction =
985                         getActions(RequestArcTerminationAction.class).get(0);
986                 existingTerminationAction.addCallback(callback);
987             } else {
988                 addAndStartAction(
989                         new RequestArcTerminationAction(this, info.getLogicalAddress(), callback));
990             }
991         }
992     }
993 
isDirectConnectAddress(int physicalAddress)994     private boolean isDirectConnectAddress(int physicalAddress) {
995         return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
996     }
997 
setAudioStatus(boolean mute, int volume)998     void setAudioStatus(boolean mute, int volume) {
999         if (!isSystemAudioActivated() || mService.getHdmiCecVolumeControl()
1000                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
1001             return;
1002         }
1003         synchronized (mLock) {
1004             mSystemAudioMute = mute;
1005             mSystemAudioVolume = volume;
1006             displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED,
1007                     mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume);
1008         }
1009     }
1010 
1011     @ServiceThreadOnly
changeVolume(int curVolume, int delta, int maxVolume)1012     void changeVolume(int curVolume, int delta, int maxVolume) {
1013         assertRunOnServiceThread();
1014         if (getAvrDeviceInfo() == null) {
1015             // On initialization process, getAvrDeviceInfo() may return null and cause exception
1016             return;
1017         }
1018         if (delta == 0 || !isSystemAudioActivated() || mService.getHdmiCecVolumeControl()
1019                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
1020             return;
1021         }
1022 
1023         int targetVolume = curVolume + delta;
1024         int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
1025         synchronized (mLock) {
1026             // If new volume is the same as current system audio volume, just ignore it.
1027             // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
1028             if (cecVolume == mSystemAudioVolume) {
1029                 // Update tv volume with system volume value.
1030                 mService.setAudioStatus(false,
1031                         VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
1032                 return;
1033             }
1034         }
1035 
1036         List<VolumeControlAction> actions = getActions(VolumeControlAction.class);
1037         if (actions.isEmpty()) {
1038             addAndStartAction(new VolumeControlAction(this,
1039                     getAvrDeviceInfo().getLogicalAddress(), delta > 0));
1040         } else {
1041             actions.get(0).handleVolumeChange(delta > 0);
1042         }
1043     }
1044 
1045     @ServiceThreadOnly
changeMute(boolean mute)1046     void changeMute(boolean mute) {
1047         assertRunOnServiceThread();
1048         if (getAvrDeviceInfo() == null || mService.getHdmiCecVolumeControl()
1049                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
1050             // On initialization process, getAvrDeviceInfo() may return null and cause exception
1051             return;
1052         }
1053         HdmiLogger.debug("[A]:Change mute:%b", mute);
1054         synchronized (mLock) {
1055             if (mSystemAudioMute == mute) {
1056                 HdmiLogger.debug("No need to change mute.");
1057                 return;
1058             }
1059         }
1060         if (!isSystemAudioActivated()) {
1061             HdmiLogger.debug("[A]:System audio is not activated.");
1062             return;
1063         }
1064 
1065         // Remove existing volume action.
1066         removeAction(VolumeControlAction.class);
1067         sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(),
1068                 HdmiCecKeycode.getMuteKey(mute));
1069     }
1070 
1071     @Override
1072     @ServiceThreadOnly
1073     @Constants.HandleMessageResult
handleInitiateArc(HdmiCecMessage message)1074     protected int handleInitiateArc(HdmiCecMessage message) {
1075         assertRunOnServiceThread();
1076 
1077         if (mService.earcBlocksArcConnection()) {
1078             Slog.i(TAG,
1079                     "ARC connection blocked because eARC connection is established or being "
1080                             + "established.");
1081             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1082         }
1083 
1084         if (!canStartArcUpdateAction(message.getSource(), true)) {
1085             HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo();
1086             if (avrDeviceInfo == null) {
1087                 // AVR may not have been discovered yet. Delay the message processing.
1088                 mDelayedMessageBuffer.add(message);
1089                 return Constants.HANDLED;
1090             }
1091             if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) {
1092                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
1093             }
1094             return Constants.ABORT_REFUSED;
1095         }
1096 
1097         // In case where <Initiate Arc> is started by <Request ARC Initiation>, this message is
1098         // handled in RequestArcInitiationAction as well.
1099         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1100                 message.getSource(), true);
1101         addAndStartAction(action);
1102         return Constants.HANDLED;
1103     }
1104 
canStartArcUpdateAction(int avrAddress, boolean enabled)1105     private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
1106         HdmiDeviceInfo avr = getAvrDeviceInfo();
1107         if (avr != null
1108                 && (avrAddress == avr.getLogicalAddress())
1109                 && isConnectedToArcPort(avr.getPhysicalAddress())) {
1110             if (enabled) {
1111                 return isConnected(avr.getPortId())
1112                     && isArcFeatureEnabled(avr.getPortId())
1113                     && isDirectConnectAddress(avr.getPhysicalAddress());
1114             } else {
1115                 return true;
1116             }
1117         } else {
1118             return false;
1119         }
1120     }
1121 
1122     @Override
1123     @ServiceThreadOnly
1124     @Constants.HandleMessageResult
handleTerminateArc(HdmiCecMessage message)1125     protected int handleTerminateArc(HdmiCecMessage message) {
1126         assertRunOnServiceThread();
1127         if (mService .isPowerStandbyOrTransient()) {
1128             disableArc();
1129             return Constants.HANDLED;
1130         }
1131         // Do not check ARC configuration since the AVR might have been already removed.
1132         // In case where <Terminate Arc> is started by <Request ARC Termination>, this
1133         // message is handled in RequestArcTerminationAction as well.
1134         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
1135                 message.getSource(), false);
1136         addAndStartAction(action);
1137         return Constants.HANDLED;
1138     }
1139 
1140     @Override
1141     @ServiceThreadOnly
1142     @Constants.HandleMessageResult
handleSetSystemAudioMode(HdmiCecMessage message)1143     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
1144         assertRunOnServiceThread();
1145         boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message);
1146         if (!isMessageForSystemAudio(message)) {
1147             if (getAvrDeviceInfo() == null) {
1148                 // AVR may not have been discovered yet. Delay the message processing.
1149                 mDelayedMessageBuffer.add(message);
1150             } else {
1151                 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message);
1152                 return Constants.ABORT_REFUSED;
1153             }
1154         } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) {
1155             HdmiLogger.debug("Ignoring <Set System Audio Mode> message "
1156                     + "because the System Audio Control feature is disabled: %s", message);
1157             return Constants.ABORT_REFUSED;
1158         }
1159         removeAction(SystemAudioAutoInitiationAction.class);
1160         SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
1161                 message.getSource(), systemAudioStatus, null);
1162         addAndStartAction(action);
1163         return Constants.HANDLED;
1164     }
1165 
1166     @Override
1167     @ServiceThreadOnly
1168     @Constants.HandleMessageResult
handleSystemAudioModeStatus(HdmiCecMessage message)1169     protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
1170         assertRunOnServiceThread();
1171         if (!isMessageForSystemAudio(message)) {
1172             HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message);
1173             // Ignore this message.
1174             return Constants.HANDLED;
1175         }
1176         boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled();
1177         boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message);
1178         // Set System Audio Mode according to TV's settings.
1179         // Handle <System Audio Mode Status> here only when
1180         // SystemAudioAutoInitiationAction timeout
1181         HdmiDeviceInfo avr = getAvrDeviceInfo();
1182         if (avr == null) {
1183             setSystemAudioMode(false);
1184         } else if (avrSystemAudioMode != tvSystemAudioMode) {
1185             addAndStartAction(new SystemAudioActionFromTv(this, avr.getLogicalAddress(),
1186                     tvSystemAudioMode, null));
1187         } else {
1188             setSystemAudioMode(tvSystemAudioMode);
1189         }
1190 
1191         return Constants.HANDLED;
1192     }
1193 
1194     // Seq #53
1195     @Override
1196     @ServiceThreadOnly
1197     @Constants.HandleMessageResult
handleRecordTvScreen(HdmiCecMessage message)1198     protected int handleRecordTvScreen(HdmiCecMessage message) {
1199         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
1200         if (!actions.isEmpty()) {
1201             // Assumes only one OneTouchRecordAction.
1202             OneTouchRecordAction action = actions.get(0);
1203             if (action.getRecorderAddress() != message.getSource()) {
1204                 announceOneTouchRecordResult(
1205                         message.getSource(),
1206                         HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
1207             }
1208             // The default behavior of <Record TV Screen> is replying <Feature Abort> with
1209             // "Cannot provide source".
1210             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1211         }
1212 
1213         int recorderAddress = message.getSource();
1214         byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
1215         return startOneTouchRecord(recorderAddress, recordSource);
1216     }
1217 
1218     @Override
1219     @Constants.HandleMessageResult
handleTimerClearedStatus(HdmiCecMessage message)1220     protected int handleTimerClearedStatus(HdmiCecMessage message) {
1221         byte[] params = message.getParams();
1222         int timerClearedStatusData = params[0] & 0xFF;
1223         announceTimerRecordingResult(message.getSource(), timerClearedStatusData);
1224         return Constants.HANDLED;
1225     }
1226 
1227     @Override
1228     @Constants.HandleMessageResult
handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message)1229     protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
1230         // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't
1231         // handle it when System Audio Mode is enabled.
1232         if (mService.isSystemAudioActivated()) {
1233             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1234         } else {
1235             int audioVolumeLevel = message.getAudioVolumeLevel();
1236             if (audioVolumeLevel >= AudioStatus.MIN_VOLUME
1237                     && audioVolumeLevel <= AudioStatus.MAX_VOLUME) {
1238                 mService.setStreamMusicVolume(audioVolumeLevel, 0);
1239             }
1240             return Constants.HANDLED;
1241         }
1242     }
1243 
announceOneTouchRecordResult(int recorderAddress, int result)1244     void announceOneTouchRecordResult(int recorderAddress, int result) {
1245         mService.invokeOneTouchRecordResult(recorderAddress, result);
1246     }
1247 
announceTimerRecordingResult(int recorderAddress, int result)1248     void announceTimerRecordingResult(int recorderAddress, int result) {
1249         mService.invokeTimerRecordingResult(recorderAddress, result);
1250     }
1251 
announceClearTimerRecordingResult(int recorderAddress, int result)1252     void announceClearTimerRecordingResult(int recorderAddress, int result) {
1253         mService.invokeClearTimerRecordingResult(recorderAddress, result);
1254     }
1255 
isMessageForSystemAudio(HdmiCecMessage message)1256     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
1257         return mService.isCecControlEnabled()
1258                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1259                 && (message.getDestination() == Constants.ADDR_TV
1260                         || message.getDestination() == Constants.ADDR_BROADCAST)
1261                 && getAvrDeviceInfo() != null;
1262     }
1263 
1264     @Nullable
1265     @ServiceThreadOnly
getAvrDeviceInfo()1266     HdmiDeviceInfo getAvrDeviceInfo() {
1267         assertRunOnServiceThread();
1268         return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1269     }
1270 
hasSystemAudioDevice()1271     boolean hasSystemAudioDevice() {
1272         return getSafeAvrDeviceInfo() != null;
1273     }
1274 
1275     @Nullable
getSafeAvrDeviceInfo()1276     HdmiDeviceInfo getSafeAvrDeviceInfo() {
1277         return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
1278     }
1279 
1280     @ServiceThreadOnly
handleRemoveActiveRoutingPath(int path)1281     void handleRemoveActiveRoutingPath(int path) {
1282         assertRunOnServiceThread();
1283         // Seq #23
1284         if (isTailOfActivePath(path, getActivePath())) {
1285             int newPath = mService.portIdToPath(getActivePortId());
1286             startRoutingControl(getActivePath(), newPath, null);
1287         }
1288     }
1289 
1290     /**
1291      * Launch routing control process.
1292      *
1293      * @param routingForBootup true if routing control is initiated due to One Touch Play
1294      *        or TV power on
1295      */
1296     @ServiceThreadOnly
launchRoutingControl(boolean routingForBootup)1297     void launchRoutingControl(boolean routingForBootup) {
1298         assertRunOnServiceThread();
1299         // Seq #24
1300         if (getActivePortId() != Constants.INVALID_PORT_ID
1301                 && getActivePortId() != Constants.CEC_SWITCH_HOME) {
1302             if (!routingForBootup && !isProhibitMode()) {
1303                 int newPath = mService.portIdToPath(getActivePortId());
1304                 setActivePath(newPath);
1305                 startRoutingControl(getActivePath(), newPath, null);
1306             }
1307         } else {
1308             int activePath = mService.getPhysicalAddress();
1309             setActivePath(activePath);
1310             if (!routingForBootup
1311                     && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
1312                 mService.sendCecCommand(
1313                         HdmiCecMessageBuilder.buildActiveSource(
1314                                 getDeviceInfo().getLogicalAddress(), activePath));
1315                 updateActiveSource(getDeviceInfo().getLogicalAddress(), activePath,
1316                         "HdmiCecLocalDeviceTv#launchRoutingControl()");
1317             }
1318         }
1319     }
1320 
1321     @Override
1322     @ServiceThreadOnly
onHotplug(int portId, boolean connected)1323     void onHotplug(int portId, boolean connected) {
1324         assertRunOnServiceThread();
1325 
1326         if (!connected) {
1327             mService.getHdmiCecNetwork().removeCecSwitches(portId);
1328         }
1329 
1330         if (!mService.isEarcEnabled() || !mService.isEarcSupported()) {
1331             HdmiDeviceInfo avr = getAvrDeviceInfo();
1332             if (avr != null
1333                     && portId == avr.getPortId()
1334                     && isConnectedToArcPort(avr.getPhysicalAddress())) {
1335                 HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
1336                 if (connected) {
1337                     if (mArcEstablished) {
1338                         enableAudioReturnChannel(true);
1339                     }
1340                 } else {
1341                     enableAudioReturnChannel(false);
1342                 }
1343             }
1344         }
1345 
1346         // Tv device will have permanent HotplugDetectionAction.
1347         List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1348         if (!hotplugActions.isEmpty()) {
1349             // Note that hotplug action is single action running on a machine.
1350             // "pollAllDevicesNow" cleans up timer and start poll action immediately.
1351             // It covers seq #40, #43.
1352             hotplugActions.get(0).pollAllDevicesNow();
1353         }
1354     }
1355 
1356     @ServiceThreadOnly
getAutoWakeup()1357     boolean getAutoWakeup() {
1358         assertRunOnServiceThread();
1359         return mService.getHdmiCecConfig().getIntValue(
1360                   HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY)
1361                     == HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED;
1362     }
1363 
1364     @Override
1365     @ServiceThreadOnly
disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)1366     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1367         assertRunOnServiceThread();
1368         mService.unregisterTvInputCallback(mTvInputCallback);
1369         // Remove any repeated working actions.
1370         // HotplugDetectionAction will be reinstated during the wake up process.
1371         // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1372         //     LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
1373         removeAction(DeviceDiscoveryAction.class);
1374         removeAction(HotplugDetectionAction.class);
1375         removeAction(PowerStatusMonitorAction.class);
1376         // Remove recording actions.
1377         removeAction(OneTouchRecordAction.class);
1378         removeAction(TimerRecordingAction.class);
1379         removeAction(NewDeviceAction.class);
1380         // Remove pending actions.
1381         removeAction(RequestActiveSourceAction.class);
1382 
1383         // Keep SAM enabled if eARC is enabled, unless we're going to Standby.
1384         if (initiatedByCec || !mService.isEarcEnabled()){
1385             disableSystemAudioIfExist();
1386         }
1387         disableArcIfExist();
1388 
1389         super.disableDevice(initiatedByCec, callback);
1390         clearDeviceInfoList();
1391         getActiveSource().invalidate();
1392         setActivePath(Constants.INVALID_PHYSICAL_ADDRESS);
1393         checkIfPendingActionsCleared();
1394     }
1395 
1396     @ServiceThreadOnly
disableSystemAudioIfExist()1397     private void disableSystemAudioIfExist() {
1398         assertRunOnServiceThread();
1399         if (getAvrDeviceInfo() == null) {
1400             return;
1401         }
1402 
1403         // Seq #31.
1404         removeAction(SystemAudioActionFromAvr.class);
1405         removeAction(SystemAudioActionFromTv.class);
1406         removeAction(SystemAudioAutoInitiationAction.class);
1407         removeAction(VolumeControlAction.class);
1408 
1409         if (!mService.isCecControlEnabled()) {
1410             setSystemAudioMode(false);
1411         }
1412     }
1413 
1414     @ServiceThreadOnly
forceDisableArcOnAllPins()1415     private void forceDisableArcOnAllPins() {
1416         List<HdmiPortInfo> ports = mService.getPortInfo();
1417         for (HdmiPortInfo port : ports) {
1418             if (isArcFeatureEnabled(port.getId())) {
1419                 mService.enableAudioReturnChannel(port.getId(), false);
1420             }
1421         }
1422     }
1423 
1424     @ServiceThreadOnly
disableArcIfExist()1425     private void disableArcIfExist() {
1426         assertRunOnServiceThread();
1427         HdmiDeviceInfo avr = getAvrDeviceInfo();
1428         if (avr == null) {
1429             return;
1430         }
1431 
1432         // Seq #44.
1433         removeAllRunningArcAction();
1434         if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
1435             addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1436         }
1437 
1438         // Disable ARC Pin earlier, prevent the case where AVR doesn't send <Terminate ARC> in time
1439         forceDisableArcOnAllPins();
1440     }
1441 
1442     @ServiceThreadOnly
removeAllRunningArcAction()1443     private void removeAllRunningArcAction() {
1444         // Running or pending actions make TV fail to broadcast <Standby> to connected devices
1445         removeAction(RequestArcTerminationAction.class);
1446         removeAction(RequestArcInitiationAction.class);
1447         removeAction(SetArcTransmissionStateAction.class);
1448     }
1449 
1450     @Override
1451     @ServiceThreadOnly
onStandby(boolean initiatedByCec, int standbyAction, StandbyCompletedCallback callback)1452     protected void onStandby(boolean initiatedByCec, int standbyAction,
1453             StandbyCompletedCallback callback) {
1454         assertRunOnServiceThread();
1455         // Seq #11
1456         if (!mService.isCecControlEnabled()) {
1457             invokeStandbyCompletedCallback(callback);
1458             return;
1459         }
1460         boolean sendStandbyOnSleep =
1461                 mService.getHdmiCecConfig().getIntValue(
1462                     HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
1463                         == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
1464         if (!initiatedByCec && sendStandbyOnSleep) {
1465             mService.sendCecCommand(
1466                     HdmiCecMessageBuilder.buildStandby(
1467                             getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST),
1468                     new SendMessageCallback() {
1469                         @Override
1470                         public void onSendCompleted(int error) {
1471                             invokeStandbyCompletedCallback(callback);
1472                         }
1473                     });
1474         } else {
1475             invokeStandbyCompletedCallback(callback);
1476         }
1477     }
1478 
isProhibitMode()1479     boolean isProhibitMode() {
1480         return mService.isProhibitMode();
1481     }
1482 
isPowerStandbyOrTransient()1483     boolean isPowerStandbyOrTransient() {
1484         return mService.isPowerStandbyOrTransient();
1485     }
1486 
1487     @ServiceThreadOnly
displayOsd(int messageId)1488     void displayOsd(int messageId) {
1489         assertRunOnServiceThread();
1490         mService.displayOsd(messageId);
1491     }
1492 
1493     @ServiceThreadOnly
displayOsd(int messageId, int extra)1494     void displayOsd(int messageId, int extra) {
1495         assertRunOnServiceThread();
1496         mService.displayOsd(messageId, extra);
1497     }
1498 
1499     // Seq #54 and #55
1500     @ServiceThreadOnly
1501     @Constants.HandleMessageResult
startOneTouchRecord(int recorderAddress, byte[] recordSource)1502     int startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1503         assertRunOnServiceThread();
1504         if (!mService.isCecControlEnabled()) {
1505             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1506             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1507             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1508         }
1509 
1510         if (!checkRecorder(recorderAddress)) {
1511             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1512             announceOneTouchRecordResult(recorderAddress,
1513                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1514             return Constants.ABORT_NOT_IN_CORRECT_MODE;
1515         }
1516 
1517         if (!checkRecordSource(recordSource)) {
1518             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1519             announceOneTouchRecordResult(recorderAddress,
1520                     ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
1521             return Constants.ABORT_CANNOT_PROVIDE_SOURCE;
1522         }
1523 
1524         addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1525         Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1526                 + Arrays.toString(recordSource));
1527         return Constants.HANDLED;
1528     }
1529 
1530     @ServiceThreadOnly
stopOneTouchRecord(int recorderAddress)1531     void stopOneTouchRecord(int recorderAddress) {
1532         assertRunOnServiceThread();
1533         if (!mService.isCecControlEnabled()) {
1534             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
1535             announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED);
1536             return;
1537         }
1538 
1539         if (!checkRecorder(recorderAddress)) {
1540             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1541             announceOneTouchRecordResult(recorderAddress,
1542                     ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
1543             return;
1544         }
1545 
1546         // Remove one touch record action so that other one touch record can be started.
1547         removeAction(OneTouchRecordAction.class);
1548         mService.sendCecCommand(
1549                 HdmiCecMessageBuilder.buildRecordOff(
1550                         getDeviceInfo().getLogicalAddress(), recorderAddress));
1551         Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1552     }
1553 
checkRecorder(int recorderAddress)1554     private boolean checkRecorder(int recorderAddress) {
1555         HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress);
1556         return (device != null) && (HdmiUtils.isEligibleAddressForDevice(
1557                 HdmiDeviceInfo.DEVICE_RECORDER, recorderAddress));
1558     }
1559 
checkRecordSource(byte[] recordSource)1560     private boolean checkRecordSource(byte[] recordSource) {
1561         return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1562     }
1563 
1564     @ServiceThreadOnly
startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1565     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1566         assertRunOnServiceThread();
1567         if (!mService.isCecControlEnabled()) {
1568             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1569             announceTimerRecordingResult(recorderAddress,
1570                     TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
1571             return;
1572         }
1573 
1574         if (!checkRecorder(recorderAddress)) {
1575             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1576             announceTimerRecordingResult(recorderAddress,
1577                     TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
1578             return;
1579         }
1580 
1581         if (!checkTimerRecordingSource(sourceType, recordSource)) {
1582             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1583             announceTimerRecordingResult(
1584                     recorderAddress,
1585                     TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
1586             return;
1587         }
1588 
1589         addAndStartAction(
1590                 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1591         Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1592                 + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1593     }
1594 
checkTimerRecordingSource(int sourceType, byte[] recordSource)1595     private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1596         return (recordSource != null)
1597                 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
1598     }
1599 
1600     @ServiceThreadOnly
clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1601     void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1602         assertRunOnServiceThread();
1603         if (!mService.isCecControlEnabled()) {
1604             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
1605             announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE);
1606             return;
1607         }
1608 
1609         if (!checkRecorder(recorderAddress)) {
1610             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1611             announceClearTimerRecordingResult(recorderAddress,
1612                     CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
1613             return;
1614         }
1615 
1616         if (!checkTimerRecordingSource(sourceType, recordSource)) {
1617             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1618             announceClearTimerRecordingResult(recorderAddress,
1619                     CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1620             return;
1621         }
1622 
1623         sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1624     }
1625 
sendClearTimerMessage(final int recorderAddress, int sourceType, byte[] recordSource)1626     private void sendClearTimerMessage(final int recorderAddress, int sourceType,
1627             byte[] recordSource) {
1628         HdmiCecMessage message = null;
1629         switch (sourceType) {
1630             case TIMER_RECORDING_TYPE_DIGITAL:
1631                 message =
1632                         HdmiCecMessageBuilder.buildClearDigitalTimer(
1633                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1634                 break;
1635             case TIMER_RECORDING_TYPE_ANALOGUE:
1636                 message =
1637                         HdmiCecMessageBuilder.buildClearAnalogueTimer(
1638                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1639                 break;
1640             case TIMER_RECORDING_TYPE_EXTERNAL:
1641                 message =
1642                         HdmiCecMessageBuilder.buildClearExternalTimer(
1643                                 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource);
1644                 break;
1645             default:
1646                 Slog.w(TAG, "Invalid source type:" + recorderAddress);
1647                 announceClearTimerRecordingResult(recorderAddress,
1648                         CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1649                 return;
1650 
1651         }
1652         mService.sendCecCommand(message, new SendMessageCallback() {
1653             @Override
1654             public void onSendCompleted(int error) {
1655                 if (error != SendMessageResult.SUCCESS) {
1656                     announceClearTimerRecordingResult(recorderAddress,
1657                             CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
1658                 }
1659             }
1660         });
1661     }
1662 
1663     @Override
1664     @Constants.HandleMessageResult
handleMenuStatus(HdmiCecMessage message)1665     protected int handleMenuStatus(HdmiCecMessage message) {
1666         // Do nothing and just return true not to prevent from responding <Feature Abort>.
1667         return Constants.HANDLED;
1668     }
1669 
1670     @Constants.RcProfile
1671     @Override
getRcProfile()1672     protected int getRcProfile() {
1673         return Constants.RC_PROFILE_TV;
1674     }
1675 
1676     @Override
getRcFeatures()1677     protected List<Integer> getRcFeatures() {
1678         List<Integer> features = new ArrayList<>();
1679         @HdmiControlManager.RcProfileTv int profile = mService.getHdmiCecConfig().getIntValue(
1680                         HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV);
1681         features.add(profile);
1682         return features;
1683     }
1684 
1685     @Override
computeDeviceFeatures()1686     protected DeviceFeatures computeDeviceFeatures() {
1687         boolean hasArcPort = false;
1688         List<HdmiPortInfo> ports = mService.getPortInfo();
1689         for (HdmiPortInfo port : ports) {
1690             if (isArcFeatureEnabled(port.getId())) {
1691                 hasArcPort = true;
1692                 break;
1693             }
1694         }
1695 
1696         return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
1697                 .setRecordTvScreenSupport(FEATURE_SUPPORTED)
1698                 .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
1699                 .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
1700                 .build();
1701     }
1702 
1703     @Override
sendStandby(int deviceId)1704     protected void sendStandby(int deviceId) {
1705         HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId);
1706         if (targetDevice == null) {
1707             return;
1708         }
1709         int targetAddress = targetDevice.getLogicalAddress();
1710         mService.sendCecCommand(
1711                 HdmiCecMessageBuilder.buildStandby(
1712                         getDeviceInfo().getLogicalAddress(), targetAddress));
1713     }
1714 
1715     @ServiceThreadOnly
processAllDelayedMessages()1716     void processAllDelayedMessages() {
1717         assertRunOnServiceThread();
1718         mDelayedMessageBuffer.processAllMessages();
1719     }
1720 
1721     @ServiceThreadOnly
processDelayedMessages(int address)1722     void processDelayedMessages(int address) {
1723         assertRunOnServiceThread();
1724         mDelayedMessageBuffer.processMessagesForDevice(address);
1725     }
1726 
1727     @ServiceThreadOnly
processDelayedActiveSource(int address)1728     void processDelayedActiveSource(int address) {
1729         assertRunOnServiceThread();
1730         mDelayedMessageBuffer.processActiveSource(address);
1731     }
1732 
1733     @Override
dump(final IndentingPrintWriter pw)1734     protected void dump(final IndentingPrintWriter pw) {
1735         super.dump(pw);
1736         pw.println("mArcEstablished: " + mArcEstablished);
1737         pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1738         pw.println("mSystemAudioMute: " + mSystemAudioMute);
1739         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
1740         pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
1741         pw.println("mPrevPortId: " + mPrevPortId);
1742     }
1743 }
1744