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