• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.hdmi;
18 
19 import static 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 (!mService.invokeVendorCommandListenersOnReceived(
931                 mDeviceType, message.getSource(), message.getDestination(), params, true)) {
932             if (message.getDestination() == Constants.ADDR_BROADCAST
933                     || message.getSource() == Constants.ADDR_UNREGISTERED) {
934                 Slog.v(TAG, "Broadcast vendor command with no listeners. Ignoring");
935             } else {
936                 return Constants.ABORT_REFUSED;
937             }
938         }
939         return Constants.HANDLED;
940     }
941 
sendStandby(int deviceId)942     protected void sendStandby(int deviceId) {
943         // Do nothing.
944     }
945 
946     @Constants.HandleMessageResult
handleSetOsdName(HdmiCecMessage message)947     protected int handleSetOsdName(HdmiCecMessage message) {
948         // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state
949         return Constants.HANDLED;
950     }
951 
952     @Constants.HandleMessageResult
handleRecordTvScreen(HdmiCecMessage message)953     protected int handleRecordTvScreen(HdmiCecMessage message) {
954         return Constants.NOT_HANDLED;
955     }
956 
957     @Constants.HandleMessageResult
handleTimerClearedStatus(HdmiCecMessage message)958     protected int handleTimerClearedStatus(HdmiCecMessage message) {
959         return Constants.NOT_HANDLED;
960     }
961 
962     @Constants.HandleMessageResult
handleReportPowerStatus(HdmiCecMessage message)963     protected int handleReportPowerStatus(HdmiCecMessage message) {
964         // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state
965         return Constants.HANDLED;
966     }
967 
968     @Constants.HandleMessageResult
handleTimerStatus(HdmiCecMessage message)969     protected int handleTimerStatus(HdmiCecMessage message) {
970         return Constants.NOT_HANDLED;
971     }
972 
973     @Constants.HandleMessageResult
handleRecordStatus(HdmiCecMessage message)974     protected int handleRecordStatus(HdmiCecMessage message) {
975         return Constants.NOT_HANDLED;
976     }
977 
978     @ServiceThreadOnly
handleAddressAllocated( int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason)979     final void handleAddressAllocated(
980             int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason) {
981         assertRunOnServiceThread();
982         preprocessBufferedMessages(bufferedMessages);
983         mPreferredAddress = logicalAddress;
984         updateDeviceFeatures();
985         if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
986             reportFeatures();
987         }
988         onAddressAllocated(logicalAddress, reason);
989         setPreferredAddress(logicalAddress);
990     }
991 
getType()992     int getType() {
993         return mDeviceType;
994     }
995 
getDeviceInfo()996     HdmiDeviceInfo getDeviceInfo() {
997         synchronized (mLock) {
998             return mDeviceInfo;
999         }
1000     }
1001 
setDeviceInfo(HdmiDeviceInfo info)1002     void setDeviceInfo(HdmiDeviceInfo info) {
1003         synchronized (mLock) {
1004             mDeviceInfo = info;
1005         }
1006     }
1007 
1008     // Returns true if the logical address is same as the argument.
1009     @ServiceThreadOnly
isAddressOf(int addr)1010     boolean isAddressOf(int addr) {
1011         assertRunOnServiceThread();
1012         return addr == mDeviceInfo.getLogicalAddress();
1013     }
1014 
1015     @ServiceThreadOnly
addAndStartAction(final HdmiCecFeatureAction action)1016     void addAndStartAction(final HdmiCecFeatureAction action) {
1017         assertRunOnServiceThread();
1018         mActions.add(action);
1019         if (mService.isPowerStandby() || !mService.isAddressAllocated()) {
1020             if (action.getClass() == ResendCecCommandAction.class) {
1021                 Slog.i(TAG, "Not ready to start ResendCecCommandAction. "
1022                         + "This action is cancelled.");
1023                 removeAction(action);
1024                 return;
1025             }
1026             Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action);
1027             return;
1028         }
1029         action.start();
1030     }
1031 
1032     @ServiceThreadOnly
addAndStartAction(final HdmiCecFeatureAction action, final boolean remove)1033     void addAndStartAction(final HdmiCecFeatureAction action, final boolean remove) {
1034         assertRunOnServiceThread();
1035         if (hasAction(action.getClass()) && remove) {
1036             // If the action is currently running, remove it and restart it.
1037             Slog.i(TAG, action.getClass().getName() + " is in progress. Restarting.");
1038             removeAction(action.getClass());
1039         }
1040         addAndStartAction(action);
1041     }
1042 
1043     @ServiceThreadOnly
startNewAvbAudioStatusAction(int targetAddress)1044     void startNewAvbAudioStatusAction(int targetAddress) {
1045         assertRunOnServiceThread();
1046         addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress), true);
1047     }
1048 
1049     @ServiceThreadOnly
removeAvbAudioStatusAction()1050     void removeAvbAudioStatusAction() {
1051         assertRunOnServiceThread();
1052         removeAction(AbsoluteVolumeAudioStatusAction.class);
1053     }
1054 
1055     @ServiceThreadOnly
updateAvbVolume(int volumeIndex)1056     void updateAvbVolume(int volumeIndex) {
1057         assertRunOnServiceThread();
1058         for (AbsoluteVolumeAudioStatusAction action :
1059                 getActions(AbsoluteVolumeAudioStatusAction.class)) {
1060             action.updateVolume(volumeIndex);
1061         }
1062     }
1063 
1064     /**
1065      * If AVB has been enabled, request the System Audio device's audio status and notify
1066      * AudioService of its response.
1067      */
1068     @ServiceThreadOnly
requestAndUpdateAvbAudioStatus()1069     void requestAndUpdateAvbAudioStatus() {
1070         assertRunOnServiceThread();
1071         for (AbsoluteVolumeAudioStatusAction action :
1072                 getActions(AbsoluteVolumeAudioStatusAction.class)) {
1073             action.requestAndUpdateAudioStatus();
1074         }
1075     }
1076 
1077     /**
1078      * Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
1079      * in parallel: send <Give Features> (to get <Report Features> in response),
1080      * and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
1081      */
1082     @ServiceThreadOnly
querySetAudioVolumeLevelSupport(int targetAddress)1083     void querySetAudioVolumeLevelSupport(int targetAddress) {
1084         assertRunOnServiceThread();
1085 
1086         // Send <Give Features> if using CEC 2.0 or above.
1087         if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
1088             mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
1089                     getDeviceInfo().getLogicalAddress(), targetAddress));
1090         }
1091 
1092         // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
1093         // device, start one.
1094         List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions =
1095                 getActions(SetAudioVolumeLevelDiscoveryAction.class);
1096         if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) {
1097             addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress,
1098                     new IHdmiControlCallback.Stub() {
1099                             @Override
1100                             public void onComplete(int result) {
1101                                 if (result == HdmiControlManager.RESULT_SUCCESS) {
1102                                     getService().checkAndUpdateAbsoluteVolumeBehavior();
1103                                 }
1104                             }
1105                         }));
1106         }
1107     }
1108 
1109     @ServiceThreadOnly
startQueuedActions()1110     void startQueuedActions() {
1111         assertRunOnServiceThread();
1112         // Use copied action list in that start() may remove itself.
1113         for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) {
1114             if (!action.started()) {
1115                 Slog.i(TAG, "Starting queued action:" + action);
1116                 action.start();
1117             }
1118         }
1119     }
1120 
1121     // See if we have an action of a given type in progress.
1122     @ServiceThreadOnly
hasAction(final Class<T> clazz)1123     <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) {
1124         assertRunOnServiceThread();
1125         for (HdmiCecFeatureAction action : mActions) {
1126             if (action.getClass().equals(clazz)) {
1127                 return true;
1128             }
1129         }
1130         return false;
1131     }
1132 
1133     // Returns all actions matched with given class type.
1134     @VisibleForTesting
1135     @ServiceThreadOnly
getActions(final Class<T> clazz)1136     <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
1137         assertRunOnServiceThread();
1138         List<T> actions = Collections.<T>emptyList();
1139         for (HdmiCecFeatureAction action : mActions) {
1140             if (action.getClass().equals(clazz)) {
1141                 if (actions.isEmpty()) {
1142                     actions = new ArrayList<T>();
1143                 }
1144                 actions.add((T) action);
1145             }
1146         }
1147         return actions;
1148     }
1149 
1150     /**
1151      * Remove the given {@link HdmiCecFeatureAction} object from the action queue.
1152      *
1153      * @param action {@link HdmiCecFeatureAction} to remove
1154      */
1155     @ServiceThreadOnly
removeAction(final HdmiCecFeatureAction action)1156     void removeAction(final HdmiCecFeatureAction action) {
1157         assertRunOnServiceThread();
1158         action.finish(false);
1159         mActions.remove(action);
1160         checkIfPendingActionsCleared();
1161     }
1162 
1163     // Remove all actions matched with the given Class type.
1164     @ServiceThreadOnly
removeAction(final Class<T> clazz)1165     <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
1166         assertRunOnServiceThread();
1167         removeActionExcept(clazz, null);
1168     }
1169 
1170     // Remove all running actions.
1171     @ServiceThreadOnly
removeAllActions()1172     void removeAllActions() {
1173         assertRunOnServiceThread();
1174         for (HdmiCecFeatureAction action : mActions) {
1175             action.finish(false);
1176         }
1177         mActions.clear();
1178     }
1179 
1180     // Remove all actions matched with the given Class type besides |exception|.
1181     @ServiceThreadOnly
removeActionExcept( final Class<T> clazz, final HdmiCecFeatureAction exception)1182     <T extends HdmiCecFeatureAction> void removeActionExcept(
1183             final Class<T> clazz, final HdmiCecFeatureAction exception) {
1184         assertRunOnServiceThread();
1185         Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
1186         while (iter.hasNext()) {
1187             HdmiCecFeatureAction action = iter.next();
1188             if (action != exception && action.getClass().equals(clazz)) {
1189                 action.finish(false);
1190                 iter.remove();
1191             }
1192         }
1193         checkIfPendingActionsCleared();
1194     }
1195 
checkIfPendingActionsCleared()1196     protected void checkIfPendingActionsCleared() {
1197         if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
1198             PendingActionClearedCallback callback = mPendingActionClearedCallback;
1199             // To prevent from calling the callback again during handling the callback itself.
1200             mPendingActionClearedCallback = null;
1201             callback.onCleared(this);
1202         }
1203     }
1204 
assertRunOnServiceThread()1205     protected void assertRunOnServiceThread() {
1206         if (Looper.myLooper() != mService.getServiceLooper()) {
1207             throw new IllegalStateException("Should run on service thread.");
1208         }
1209     }
1210 
1211     /**
1212      * Called when a hot-plug event issued.
1213      *
1214      * @param portId id of port where a hot-plug event happened
1215      * @param connected whether to connected or not on the event
1216      */
onHotplug(int portId, boolean connected)1217     void onHotplug(int portId, boolean connected) {}
1218 
getService()1219     final HdmiControlService getService() {
1220         return mService;
1221     }
1222 
1223     @ServiceThreadOnly
isConnectedToArcPort(int path)1224     final boolean isConnectedToArcPort(int path) {
1225         assertRunOnServiceThread();
1226         return mService.isConnectedToArcPort(path);
1227     }
1228 
getActiveSource()1229     ActiveSource getActiveSource() {
1230         return mService.getLocalActiveSource();
1231     }
1232 
setActiveSource(ActiveSource newActive, String caller)1233     void setActiveSource(ActiveSource newActive, String caller) {
1234         setActiveSource(newActive.logicalAddress, newActive.physicalAddress, caller);
1235     }
1236 
setActiveSource(HdmiDeviceInfo info, String caller)1237     void setActiveSource(HdmiDeviceInfo info, String caller) {
1238         setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress(), caller);
1239     }
1240 
setActiveSource(int logicalAddress, int physicalAddress, String caller)1241     void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
1242         mService.setActiveSource(logicalAddress, physicalAddress, caller);
1243         mService.setLastInputForMhl(Constants.INVALID_PORT_ID);
1244     }
1245 
getActivePath()1246     int getActivePath() {
1247         synchronized (mLock) {
1248             return mActiveRoutingPath;
1249         }
1250     }
1251 
setActivePath(int path)1252     void setActivePath(int path) {
1253         synchronized (mLock) {
1254             mActiveRoutingPath = path;
1255         }
1256         mService.setActivePortId(pathToPortId(path));
1257     }
1258 
1259     /**
1260      * Returns the ID of the active HDMI port. The active port is the one that has the active
1261      * routing path connected to it directly or indirectly under the device hierarchy.
1262      */
getActivePortId()1263     int getActivePortId() {
1264         synchronized (mLock) {
1265             return mService.pathToPortId(mActiveRoutingPath);
1266         }
1267     }
1268 
1269     /**
1270      * Update the active port.
1271      *
1272      * @param portId the new active port id
1273      */
setActivePortId(int portId)1274     void setActivePortId(int portId) {
1275         // We update active routing path instead, since we get the active port id from
1276         // the active routing path.
1277         setActivePath(mService.portIdToPath(portId));
1278     }
1279 
1280     // Returns the id of the port that the target device is connected to.
getPortId(int physicalAddress)1281     int getPortId(int physicalAddress) {
1282         return mService.pathToPortId(physicalAddress);
1283     }
1284 
1285     @ServiceThreadOnly
getCecMessageCache()1286     HdmiCecMessageCache getCecMessageCache() {
1287         assertRunOnServiceThread();
1288         return mCecMessageCache;
1289     }
1290 
1291     @ServiceThreadOnly
pathToPortId(int newPath)1292     int pathToPortId(int newPath) {
1293         assertRunOnServiceThread();
1294         return mService.pathToPortId(newPath);
1295     }
1296 
1297     /**
1298      * Called when the system goes to standby mode.
1299      *
1300      * @param initiatedByCec true if this power sequence is initiated by the reception the CEC
1301      *     messages like &lt;Standby&gt;
1302      * @param standbyAction Intent action that drives the standby process, either {@link
1303      *     HdmiControlService#STANDBY_SCREEN_OFF} or {@link HdmiControlService#STANDBY_SHUTDOWN}
1304      * @param callback callback invoked after the standby process for the local device is completed.
1305      */
onStandby(boolean initiatedByCec, int standbyAction, StandbyCompletedCallback callback)1306     protected void onStandby(boolean initiatedByCec, int standbyAction,
1307             StandbyCompletedCallback callback) {}
1308 
onStandby(boolean initiatedByCec, int standbyAction)1309     protected void onStandby(boolean initiatedByCec, int standbyAction) {
1310         onStandby(initiatedByCec, standbyAction, null);
1311     }
1312 
1313     /**
1314      * Called when the initialization of local devices is complete.
1315      */
onInitializeCecComplete(int initiatedBy)1316     protected void onInitializeCecComplete(int initiatedBy) {}
1317 
1318     /**
1319      * Disable device. {@code callback} is used to get notified when all pending actions are
1320      * completed or timeout is issued.
1321      *
1322      * @param initiatedByCec true if this sequence is initiated by the reception the CEC messages
1323      *     like &lt;Standby&gt;
1324      * @param originalCallback callback interface to get notified when all pending actions are
1325      *     cleared
1326      */
disableDevice( boolean initiatedByCec, final PendingActionClearedCallback originalCallback)1327     protected void disableDevice(
1328             boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
1329         removeAction(SetAudioVolumeLevelDiscoveryAction.class);
1330         removeAction(ActiveSourceAction.class);
1331         removeAction(ResendCecCommandAction.class);
1332 
1333         mPendingActionClearedCallback =
1334                 new PendingActionClearedCallback() {
1335                     @Override
1336                     public void onCleared(HdmiCecLocalDevice device) {
1337                         mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
1338                         originalCallback.onCleared(device);
1339                     }
1340                 };
1341         mHandler.sendMessageDelayed(
1342                 Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), DEVICE_CLEANUP_TIMEOUT);
1343     }
1344 
1345     @ServiceThreadOnly
handleDisableDeviceTimeout()1346     private void handleDisableDeviceTimeout() {
1347         assertRunOnServiceThread();
1348 
1349         // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them.
1350         // onCleard will be called at the last action's finish method.
1351         Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
1352         while (iter.hasNext()) {
1353             HdmiCecFeatureAction action = iter.next();
1354             action.finish(false);
1355             iter.remove();
1356         }
1357         if (mPendingActionClearedCallback != null) {
1358             PendingActionClearedCallback callback = mPendingActionClearedCallback;
1359             // To prevent from calling the callback again during handling the callback itself.
1360             mPendingActionClearedCallback = null;
1361             callback.onCleared(this);
1362         }
1363     }
1364 
1365     /**
1366      * Send a key event to other CEC device. The logical address of target device will be given by
1367      * {@link #findKeyReceiverAddress}.
1368      *
1369      * @param keyCode key code defined in {@link android.view.KeyEvent}
1370      * @param isPressed {@code true} for key down event
1371      * @see #findKeyReceiverAddress()
1372      */
1373     @ServiceThreadOnly
sendKeyEvent(int keyCode, boolean isPressed)1374     protected void sendKeyEvent(int keyCode, boolean isPressed) {
1375         assertRunOnServiceThread();
1376         if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
1377             Slog.w(TAG, "Unsupported key: " + keyCode);
1378             return;
1379         }
1380         List<SendKeyAction> action = getActions(SendKeyAction.class);
1381         int logicalAddress = findKeyReceiverAddress();
1382         if (logicalAddress == Constants.ADDR_INVALID
1383                 || logicalAddress == mDeviceInfo.getLogicalAddress()) {
1384             // Don't send key event to invalid device or itself.
1385             Slog.w(
1386                     TAG,
1387                     "Discard key event: "
1388                             + keyCode
1389                             + ", pressed:"
1390                             + isPressed
1391                             + ", receiverAddr="
1392                             + logicalAddress);
1393         } else if (!action.isEmpty()) {
1394             action.get(0).processKeyEvent(keyCode, isPressed);
1395         } else if (isPressed) {
1396             addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
1397         }
1398     }
1399 
1400     /**
1401      * Send a volume key event to other CEC device. The logical address of target device will be
1402      * given by {@link #findAudioReceiverAddress()}.
1403      *
1404      * @param keyCode key code defined in {@link android.view.KeyEvent}
1405      * @param isPressed {@code true} for key down event
1406      * @see #findAudioReceiverAddress()
1407      */
1408     @ServiceThreadOnly
sendVolumeKeyEvent(int keyCode, boolean isPressed)1409     protected void sendVolumeKeyEvent(int keyCode, boolean isPressed) {
1410         assertRunOnServiceThread();
1411         if (mService.getHdmiCecVolumeControl()
1412                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
1413             return;
1414         }
1415         if (!HdmiCecKeycode.isVolumeKeycode(keyCode)) {
1416             Slog.w(TAG, "Not a volume key: " + keyCode);
1417             return;
1418         }
1419         List<SendKeyAction> action = getActions(SendKeyAction.class);
1420         int logicalAddress = findAudioReceiverAddress();
1421         if (logicalAddress == Constants.ADDR_INVALID
1422                 || mService.getAllCecLocalDevices().stream().anyMatch(
1423                         device -> device.getDeviceInfo().getLogicalAddress() == logicalAddress)) {
1424             // Don't send key event to invalid device or itself.
1425             Slog.w(
1426                     TAG,
1427                     "Discard volume key event: "
1428                             + keyCode
1429                             + ", pressed:"
1430                             + isPressed
1431                             + ", receiverAddr="
1432                             + logicalAddress);
1433         } else if (!action.isEmpty()) {
1434             action.get(0).processKeyEvent(keyCode, isPressed);
1435         } else if (isPressed) {
1436             addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
1437         }
1438     }
1439 
1440     /**
1441      * Returns the logical address of the device which will receive key events via {@link
1442      * #sendKeyEvent}.
1443      *
1444      * @see #sendKeyEvent(int, boolean)
1445      */
findKeyReceiverAddress()1446     protected int findKeyReceiverAddress() {
1447         Slog.w(TAG, "findKeyReceiverAddress is not implemented");
1448         return Constants.ADDR_INVALID;
1449     }
1450 
1451     /**
1452      * Returns the logical address of the audio receiver device which will receive volume key events
1453      * via {@link#sendVolumeKeyEvent}.
1454      *
1455      * @see #sendVolumeKeyEvent(int, boolean)
1456      */
findAudioReceiverAddress()1457     protected int findAudioReceiverAddress() {
1458         Slog.w(TAG, "findAudioReceiverAddress is not implemented");
1459         return Constants.ADDR_INVALID;
1460     }
1461 
1462     @ServiceThreadOnly
invokeCallback(IHdmiControlCallback callback, int result)1463     void invokeCallback(IHdmiControlCallback callback, int result) {
1464         assertRunOnServiceThread();
1465         if (callback == null) {
1466             return;
1467         }
1468         try {
1469             callback.onComplete(result);
1470         } catch (RemoteException e) {
1471             Slog.e(TAG, "Invoking callback failed:" + e);
1472         }
1473     }
1474 
1475     @ServiceThreadOnly
1476     @VisibleForTesting
invokeStandbyCompletedCallback(StandbyCompletedCallback callback)1477     public void invokeStandbyCompletedCallback(StandbyCompletedCallback callback) {
1478         assertRunOnServiceThread();
1479         if (callback == null) {
1480             return;
1481         }
1482         callback.onStandbyCompleted();
1483     }
1484 
sendUserControlPressedAndReleased(int targetAddress, int cecKeycode)1485     void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) {
1486         mService.sendCecCommand(
1487                 HdmiCecMessageBuilder.buildUserControlPressed(
1488                         mDeviceInfo.getLogicalAddress(), targetAddress, cecKeycode));
1489         mService.sendCecCommand(
1490                 HdmiCecMessageBuilder.buildUserControlReleased(
1491                         mDeviceInfo.getLogicalAddress(), targetAddress));
1492     }
1493 
addActiveSourceHistoryItem(ActiveSource activeSource, boolean isActiveSource, String caller)1494     void addActiveSourceHistoryItem(ActiveSource activeSource, boolean isActiveSource,
1495             String caller) {
1496         ActiveSourceHistoryRecord record = new ActiveSourceHistoryRecord(activeSource,
1497                 isActiveSource, caller);
1498         if (!mActiveSourceHistory.offer(record)) {
1499             mActiveSourceHistory.poll();
1500             mActiveSourceHistory.offer(record);
1501         }
1502     }
1503 
getActiveSourceHistory()1504     public ArrayBlockingQueue<HdmiCecController.Dumpable> getActiveSourceHistory() {
1505         return this.mActiveSourceHistory;
1506     }
1507 
1508     /** Dump internal status of HdmiCecLocalDevice object. */
dump(final IndentingPrintWriter pw)1509     protected void dump(final IndentingPrintWriter pw) {
1510         pw.println("mDeviceType: " + mDeviceType);
1511         pw.println("mPreferredAddress: " + mPreferredAddress);
1512         pw.println("mDeviceInfo: " + mDeviceInfo);
1513         pw.println("mActiveSource: " + getActiveSource());
1514         pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
1515     }
1516 
1517     /** Calculates the physical address for {@code activePortId}.
1518      *
1519      * <p>This method assumes current device physical address is valid.
1520      * <p>If the current device is already the leaf of the whole CEC system
1521      * and can't have devices under it, will return its own physical address.
1522      *
1523      * @param activePortId is the local active port Id
1524      * @return the calculated physical address of the port
1525      */
getActivePathOnSwitchFromActivePortId(@ocalActivePort int activePortId)1526     protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) {
1527         int myPhysicalAddress = mService.getPhysicalAddress();
1528         int finalMask = activePortId << 8;
1529         int mask;
1530         for (mask = 0x0F00; mask > 0x000F;  mask >>= 4) {
1531             if ((myPhysicalAddress & mask) == 0)  {
1532                 break;
1533             } else {
1534                 finalMask >>= 4;
1535             }
1536         }
1537         return finalMask | myPhysicalAddress;
1538     }
1539 
1540     private static final class ActiveSourceHistoryRecord extends HdmiCecController.Dumpable {
1541         private final ActiveSource mActiveSource;
1542         private final boolean mIsActiveSource;
1543         private final String mCaller;
1544 
ActiveSourceHistoryRecord(ActiveSource mActiveSource, boolean mIsActiveSource, String caller)1545         private ActiveSourceHistoryRecord(ActiveSource mActiveSource, boolean mIsActiveSource,
1546                 String caller) {
1547             this.mActiveSource = mActiveSource;
1548             this.mIsActiveSource = mIsActiveSource;
1549             this.mCaller = caller;
1550         }
1551 
1552         @Override
dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1553         void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
1554             pw.print("time=");
1555             pw.print(sdf.format(new Date(mTime)));
1556             pw.print(" active source=");
1557             pw.print(mActiveSource);
1558             pw.print(" isActiveSource=");
1559             pw.print(mIsActiveSource);
1560             pw.print(" from=");
1561             pw.println(mCaller);
1562         }
1563     }
1564 }
1565