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 com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT;
20 
21 import android.annotation.CallSuper;
22 import android.hardware.hdmi.DeviceFeatures;
23 import android.hardware.hdmi.HdmiControlManager;
24 import android.hardware.hdmi.HdmiDeviceInfo;
25 import android.hardware.hdmi.IHdmiControlCallback;
26 import android.hardware.input.InputManager;
27 import android.hardware.input.InputManagerGlobal;
28 import android.hardware.tv.cec.V1_0.Result;
29 import android.hardware.tv.cec.V1_0.SendMessageResult;
30 import android.media.AudioManager;
31 import android.os.Handler;
32 import android.os.Looper;
33 import android.os.Message;
34 import android.os.RemoteException;
35 import android.os.SystemClock;
36 import android.util.Slog;
37 import android.view.InputDevice;
38 import android.view.KeyCharacterMap;
39 import android.view.KeyEvent;
40 
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.util.IndentingPrintWriter;
44 import com.android.server.hdmi.Constants.LocalActivePort;
45 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
46 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
47 
48 import java.text.SimpleDateFormat;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.Date;
52 import java.util.Iterator;
53 import java.util.List;
54 import java.util.concurrent.ArrayBlockingQueue;
55 
56 /**
57  * Class that models a logical CEC device hosted in this system. Handles initialization, CEC
58  * commands that call for actions customized per device type.
59  */
60 abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
61     private static final String TAG = "HdmiCecLocalDevice";
62 
63     private static final int MAX_HDMI_ACTIVE_SOURCE_HISTORY = 10;
64     private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1;
65     private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2;
66     // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
67     // When it expires, we can assume <User Control Release> is received.
68     private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
69 
70     protected int mPreferredAddress;
71     @GuardedBy("mLock")
72     private HdmiDeviceInfo mDeviceInfo;
73     protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
74     protected int mLastKeyRepeatCount = 0;
75 
76     HdmiCecStandbyModeHandler mStandbyHandler;
77 
78     // Stores recent changes to the active source in the CEC network.
79     private final ArrayBlockingQueue<HdmiCecController.Dumpable> mActiveSourceHistory =
80             new ArrayBlockingQueue<>(MAX_HDMI_ACTIVE_SOURCE_HISTORY);
81 
82     static class ActiveSource {
83         int logicalAddress;
84         int physicalAddress;
85 
ActiveSource()86         public ActiveSource() {
87             invalidate();
88         }
89 
ActiveSource(int logical, int physical)90         public ActiveSource(int logical, int physical) {
91             logicalAddress = logical;
92             physicalAddress = physical;
93         }
94 
of(ActiveSource source)95         public static ActiveSource of(ActiveSource source) {
96             return new ActiveSource(source.logicalAddress, source.physicalAddress);
97         }
98 
of(int logical, int physical)99         public static ActiveSource of(int logical, int physical) {
100             return new ActiveSource(logical, physical);
101         }
102 
isValid()103         public boolean isValid() {
104             return HdmiUtils.isValidAddress(logicalAddress);
105         }
106 
invalidate()107         public void invalidate() {
108             logicalAddress = Constants.ADDR_INVALID;
109             physicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
110         }
111 
equals(int logical, int physical)112         public boolean equals(int logical, int physical) {
113             return logicalAddress == logical && physicalAddress == physical;
114         }
115 
116         @Override
equals(Object obj)117         public boolean equals(Object obj) {
118             if (obj instanceof ActiveSource) {
119                 ActiveSource that = (ActiveSource) obj;
120                 return that.logicalAddress == logicalAddress
121                         && that.physicalAddress == physicalAddress;
122             }
123             return false;
124         }
125 
126         @Override
hashCode()127         public int hashCode() {
128             return logicalAddress * 29 + physicalAddress;
129         }
130 
131         @Override
toString()132         public String toString() {
133             StringBuilder s = new StringBuilder();
134             String logicalAddressString =
135                     (logicalAddress == Constants.ADDR_INVALID)
136                             ? "invalid"
137                             : String.format("0x%02x", logicalAddress);
138             s.append("(").append(logicalAddressString);
139             String physicalAddressString =
140                     (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS)
141                             ? "invalid"
142                             : String.format("0x%04x", physicalAddress);
143             s.append(", ").append(physicalAddressString).append(")");
144             return s.toString();
145         }
146     }
147 
148     // Active routing path. Physical address of the active source but not all the time, such as
149     // when the new active source does not claim itself to be one. Note that we don't keep
150     // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}.
151     @GuardedBy("mLock")
152     private int mActiveRoutingPath;
153 
154     protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
155 
156     // A collection of FeatureAction.
157     // Note that access to this collection should happen in service thread.
158     @VisibleForTesting
159     final ArrayList<HdmiCecFeatureAction> mActions = new ArrayList<>();
160 
161     private final Handler mHandler =
162             new Handler() {
163                 @Override
164                 public void handleMessage(Message msg) {
165                     switch (msg.what) {
166                         case MSG_DISABLE_DEVICE_TIMEOUT:
167                             handleDisableDeviceTimeout();
168                             break;
169                         case MSG_USER_CONTROL_RELEASE_TIMEOUT:
170                             handleUserControlReleased();
171                             break;
172                     }
173                 }
174             };
175 
176     /**
177      * A callback interface used by local devices use to indicate that they have finished their part
178      * of the standby process.
179      */
180     interface StandbyCompletedCallback {
onStandbyCompleted()181         void onStandbyCompleted();
182     }
183 
184     /**
185      * A callback interface to get notified when all pending action is cleared. It can be called
186      * when timeout happened.
187      */
188     interface PendingActionClearedCallback {
onCleared(HdmiCecLocalDevice device)189         void onCleared(HdmiCecLocalDevice device);
190     }
191 
192     protected PendingActionClearedCallback mPendingActionClearedCallback;
193 
HdmiCecLocalDevice(HdmiControlService service, int deviceType)194     protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
195         super(service, deviceType);
196     }
197 
198     // Factory method that returns HdmiCecLocalDevice of corresponding type.
create(HdmiControlService service, int deviceType)199     static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) {
200         switch (deviceType) {
201             case HdmiDeviceInfo.DEVICE_TV:
202                 return new HdmiCecLocalDeviceTv(service);
203             case HdmiDeviceInfo.DEVICE_PLAYBACK:
204                 return new HdmiCecLocalDevicePlayback(service);
205             case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
206                 return new HdmiCecLocalDeviceAudioSystem(service);
207             default:
208                 return null;
209         }
210     }
211 
212     @ServiceThreadOnly
init()213     void init() {
214         assertRunOnServiceThread();
215         mPreferredAddress = getPreferredAddress();
216         if (mHandler.hasMessages(MSG_DISABLE_DEVICE_TIMEOUT)) {
217             // Remove and trigger the queued message for clearing all actions when going to standby.
218             // This is necessary because the device may wake up before the message is triggered.
219             mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
220             handleDisableDeviceTimeout();
221         }
222         mPendingActionClearedCallback = null;
223     }
224 
225     /** Called once a logical address of the local device is allocated. */
onAddressAllocated(int logicalAddress, int reason)226     protected abstract void onAddressAllocated(int logicalAddress, int reason);
227 
228     /** Get the preferred logical address from system properties. */
getPreferredAddress()229     protected abstract int getPreferredAddress();
230 
231     /** Set the preferred logical address to system properties. */
setPreferredAddress(int addr)232     protected abstract void setPreferredAddress(int addr);
233 
234     /**
235      * Returns true if the TV input associated with the CEC device is ready to accept further
236      * processing such as input switching.
237      *
238      * <p>This is used to buffer certain CEC commands and process it later if the input is not ready
239      * yet. For other types of local devices(non-TV), this method returns true by default to let the
240      * commands be processed right away.
241      */
isInputReady(int deviceId)242     protected boolean isInputReady(int deviceId) {
243         return true;
244     }
245 
246     /**
247      * Returns true if the local device allows the system to be put to standby.
248      *
249      * <p>The default implementation returns true.
250      */
canGoToStandby()251     protected boolean canGoToStandby() {
252         return true;
253     }
254 
255     /**
256      * Dispatch incoming message.
257      *
258      * @param message incoming message
259      * @return true if consumed a message; otherwise, return false.
260      */
261     @ServiceThreadOnly
262     @VisibleForTesting
263     @Constants.HandleMessageResult
dispatchMessage(HdmiCecMessage message)264     protected int dispatchMessage(HdmiCecMessage message) {
265         assertRunOnServiceThread();
266         int dest = message.getDestination();
267         if (dest != mDeviceInfo.getLogicalAddress() && dest != Constants.ADDR_BROADCAST) {
268             return Constants.NOT_HANDLED;
269         }
270         if (mService.isPowerStandby()
271                 && !mService.isWakeUpMessageReceived()
272                 && mStandbyHandler.handleCommand(message)) {
273             return Constants.HANDLED;
274         }
275         // Cache incoming message if it is included in the list of cacheable opcodes.
276         mCecMessageCache.cacheMessage(message);
277         return onMessage(message);
278     }
279 
280     @ServiceThreadOnly
281     @VisibleForTesting
isAlreadyActiveSource(HdmiDeviceInfo targetDevice, int targetAddress, IHdmiControlCallback callback)282     protected boolean isAlreadyActiveSource(HdmiDeviceInfo targetDevice, int targetAddress,
283             IHdmiControlCallback callback) {
284         ActiveSource active = getActiveSource();
285         if (targetDevice.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON
286                 && active.isValid()
287                 && targetAddress == active.logicalAddress) {
288             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
289             return true;
290         }
291         return false;
292     }
293 
294     // Clear all device info.
295     @ServiceThreadOnly
clearDeviceInfoList()296     void clearDeviceInfoList() {
297         assertRunOnServiceThread();
298         mService.getHdmiCecNetwork().clearDeviceList();
299     }
300 
301     @ServiceThreadOnly
302     @Constants.HandleMessageResult
onMessage(HdmiCecMessage message)303     protected final int onMessage(HdmiCecMessage message) {
304         assertRunOnServiceThread();
305         if (dispatchMessageToAction(message)) {
306             return Constants.HANDLED;
307         }
308 
309         // If a message type has its own class, all valid messages of that type
310         // will be represented by an instance of that class.
311         if (message instanceof SetAudioVolumeLevelMessage) {
312             return handleSetAudioVolumeLevel((SetAudioVolumeLevelMessage) message);
313         }
314 
315         switch (message.getOpcode()) {
316             case Constants.MESSAGE_ACTIVE_SOURCE:
317                 return handleActiveSource(message);
318             case Constants.MESSAGE_INACTIVE_SOURCE:
319                 return handleInactiveSource(message);
320             case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
321                 return handleRequestActiveSource(message);
322             case Constants.MESSAGE_GET_MENU_LANGUAGE:
323                 return handleGetMenuLanguage(message);
324             case Constants.MESSAGE_SET_MENU_LANGUAGE:
325                 return handleSetMenuLanguage(message);
326             case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS:
327                 return handleGivePhysicalAddress(message);
328             case Constants.MESSAGE_GIVE_OSD_NAME:
329                 return handleGiveOsdName(message);
330             case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
331                 return handleGiveDeviceVendorId(message);
332             case Constants.MESSAGE_CEC_VERSION:
333                 return handleCecVersion();
334             case Constants.MESSAGE_GET_CEC_VERSION:
335                 return handleGetCecVersion(message);
336             case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
337                 return handleReportPhysicalAddress(message);
338             case Constants.MESSAGE_ROUTING_CHANGE:
339                 return handleRoutingChange(message);
340             case Constants.MESSAGE_ROUTING_INFORMATION:
341                 return handleRoutingInformation(message);
342             case Constants.MESSAGE_REQUEST_ARC_INITIATION:
343                 return handleRequestArcInitiate(message);
344             case Constants.MESSAGE_REQUEST_ARC_TERMINATION:
345                 return handleRequestArcTermination(message);
346             case Constants.MESSAGE_INITIATE_ARC:
347                 return handleInitiateArc(message);
348             case Constants.MESSAGE_TERMINATE_ARC:
349                 return handleTerminateArc(message);
350             case Constants.MESSAGE_REPORT_ARC_INITIATED:
351                 return handleReportArcInitiate(message);
352             case Constants.MESSAGE_REPORT_ARC_TERMINATED:
353                 return handleReportArcTermination(message);
354             case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
355                 return handleSystemAudioModeRequest(message);
356             case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
357                 return handleSetSystemAudioMode(message);
358             case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
359                 return handleSystemAudioModeStatus(message);
360             case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
361                 return handleGiveSystemAudioModeStatus(message);
362             case Constants.MESSAGE_GIVE_AUDIO_STATUS:
363                 return handleGiveAudioStatus(message);
364             case Constants.MESSAGE_REPORT_AUDIO_STATUS:
365                 return handleReportAudioStatus(message);
366             case Constants.MESSAGE_STANDBY:
367                 return handleStandby(message);
368             case Constants.MESSAGE_TEXT_VIEW_ON:
369                 return handleTextViewOn(message);
370             case Constants.MESSAGE_IMAGE_VIEW_ON:
371                 return handleImageViewOn(message);
372             case Constants.MESSAGE_USER_CONTROL_PRESSED:
373                 return handleUserControlPressed(message);
374             case Constants.MESSAGE_USER_CONTROL_RELEASED:
375                 return handleUserControlReleased();
376             case Constants.MESSAGE_SET_STREAM_PATH:
377                 return handleSetStreamPath(message);
378             case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
379                 return handleGiveDevicePowerStatus(message);
380             case Constants.MESSAGE_MENU_REQUEST:
381                 return handleMenuRequest(message);
382             case Constants.MESSAGE_MENU_STATUS:
383                 return handleMenuStatus(message);
384             case Constants.MESSAGE_VENDOR_COMMAND:
385                 return handleVendorCommand(message);
386             case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID:
387                 return handleVendorCommandWithId(message);
388             case Constants.MESSAGE_SET_OSD_NAME:
389                 return handleSetOsdName(message);
390             case Constants.MESSAGE_RECORD_TV_SCREEN:
391                 return handleRecordTvScreen(message);
392             case Constants.MESSAGE_TIMER_CLEARED_STATUS:
393                 return handleTimerClearedStatus(message);
394             case Constants.MESSAGE_REPORT_POWER_STATUS:
395                 return handleReportPowerStatus(message);
396             case Constants.MESSAGE_TIMER_STATUS:
397                 return handleTimerStatus(message);
398             case Constants.MESSAGE_RECORD_STATUS:
399                 return handleRecordStatus(message);
400             case Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR:
401                 return handleRequestShortAudioDescriptor(message);
402             case Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR:
403                 return handleReportShortAudioDescriptor(message);
404             case Constants.MESSAGE_GIVE_FEATURES:
405                 return handleGiveFeatures(message);
406             default:
407                 return Constants.NOT_HANDLED;
408         }
409     }
410 
411     @ServiceThreadOnly
dispatchMessageToAction(HdmiCecMessage message)412     private boolean dispatchMessageToAction(HdmiCecMessage message) {
413         assertRunOnServiceThread();
414         boolean processed = false;
415         // Use copied action list in that processCommand may remove itself.
416         for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) {
417             // Iterates all actions to check whether incoming message is consumed.
418             boolean result = action.processCommand(message);
419             processed = processed || result;
420         }
421         return processed;
422     }
423 
424     @ServiceThreadOnly
425     @Constants.HandleMessageResult
handleGivePhysicalAddress(HdmiCecMessage message)426     protected int handleGivePhysicalAddress(HdmiCecMessage message) {
427         assertRunOnServiceThread();
428         int physicalAddress = mService.getPhysicalAddress();
429         if (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) {
430             mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE);
431         } else {
432             HdmiCecMessage cecMessage =
433                     HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
434                             mDeviceInfo.getLogicalAddress(), physicalAddress, mDeviceType);
435             mService.sendCecCommand(cecMessage);
436         }
437         return Constants.HANDLED;
438     }
439 
440     @ServiceThreadOnly
441     @Constants.HandleMessageResult
handleGiveDeviceVendorId(HdmiCecMessage message)442     protected int handleGiveDeviceVendorId(HdmiCecMessage message) {
443         assertRunOnServiceThread();
444         int vendorId = mService.getVendorId();
445         if (vendorId == Result.FAILURE_UNKNOWN) {
446             mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE);
447         } else {
448             HdmiCecMessage cecMessage =
449                     HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
450                             mDeviceInfo.getLogicalAddress(), vendorId);
451             mService.sendCecCommand(cecMessage);
452         }
453         return Constants.HANDLED;
454     }
455 
456     @ServiceThreadOnly
457     @Constants.HandleMessageResult
handleGetCecVersion(HdmiCecMessage message)458     protected int handleGetCecVersion(HdmiCecMessage message) {
459         assertRunOnServiceThread();
460         int version = mService.getCecVersion();
461         HdmiCecMessage cecMessage =
462                 HdmiCecMessageBuilder.buildCecVersion(
463                         message.getDestination(), message.getSource(), version);
464         mService.sendCecCommand(cecMessage);
465         return Constants.HANDLED;
466     }
467 
468     @ServiceThreadOnly
469     @Constants.HandleMessageResult
handleCecVersion()470     protected int handleCecVersion() {
471         assertRunOnServiceThread();
472 
473         // Return true to avoid <Feature Abort> responses. Cec Version is tracked in HdmiCecNetwork.
474         return Constants.HANDLED;
475     }
476 
477     @ServiceThreadOnly
478     @Constants.HandleMessageResult
handleActiveSource(HdmiCecMessage message)479     protected int handleActiveSource(HdmiCecMessage message) {
480         return Constants.NOT_HANDLED;
481     }
482 
483     @ServiceThreadOnly
484     @Constants.HandleMessageResult
handleInactiveSource(HdmiCecMessage message)485     protected int handleInactiveSource(HdmiCecMessage message) {
486         return Constants.NOT_HANDLED;
487     }
488 
489     @ServiceThreadOnly
490     @Constants.HandleMessageResult
handleRequestActiveSource(HdmiCecMessage message)491     protected int handleRequestActiveSource(HdmiCecMessage message) {
492         return Constants.NOT_HANDLED;
493     }
494 
495     @ServiceThreadOnly
496     @Constants.HandleMessageResult
handleGetMenuLanguage(HdmiCecMessage message)497     protected int handleGetMenuLanguage(HdmiCecMessage message) {
498         assertRunOnServiceThread();
499         Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
500         return Constants.NOT_HANDLED;
501     }
502 
503     @ServiceThreadOnly
504     @Constants.HandleMessageResult
handleSetMenuLanguage(HdmiCecMessage message)505     protected int handleSetMenuLanguage(HdmiCecMessage message) {
506         assertRunOnServiceThread();
507         Slog.w(TAG, "Only Playback device can handle <Set Menu Language>:" + message.toString());
508         return Constants.NOT_HANDLED;
509     }
510 
511     @ServiceThreadOnly
512     @Constants.HandleMessageResult
handleGiveOsdName(HdmiCecMessage message)513     protected int handleGiveOsdName(HdmiCecMessage message) {
514         assertRunOnServiceThread();
515         // Note that since this method is called after logical address allocation is done,
516         // mDeviceInfo should not be null.
517         buildAndSendSetOsdName(message.getSource());
518         return Constants.HANDLED;
519     }
520 
buildAndSendSetOsdName(int dest)521     protected void buildAndSendSetOsdName(int dest) {
522         HdmiCecMessage cecMessage =
523                 HdmiCecMessageBuilder.buildSetOsdNameCommand(
524                         mDeviceInfo.getLogicalAddress(), dest, mDeviceInfo.getDisplayName());
525         if (cecMessage != null) {
526             mService.sendCecCommand(cecMessage, new SendMessageCallback() {
527                 @Override
528                 public void onSendCompleted(int error) {
529                     if (error != SendMessageResult.SUCCESS) {
530                         HdmiLogger.debug("Failed to send cec command " + cecMessage);
531                     }
532                 }
533             });
534         } else {
535             Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
536         }
537     }
538 
539     // Audio System device with no Playback device type
540     // needs to refactor this function if it's also a switch
541     @Constants.HandleMessageResult
handleRoutingChange(HdmiCecMessage message)542     protected int handleRoutingChange(HdmiCecMessage message) {
543         return Constants.NOT_HANDLED;
544     }
545 
546     // Audio System device with no Playback device type
547     // needs to refactor this function if it's also a switch
548     @Constants.HandleMessageResult
handleRoutingInformation(HdmiCecMessage message)549     protected int handleRoutingInformation(HdmiCecMessage message) {
550         return Constants.NOT_HANDLED;
551     }
552 
553     @CallSuper
554     @Constants.HandleMessageResult
handleReportPhysicalAddress(HdmiCecMessage message)555     protected int handleReportPhysicalAddress(HdmiCecMessage message) {
556         // <Report Physical Address>  is also handled in HdmiCecNetwork to update the local network
557         // state
558 
559         int address = message.getSource();
560 
561         // Ignore if [Device Discovery Action] is going on.
562         if (hasAction(DeviceDiscoveryAction.class)) {
563             Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
564             return Constants.HANDLED;
565         }
566 
567         HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address);
568         // If no non-default display name is available for the device, request the devices OSD name.
569         // On TV devices, the OSD name is queried in NewDeviceAction instead.
570         if (!mService.isTvDevice() && cecDeviceInfo != null
571                 && cecDeviceInfo.getDisplayName().equals(
572                 HdmiUtils.getDefaultDeviceName(address))) {
573             mService.sendCecCommand(
574                     HdmiCecMessageBuilder.buildGiveOsdNameCommand(
575                             mDeviceInfo.getLogicalAddress(), address));
576         }
577 
578         return Constants.HANDLED;
579     }
580 
581     @Constants.HandleMessageResult
handleSystemAudioModeStatus(HdmiCecMessage message)582     protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
583         return Constants.NOT_HANDLED;
584     }
585 
586     @Constants.HandleMessageResult
handleGiveSystemAudioModeStatus(HdmiCecMessage message)587     protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
588         return Constants.NOT_HANDLED;
589     }
590 
591     @Constants.HandleMessageResult
handleSetSystemAudioMode(HdmiCecMessage message)592     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
593         return Constants.NOT_HANDLED;
594     }
595 
596     @Constants.HandleMessageResult
handleSystemAudioModeRequest(HdmiCecMessage message)597     protected int handleSystemAudioModeRequest(HdmiCecMessage message) {
598         return Constants.NOT_HANDLED;
599     }
600 
601     @Constants.HandleMessageResult
handleTerminateArc(HdmiCecMessage message)602     protected int handleTerminateArc(HdmiCecMessage message) {
603         return Constants.NOT_HANDLED;
604     }
605 
606     @Constants.HandleMessageResult
handleInitiateArc(HdmiCecMessage message)607     protected int handleInitiateArc(HdmiCecMessage message) {
608         return Constants.NOT_HANDLED;
609     }
610 
611     @Constants.HandleMessageResult
handleRequestArcInitiate(HdmiCecMessage message)612     protected int handleRequestArcInitiate(HdmiCecMessage message) {
613         return Constants.NOT_HANDLED;
614     }
615 
616     @Constants.HandleMessageResult
handleRequestArcTermination(HdmiCecMessage message)617     protected int handleRequestArcTermination(HdmiCecMessage message) {
618         return Constants.NOT_HANDLED;
619     }
620 
621     @Constants.HandleMessageResult
handleReportArcInitiate(HdmiCecMessage message)622     protected int handleReportArcInitiate(HdmiCecMessage message) {
623         return Constants.NOT_HANDLED;
624     }
625 
626     @Constants.HandleMessageResult
handleReportArcTermination(HdmiCecMessage message)627     protected int handleReportArcTermination(HdmiCecMessage message) {
628         return Constants.NOT_HANDLED;
629     }
630 
631     @Constants.HandleMessageResult
handleReportAudioStatus(HdmiCecMessage message)632     protected int handleReportAudioStatus(HdmiCecMessage message) {
633         return Constants.NOT_HANDLED;
634     }
635 
636     @Constants.HandleMessageResult
handleGiveAudioStatus(HdmiCecMessage message)637     protected int handleGiveAudioStatus(HdmiCecMessage message) {
638         return Constants.NOT_HANDLED;
639     }
640 
641     @Constants.HandleMessageResult
handleRequestShortAudioDescriptor(HdmiCecMessage message)642     protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) {
643         return Constants.NOT_HANDLED;
644     }
645 
646     @Constants.HandleMessageResult
handleReportShortAudioDescriptor(HdmiCecMessage message)647     protected int handleReportShortAudioDescriptor(HdmiCecMessage message) {
648         return Constants.NOT_HANDLED;
649     }
650 
651     @Constants.HandleMessageResult
handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message)652     protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
653         return Constants.NOT_HANDLED;
654     }
655 
656     /**
657      * Called after logical address allocation is finished, allowing a local device to react to
658      * messages in the buffer before they are processed. This method may be used to cancel deferred
659      * actions.
660      */
preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages)661     protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {}
662 
663     @Constants.RcProfile
getRcProfile()664     protected abstract int getRcProfile();
665 
getRcFeatures()666     protected abstract List<Integer> getRcFeatures();
667 
668     /**
669      * Computes the set of supported device features. To update local state with changes in
670      * the set of supported device features, use {@link #getDeviceFeatures} instead.
671      */
computeDeviceFeatures()672     protected DeviceFeatures computeDeviceFeatures() {
673         return DeviceFeatures.NO_FEATURES_SUPPORTED;
674     }
675 
676     /**
677      * Computes the set of supported device features, and updates local state to match.
678      */
updateDeviceFeatures()679     private void updateDeviceFeatures() {
680         setDeviceInfo(getDeviceInfo().toBuilder()
681                 .setDeviceFeatures(computeDeviceFeatures())
682                 .build());
683     }
684 
685     /**
686      * Computes and returns the set of supported device features. Updates local state to match.
687      */
getDeviceFeatures()688     protected final DeviceFeatures getDeviceFeatures() {
689         updateDeviceFeatures();
690         return getDeviceInfo().getDeviceFeatures();
691     }
692 
693     @Constants.HandleMessageResult
handleGiveFeatures(HdmiCecMessage message)694     protected int handleGiveFeatures(HdmiCecMessage message) {
695         if (mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
696             return Constants.ABORT_UNRECOGNIZED_OPCODE;
697         }
698 
699         reportFeatures();
700         return Constants.HANDLED;
701     }
702 
reportFeatures()703     protected void reportFeatures() {
704         List<Integer> localDeviceTypes = new ArrayList<>();
705         for (HdmiCecLocalDevice localDevice : mService.getAllCecLocalDevices()) {
706             localDeviceTypes.add(localDevice.mDeviceType);
707         }
708 
709 
710         int rcProfile = getRcProfile();
711         List<Integer> rcFeatures = getRcFeatures();
712         DeviceFeatures deviceFeatures = getDeviceFeatures();
713 
714 
715         int logicalAddress;
716         synchronized (mLock) {
717             logicalAddress = mDeviceInfo.getLogicalAddress();
718         }
719 
720         mService.sendCecCommand(
721                 ReportFeaturesMessage.build(
722                         logicalAddress,
723                         mService.getCecVersion(),
724                         localDeviceTypes,
725                         rcProfile,
726                         rcFeatures,
727                         deviceFeatures));
728     }
729 
730     @ServiceThreadOnly
731     @Constants.HandleMessageResult
handleStandby(HdmiCecMessage message)732     protected int handleStandby(HdmiCecMessage message) {
733         assertRunOnServiceThread();
734         // Seq #12
735         if (mService.isCecControlEnabled()
736                 && !mService.isProhibitMode()
737                 && mService.isPowerOnOrTransient()) {
738             mService.standby();
739             return Constants.HANDLED;
740         }
741         return Constants.ABORT_NOT_IN_CORRECT_MODE;
742     }
743 
744     @ServiceThreadOnly
745     @Constants.HandleMessageResult
handleUserControlPressed(HdmiCecMessage message)746     protected int handleUserControlPressed(HdmiCecMessage message) {
747         assertRunOnServiceThread();
748         mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
749         if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
750             mService.standby();
751             return Constants.HANDLED;
752         } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
753             mService.wakeUp();
754             return Constants.HANDLED;
755         } else if (mService.getHdmiCecVolumeControl()
756                 == HdmiControlManager.VOLUME_CONTROL_DISABLED && isVolumeOrMuteCommand(
757                 message)) {
758             return Constants.ABORT_REFUSED;
759         }
760 
761         if (isPowerOffOrToggleCommand(message) || isPowerOnOrToggleCommand(message)) {
762             // Power commands should already be handled above. Don't continue and convert the CEC
763             // keycode to Android keycode.
764             // Do not <Feature Abort> as the local device should already be in the correct power
765             // state.
766             return Constants.HANDLED;
767         }
768 
769         final long downTime = SystemClock.uptimeMillis();
770         final byte[] params = message.getParams();
771         final int keycode = HdmiCecKeycode.cecKeycodeAndParamsToAndroidKey(params);
772         int keyRepeatCount = 0;
773         if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
774             if (keycode == mLastKeycode) {
775                 keyRepeatCount = mLastKeyRepeatCount + 1;
776             } else {
777                 injectKeyEvent(downTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
778             }
779         }
780         mLastKeycode = keycode;
781         mLastKeyRepeatCount = keyRepeatCount;
782 
783         if (keycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
784             injectKeyEvent(downTime, KeyEvent.ACTION_DOWN, keycode, keyRepeatCount);
785             mHandler.sendMessageDelayed(
786                     Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT),
787                     FOLLOWER_SAFETY_TIMEOUT);
788             return Constants.HANDLED;
789         } else if (params.length > 0) {
790             // Handle CEC UI commands that are not mapped to an Android keycode
791             return handleUnmappedCecKeycode(params[0]);
792         }
793 
794         return Constants.ABORT_INVALID_OPERAND;
795     }
796 
797     @ServiceThreadOnly
798     @Constants.HandleMessageResult
handleUnmappedCecKeycode(int cecKeycode)799     protected int handleUnmappedCecKeycode(int cecKeycode) {
800         if (cecKeycode == HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION) {
801             mService.getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC,
802                     AudioManager.ADJUST_MUTE, AudioManager.FLAG_SHOW_UI);
803             return Constants.HANDLED;
804         } else if (cecKeycode == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION) {
805             mService.getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC,
806                     AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_SHOW_UI);
807             return Constants.HANDLED;
808         }
809         return Constants.ABORT_INVALID_OPERAND;
810     }
811 
812     @ServiceThreadOnly
813     @Constants.HandleMessageResult
handleUserControlReleased()814     protected int handleUserControlReleased() {
815         assertRunOnServiceThread();
816         mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
817         mLastKeyRepeatCount = 0;
818         if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
819             final long upTime = SystemClock.uptimeMillis();
820             injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
821             mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
822         }
823         return Constants.HANDLED;
824     }
825 
injectKeyEvent(long time, int action, int keycode, int repeat)826     static void injectKeyEvent(long time, int action, int keycode, int repeat) {
827         KeyEvent keyEvent =
828                 KeyEvent.obtain(
829                         time,
830                         time,
831                         action,
832                         keycode,
833                         repeat,
834                         0,
835                         KeyCharacterMap.VIRTUAL_KEYBOARD,
836                         0,
837                         KeyEvent.FLAG_FROM_SYSTEM,
838                         InputDevice.SOURCE_HDMI,
839                         null);
840         InputManagerGlobal.getInstance()
841                 .injectInputEvent(keyEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
842         keyEvent.recycle();
843     }
844 
isPowerOnOrToggleCommand(HdmiCecMessage message)845     static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
846         byte[] params = message.getParams();
847         return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
848                 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
849                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION
850                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
851     }
852 
isPowerOffOrToggleCommand(HdmiCecMessage message)853     static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
854         byte[] params = message.getParams();
855         return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
856                 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
857                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
858     }
859 
isVolumeOrMuteCommand(HdmiCecMessage message)860     static boolean isVolumeOrMuteCommand(HdmiCecMessage message) {
861         byte[] params = message.getParams();
862         return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
863                 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN
864                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
865                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE
866                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION
867                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION);
868     }
869 
870     @Constants.HandleMessageResult
handleTextViewOn(HdmiCecMessage message)871     protected int handleTextViewOn(HdmiCecMessage message) {
872         return Constants.NOT_HANDLED;
873     }
874 
875     @Constants.HandleMessageResult
handleImageViewOn(HdmiCecMessage message)876     protected int handleImageViewOn(HdmiCecMessage message) {
877         return Constants.NOT_HANDLED;
878     }
879 
880     @Constants.HandleMessageResult
handleSetStreamPath(HdmiCecMessage message)881     protected int handleSetStreamPath(HdmiCecMessage message) {
882         return Constants.NOT_HANDLED;
883     }
884 
885     @Constants.HandleMessageResult
handleGiveDevicePowerStatus(HdmiCecMessage message)886     protected int handleGiveDevicePowerStatus(HdmiCecMessage message) {
887         mService.sendCecCommand(
888                 HdmiCecMessageBuilder.buildReportPowerStatus(
889                         mDeviceInfo.getLogicalAddress(),
890                         message.getSource(),
891                         mService.getPowerStatus()));
892         return Constants.HANDLED;
893     }
894 
895     @Constants.HandleMessageResult
handleMenuRequest(HdmiCecMessage message)896     protected int handleMenuRequest(HdmiCecMessage message) {
897         // Always report menu active to receive Remote Control.
898         mService.sendCecCommand(
899                 HdmiCecMessageBuilder.buildReportMenuStatus(
900                         mDeviceInfo.getLogicalAddress(),
901                         message.getSource(),
902                         Constants.MENU_STATE_ACTIVATED));
903         return Constants.HANDLED;
904     }
905 
906     @Constants.HandleMessageResult
handleMenuStatus(HdmiCecMessage message)907     protected int handleMenuStatus(HdmiCecMessage message) {
908         return Constants.NOT_HANDLED;
909     }
910 
911     @Constants.HandleMessageResult
handleVendorCommand(HdmiCecMessage message)912     protected int handleVendorCommand(HdmiCecMessage message) {
913         if (!mService.invokeVendorCommandListenersOnReceived(
914                 mDeviceType,
915                 message.getSource(),
916                 message.getDestination(),
917                 message.getParams(),
918                 false)) {
919             // Vendor command listener may not have been registered yet. Respond with
920             // <Feature Abort> [Refused] so that the sender can try again later.
921             return Constants.ABORT_REFUSED;
922         }
923         return Constants.HANDLED;
924     }
925 
926     @Constants.HandleMessageResult
handleVendorCommandWithId(HdmiCecMessage message)927     protected int handleVendorCommandWithId(HdmiCecMessage message) {
928         byte[] params = message.getParams();
929         int vendorId = HdmiUtils.threeBytesToInt(params);
930         if (message.getDestination() == Constants.ADDR_BROADCAST
931                 || message.getSource() == Constants.ADDR_UNREGISTERED) {
932             Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
933         } else if (!mService.invokeVendorCommandListenersOnReceived(
934                 mDeviceType, message.getSource(), message.getDestination(), params, true)) {
935             return Constants.ABORT_REFUSED;
936         }
937         return Constants.HANDLED;
938     }
939 
sendStandby(int deviceId)940     protected void sendStandby(int deviceId) {
941         // Do nothing.
942     }
943 
944     @Constants.HandleMessageResult
handleSetOsdName(HdmiCecMessage message)945     protected int handleSetOsdName(HdmiCecMessage message) {
946         // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state
947         return Constants.HANDLED;
948     }
949 
950     @Constants.HandleMessageResult
handleRecordTvScreen(HdmiCecMessage message)951     protected int handleRecordTvScreen(HdmiCecMessage message) {
952         return Constants.NOT_HANDLED;
953     }
954 
955     @Constants.HandleMessageResult
handleTimerClearedStatus(HdmiCecMessage message)956     protected int handleTimerClearedStatus(HdmiCecMessage message) {
957         return Constants.NOT_HANDLED;
958     }
959 
960     @Constants.HandleMessageResult
handleReportPowerStatus(HdmiCecMessage message)961     protected int handleReportPowerStatus(HdmiCecMessage message) {
962         // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state
963         return Constants.HANDLED;
964     }
965 
966     @Constants.HandleMessageResult
handleTimerStatus(HdmiCecMessage message)967     protected int handleTimerStatus(HdmiCecMessage message) {
968         return Constants.NOT_HANDLED;
969     }
970 
971     @Constants.HandleMessageResult
handleRecordStatus(HdmiCecMessage message)972     protected int handleRecordStatus(HdmiCecMessage message) {
973         return Constants.NOT_HANDLED;
974     }
975 
976     @ServiceThreadOnly
handleAddressAllocated( int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason)977     final void handleAddressAllocated(
978             int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason) {
979         assertRunOnServiceThread();
980         preprocessBufferedMessages(bufferedMessages);
981         mPreferredAddress = logicalAddress;
982         updateDeviceFeatures();
983         if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
984             reportFeatures();
985         }
986         onAddressAllocated(logicalAddress, reason);
987         setPreferredAddress(logicalAddress);
988     }
989 
getType()990     int getType() {
991         return mDeviceType;
992     }
993 
getDeviceInfo()994     HdmiDeviceInfo getDeviceInfo() {
995         synchronized (mLock) {
996             return mDeviceInfo;
997         }
998     }
999 
setDeviceInfo(HdmiDeviceInfo info)1000     void setDeviceInfo(HdmiDeviceInfo info) {
1001         synchronized (mLock) {
1002             mDeviceInfo = info;
1003         }
1004     }
1005 
1006     // Returns true if the logical address is same as the argument.
1007     @ServiceThreadOnly
isAddressOf(int addr)1008     boolean isAddressOf(int addr) {
1009         assertRunOnServiceThread();
1010         return addr == mDeviceInfo.getLogicalAddress();
1011     }
1012 
1013     @ServiceThreadOnly
addAndStartAction(final HdmiCecFeatureAction action)1014     void addAndStartAction(final HdmiCecFeatureAction action) {
1015         assertRunOnServiceThread();
1016         mActions.add(action);
1017         if (mService.isPowerStandby() || !mService.isAddressAllocated()) {
1018             if (action.getClass() == ResendCecCommandAction.class) {
1019                 Slog.i(TAG, "Not ready to start ResendCecCommandAction. "
1020                         + "This action is cancelled.");
1021                 removeAction(action);
1022                 return;
1023             }
1024             Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action);
1025             return;
1026         }
1027         action.start();
1028     }
1029 
1030     @ServiceThreadOnly
startNewAvbAudioStatusAction(int targetAddress)1031     void startNewAvbAudioStatusAction(int targetAddress) {
1032         assertRunOnServiceThread();
1033         removeAction(AbsoluteVolumeAudioStatusAction.class);
1034         addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
1035     }
1036 
1037     @ServiceThreadOnly
removeAvbAudioStatusAction()1038     void removeAvbAudioStatusAction() {
1039         assertRunOnServiceThread();
1040         removeAction(AbsoluteVolumeAudioStatusAction.class);
1041     }
1042 
1043     @ServiceThreadOnly
updateAvbVolume(int volumeIndex)1044     void updateAvbVolume(int volumeIndex) {
1045         assertRunOnServiceThread();
1046         for (AbsoluteVolumeAudioStatusAction action :
1047                 getActions(AbsoluteVolumeAudioStatusAction.class)) {
1048             action.updateVolume(volumeIndex);
1049         }
1050     }
1051 
1052     /**
1053      * If AVB has been enabled, request the System Audio device's audio status and notify
1054      * AudioService of its response.
1055      */
1056     @ServiceThreadOnly
requestAndUpdateAvbAudioStatus()1057     void requestAndUpdateAvbAudioStatus() {
1058         assertRunOnServiceThread();
1059         for (AbsoluteVolumeAudioStatusAction action :
1060                 getActions(AbsoluteVolumeAudioStatusAction.class)) {
1061             action.requestAndUpdateAudioStatus();
1062         }
1063     }
1064 
1065     /**
1066      * Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
1067      * in parallel: send <Give Features> (to get <Report Features> in response),
1068      * and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
1069      */
1070     @ServiceThreadOnly
querySetAudioVolumeLevelSupport(int targetAddress)1071     void querySetAudioVolumeLevelSupport(int targetAddress) {
1072         assertRunOnServiceThread();
1073 
1074         // Send <Give Features> if using CEC 2.0 or above.
1075         if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
1076             mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
1077                     getDeviceInfo().getLogicalAddress(), targetAddress));
1078         }
1079 
1080         // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
1081         // device, start one.
1082         List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions =
1083                 getActions(SetAudioVolumeLevelDiscoveryAction.class);
1084         if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) {
1085             addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress,
1086                     new IHdmiControlCallback.Stub() {
1087                             @Override
1088                             public void onComplete(int result) {
1089                                 if (result == HdmiControlManager.RESULT_SUCCESS) {
1090                                     getService().checkAndUpdateAbsoluteVolumeBehavior();
1091                                 }
1092                             }
1093                         }));
1094         }
1095     }
1096 
1097     @ServiceThreadOnly
startQueuedActions()1098     void startQueuedActions() {
1099         assertRunOnServiceThread();
1100         // Use copied action list in that start() may remove itself.
1101         for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) {
1102             if (!action.started()) {
1103                 Slog.i(TAG, "Starting queued action:" + action);
1104                 action.start();
1105             }
1106         }
1107     }
1108 
1109     // See if we have an action of a given type in progress.
1110     @ServiceThreadOnly
hasAction(final Class<T> clazz)1111     <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) {
1112         assertRunOnServiceThread();
1113         for (HdmiCecFeatureAction action : mActions) {
1114             if (action.getClass().equals(clazz)) {
1115                 return true;
1116             }
1117         }
1118         return false;
1119     }
1120 
1121     // Returns all actions matched with given class type.
1122     @VisibleForTesting
1123     @ServiceThreadOnly
getActions(final Class<T> clazz)1124     <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
1125         assertRunOnServiceThread();
1126         List<T> actions = Collections.<T>emptyList();
1127         for (HdmiCecFeatureAction action : mActions) {
1128             if (action.getClass().equals(clazz)) {
1129                 if (actions.isEmpty()) {
1130                     actions = new ArrayList<T>();
1131                 }
1132                 actions.add((T) action);
1133             }
1134         }
1135         return actions;
1136     }
1137 
1138     /**
1139      * Remove the given {@link HdmiCecFeatureAction} object from the action queue.
1140      *
1141      * @param action {@link HdmiCecFeatureAction} to remove
1142      */
1143     @ServiceThreadOnly
removeAction(final HdmiCecFeatureAction action)1144     void removeAction(final HdmiCecFeatureAction action) {
1145         assertRunOnServiceThread();
1146         action.finish(false);
1147         mActions.remove(action);
1148         checkIfPendingActionsCleared();
1149     }
1150 
1151     // Remove all actions matched with the given Class type.
1152     @ServiceThreadOnly
removeAction(final Class<T> clazz)1153     <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
1154         assertRunOnServiceThread();
1155         removeActionExcept(clazz, null);
1156     }
1157 
1158     // Remove all running actions.
1159     @ServiceThreadOnly
removeAllActions()1160     void removeAllActions() {
1161         assertRunOnServiceThread();
1162         for (HdmiCecFeatureAction action : mActions) {
1163             action.finish(false);
1164         }
1165         mActions.clear();
1166     }
1167 
1168     // Remove all actions matched with the given Class type besides |exception|.
1169     @ServiceThreadOnly
removeActionExcept( final Class<T> clazz, final HdmiCecFeatureAction exception)1170     <T extends HdmiCecFeatureAction> void removeActionExcept(
1171             final Class<T> clazz, final HdmiCecFeatureAction exception) {
1172         assertRunOnServiceThread();
1173         Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
1174         while (iter.hasNext()) {
1175             HdmiCecFeatureAction action = iter.next();
1176             if (action != exception && action.getClass().equals(clazz)) {
1177                 action.finish(false);
1178                 iter.remove();
1179             }
1180         }
1181         checkIfPendingActionsCleared();
1182     }
1183 
checkIfPendingActionsCleared()1184     protected void checkIfPendingActionsCleared() {
1185         if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
1186             PendingActionClearedCallback callback = mPendingActionClearedCallback;
1187             // To prevent from calling the callback again during handling the callback itself.
1188             mPendingActionClearedCallback = null;
1189             callback.onCleared(this);
1190         }
1191     }
1192 
assertRunOnServiceThread()1193     protected void assertRunOnServiceThread() {
1194         if (Looper.myLooper() != mService.getServiceLooper()) {
1195             throw new IllegalStateException("Should run on service thread.");
1196         }
1197     }
1198 
1199     /**
1200      * Called when a hot-plug event issued.
1201      *
1202      * @param portId id of port where a hot-plug event happened
1203      * @param connected whether to connected or not on the event
1204      */
onHotplug(int portId, boolean connected)1205     void onHotplug(int portId, boolean connected) {}
1206 
getService()1207     final HdmiControlService getService() {
1208         return mService;
1209     }
1210 
1211     @ServiceThreadOnly
isConnectedToArcPort(int path)1212     final boolean isConnectedToArcPort(int path) {
1213         assertRunOnServiceThread();
1214         return mService.isConnectedToArcPort(path);
1215     }
1216 
getActiveSource()1217     ActiveSource getActiveSource() {
1218         return mService.getLocalActiveSource();
1219     }
1220 
setActiveSource(ActiveSource newActive, String caller)1221     void setActiveSource(ActiveSource newActive, String caller) {
1222         setActiveSource(newActive.logicalAddress, newActive.physicalAddress, caller);
1223     }
1224 
setActiveSource(HdmiDeviceInfo info, String caller)1225     void setActiveSource(HdmiDeviceInfo info, String caller) {
1226         setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress(), caller);
1227     }
1228 
setActiveSource(int logicalAddress, int physicalAddress, String caller)1229     void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
1230         mService.setActiveSource(logicalAddress, physicalAddress, caller);
1231         mService.setLastInputForMhl(Constants.INVALID_PORT_ID);
1232     }
1233 
getActivePath()1234     int getActivePath() {
1235         synchronized (mLock) {
1236             return mActiveRoutingPath;
1237         }
1238     }
1239 
setActivePath(int path)1240     void setActivePath(int path) {
1241         synchronized (mLock) {
1242             mActiveRoutingPath = path;
1243         }
1244         mService.setActivePortId(pathToPortId(path));
1245     }
1246 
1247     /**
1248      * Returns the ID of the active HDMI port. The active port is the one that has the active
1249      * routing path connected to it directly or indirectly under the device hierarchy.
1250      */
getActivePortId()1251     int getActivePortId() {
1252         synchronized (mLock) {
1253             return mService.pathToPortId(mActiveRoutingPath);
1254         }
1255     }
1256 
1257     /**
1258      * Update the active port.
1259      *
1260      * @param portId the new active port id
1261      */
setActivePortId(int portId)1262     void setActivePortId(int portId) {
1263         // We update active routing path instead, since we get the active port id from
1264         // the active routing path.
1265         setActivePath(mService.portIdToPath(portId));
1266     }
1267 
1268     // Returns the id of the port that the target device is connected to.
getPortId(int physicalAddress)1269     int getPortId(int physicalAddress) {
1270         return mService.pathToPortId(physicalAddress);
1271     }
1272 
1273     @ServiceThreadOnly
getCecMessageCache()1274     HdmiCecMessageCache getCecMessageCache() {
1275         assertRunOnServiceThread();
1276         return mCecMessageCache;
1277     }
1278 
1279     @ServiceThreadOnly
pathToPortId(int newPath)1280     int pathToPortId(int newPath) {
1281         assertRunOnServiceThread();
1282         return mService.pathToPortId(newPath);
1283     }
1284 
1285     /**
1286      * Called when the system goes to standby mode.
1287      *
1288      * @param initiatedByCec true if this power sequence is initiated by the reception the CEC
1289      *     messages like &lt;Standby&gt;
1290      * @param standbyAction Intent action that drives the standby process, either {@link
1291      *     HdmiControlService#STANDBY_SCREEN_OFF} or {@link HdmiControlService#STANDBY_SHUTDOWN}
1292      * @param callback callback invoked after the standby process for the local device is completed.
1293      */
onStandby(boolean initiatedByCec, int standbyAction, StandbyCompletedCallback callback)1294     protected void onStandby(boolean initiatedByCec, int standbyAction,
1295             StandbyCompletedCallback callback) {}
1296 
onStandby(boolean initiatedByCec, int standbyAction)1297     protected void onStandby(boolean initiatedByCec, int standbyAction) {
1298         onStandby(initiatedByCec, standbyAction, null);
1299     }
1300 
1301     /**
1302      * Called when the initialization of local devices is complete.
1303      */
onInitializeCecComplete(int initiatedBy)1304     protected void onInitializeCecComplete(int initiatedBy) {}
1305 
1306     /**
1307      * Disable device. {@code callback} is used to get notified when all pending actions are
1308      * completed or timeout is issued.
1309      *
1310      * @param initiatedByCec true if this sequence is initiated by the reception the CEC messages
1311      *     like &lt;Standby&gt;
1312      * @param originalCallback callback interface to get notified when all pending actions are
1313      *     cleared
1314      */
disableDevice( boolean initiatedByCec, final PendingActionClearedCallback originalCallback)1315     protected void disableDevice(
1316             boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
1317         removeAction(SetAudioVolumeLevelDiscoveryAction.class);
1318         removeAction(ActiveSourceAction.class);
1319         removeAction(ResendCecCommandAction.class);
1320 
1321         mPendingActionClearedCallback =
1322                 new PendingActionClearedCallback() {
1323                     @Override
1324                     public void onCleared(HdmiCecLocalDevice device) {
1325                         mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
1326                         originalCallback.onCleared(device);
1327                     }
1328                 };
1329         mHandler.sendMessageDelayed(
1330                 Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), DEVICE_CLEANUP_TIMEOUT);
1331     }
1332 
1333     @ServiceThreadOnly
handleDisableDeviceTimeout()1334     private void handleDisableDeviceTimeout() {
1335         assertRunOnServiceThread();
1336 
1337         // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them.
1338         // onCleard will be called at the last action's finish method.
1339         Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
1340         while (iter.hasNext()) {
1341             HdmiCecFeatureAction action = iter.next();
1342             action.finish(false);
1343             iter.remove();
1344         }
1345         if (mPendingActionClearedCallback != null) {
1346             mPendingActionClearedCallback.onCleared(this);
1347         }
1348     }
1349 
1350     /**
1351      * Send a key event to other CEC device. The logical address of target device will be given by
1352      * {@link #findKeyReceiverAddress}.
1353      *
1354      * @param keyCode key code defined in {@link android.view.KeyEvent}
1355      * @param isPressed {@code true} for key down event
1356      * @see #findKeyReceiverAddress()
1357      */
1358     @ServiceThreadOnly
sendKeyEvent(int keyCode, boolean isPressed)1359     protected void sendKeyEvent(int keyCode, boolean isPressed) {
1360         assertRunOnServiceThread();
1361         if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
1362             Slog.w(TAG, "Unsupported key: " + keyCode);
1363             return;
1364         }
1365         List<SendKeyAction> action = getActions(SendKeyAction.class);
1366         int logicalAddress = findKeyReceiverAddress();
1367         if (logicalAddress == Constants.ADDR_INVALID
1368                 || logicalAddress == mDeviceInfo.getLogicalAddress()) {
1369             // Don't send key event to invalid device or itself.
1370             Slog.w(
1371                     TAG,
1372                     "Discard key event: "
1373                             + keyCode
1374                             + ", pressed:"
1375                             + isPressed
1376                             + ", receiverAddr="
1377                             + logicalAddress);
1378         } else if (!action.isEmpty()) {
1379             action.get(0).processKeyEvent(keyCode, isPressed);
1380         } else if (isPressed) {
1381             addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
1382         }
1383     }
1384 
1385     /**
1386      * Send a volume key event to other CEC device. The logical address of target device will be
1387      * given by {@link #findAudioReceiverAddress()}.
1388      *
1389      * @param keyCode key code defined in {@link android.view.KeyEvent}
1390      * @param isPressed {@code true} for key down event
1391      * @see #findAudioReceiverAddress()
1392      */
1393     @ServiceThreadOnly
sendVolumeKeyEvent(int keyCode, boolean isPressed)1394     protected void sendVolumeKeyEvent(int keyCode, boolean isPressed) {
1395         assertRunOnServiceThread();
1396         if (mService.getHdmiCecVolumeControl()
1397                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
1398             return;
1399         }
1400         if (!HdmiCecKeycode.isVolumeKeycode(keyCode)) {
1401             Slog.w(TAG, "Not a volume key: " + keyCode);
1402             return;
1403         }
1404         List<SendKeyAction> action = getActions(SendKeyAction.class);
1405         int logicalAddress = findAudioReceiverAddress();
1406         if (logicalAddress == Constants.ADDR_INVALID
1407                 || mService.getAllCecLocalDevices().stream().anyMatch(
1408                         device -> device.getDeviceInfo().getLogicalAddress() == logicalAddress)) {
1409             // Don't send key event to invalid device or itself.
1410             Slog.w(
1411                     TAG,
1412                     "Discard volume key event: "
1413                             + keyCode
1414                             + ", pressed:"
1415                             + isPressed
1416                             + ", receiverAddr="
1417                             + logicalAddress);
1418         } else if (!action.isEmpty()) {
1419             action.get(0).processKeyEvent(keyCode, isPressed);
1420         } else if (isPressed) {
1421             addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
1422         }
1423     }
1424 
1425     /**
1426      * Returns the logical address of the device which will receive key events via {@link
1427      * #sendKeyEvent}.
1428      *
1429      * @see #sendKeyEvent(int, boolean)
1430      */
findKeyReceiverAddress()1431     protected int findKeyReceiverAddress() {
1432         Slog.w(TAG, "findKeyReceiverAddress is not implemented");
1433         return Constants.ADDR_INVALID;
1434     }
1435 
1436     /**
1437      * Returns the logical address of the audio receiver device which will receive volume key events
1438      * via {@link#sendVolumeKeyEvent}.
1439      *
1440      * @see #sendVolumeKeyEvent(int, boolean)
1441      */
findAudioReceiverAddress()1442     protected int findAudioReceiverAddress() {
1443         Slog.w(TAG, "findAudioReceiverAddress is not implemented");
1444         return Constants.ADDR_INVALID;
1445     }
1446 
1447     @ServiceThreadOnly
invokeCallback(IHdmiControlCallback callback, int result)1448     void invokeCallback(IHdmiControlCallback callback, int result) {
1449         assertRunOnServiceThread();
1450         if (callback == null) {
1451             return;
1452         }
1453         try {
1454             callback.onComplete(result);
1455         } catch (RemoteException e) {
1456             Slog.e(TAG, "Invoking callback failed:" + e);
1457         }
1458     }
1459 
1460     @ServiceThreadOnly
1461     @VisibleForTesting
invokeStandbyCompletedCallback(StandbyCompletedCallback callback)1462     public void invokeStandbyCompletedCallback(StandbyCompletedCallback callback) {
1463         assertRunOnServiceThread();
1464         if (callback == null) {
1465             return;
1466         }
1467         callback.onStandbyCompleted();
1468     }
1469 
sendUserControlPressedAndReleased(int targetAddress, int cecKeycode)1470     void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) {
1471         mService.sendCecCommand(
1472                 HdmiCecMessageBuilder.buildUserControlPressed(
1473                         mDeviceInfo.getLogicalAddress(), targetAddress, cecKeycode));
1474         mService.sendCecCommand(
1475                 HdmiCecMessageBuilder.buildUserControlReleased(
1476                         mDeviceInfo.getLogicalAddress(), targetAddress));
1477     }
1478 
addActiveSourceHistoryItem(ActiveSource activeSource, boolean isActiveSource, String caller)1479     void addActiveSourceHistoryItem(ActiveSource activeSource, boolean isActiveSource,
1480             String caller) {
1481         ActiveSourceHistoryRecord record = new ActiveSourceHistoryRecord(activeSource,
1482                 isActiveSource, caller);
1483         if (!mActiveSourceHistory.offer(record)) {
1484             mActiveSourceHistory.poll();
1485             mActiveSourceHistory.offer(record);
1486         }
1487     }
1488 
getActiveSourceHistory()1489     public ArrayBlockingQueue<HdmiCecController.Dumpable> getActiveSourceHistory() {
1490         return this.mActiveSourceHistory;
1491     }
1492 
1493     /** Dump internal status of HdmiCecLocalDevice object. */
dump(final IndentingPrintWriter pw)1494     protected void dump(final IndentingPrintWriter pw) {
1495         pw.println("mDeviceType: " + mDeviceType);
1496         pw.println("mPreferredAddress: " + mPreferredAddress);
1497         pw.println("mDeviceInfo: " + mDeviceInfo);
1498         pw.println("mActiveSource: " + getActiveSource());
1499         pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
1500     }
1501 
1502     /** Calculates the physical address for {@code activePortId}.
1503      *
1504      * <p>This method assumes current device physical address is valid.
1505      * <p>If the current device is already the leaf of the whole CEC system
1506      * and can't have devices under it, will return its own physical address.
1507      *
1508      * @param activePortId is the local active port Id
1509      * @return the calculated physical address of the port
1510      */
getActivePathOnSwitchFromActivePortId(@ocalActivePort int activePortId)1511     protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) {
1512         int myPhysicalAddress = mService.getPhysicalAddress();
1513         int finalMask = activePortId << 8;
1514         int mask;
1515         for (mask = 0x0F00; mask > 0x000F;  mask >>= 4) {
1516             if ((myPhysicalAddress & mask) == 0)  {
1517                 break;
1518             } else {
1519                 finalMask >>= 4;
1520             }
1521         }
1522         return finalMask | myPhysicalAddress;
1523     }
1524 
1525     private static final class ActiveSourceHistoryRecord extends HdmiCecController.Dumpable {
1526         private final ActiveSource mActiveSource;
1527         private final boolean mIsActiveSource;
1528         private final String mCaller;
1529 
ActiveSourceHistoryRecord(ActiveSource mActiveSource, boolean mIsActiveSource, String caller)1530         private ActiveSourceHistoryRecord(ActiveSource mActiveSource, boolean mIsActiveSource,
1531                 String caller) {
1532             this.mActiveSource = mActiveSource;
1533             this.mIsActiveSource = mIsActiveSource;
1534             this.mCaller = caller;
1535         }
1536 
1537         @Override
dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1538         void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
1539             pw.print("time=");
1540             pw.print(sdf.format(new Date(mTime)));
1541             pw.print(" active source=");
1542             pw.print(mActiveSource);
1543             pw.print(" isActiveSource=");
1544             pw.print(mIsActiveSource);
1545             pw.print(" from=");
1546             pw.println(mCaller);
1547         }
1548     }
1549 }
1550