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