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