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.hardware.hdmi.HdmiDeviceInfo;
20 import android.hardware.input.InputManager;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.os.Message;
24 import android.os.SystemClock;
25 import android.util.Slog;
26 import android.view.InputDevice;
27 import android.view.KeyCharacterMap;
28 import android.view.KeyEvent;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.util.IndentingPrintWriter;
32 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
33 
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Iterator;
37 import java.util.List;
38 
39 /**
40  * Class that models a logical CEC device hosted in this system. Handles initialization,
41  * CEC commands that call for actions customized per device type.
42  */
43 abstract class HdmiCecLocalDevice {
44     private static final String TAG = "HdmiCecLocalDevice";
45 
46     private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1;
47     private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2;
48     // Timeout in millisecond for device clean up (5s).
49     // Normal actions timeout is 2s but some of them would have several sequence of timeout.
50     private static final int DEVICE_CLEANUP_TIMEOUT = 5000;
51     // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior.
52     // When it expires, we can assume <User Control Release> is received.
53     private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
54 
55     protected final HdmiControlService mService;
56     protected final int mDeviceType;
57     protected int mAddress;
58     protected int mPreferredAddress;
59     protected HdmiDeviceInfo mDeviceInfo;
60     protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
61     protected int mLastKeyRepeatCount = 0;
62 
63     static class ActiveSource {
64         int logicalAddress;
65         int physicalAddress;
66 
ActiveSource()67         public ActiveSource() {
68             invalidate();
69         }
ActiveSource(int logical, int physical)70         public ActiveSource(int logical, int physical) {
71             logicalAddress = logical;
72             physicalAddress = physical;
73         }
of(ActiveSource source)74         public static ActiveSource of(ActiveSource source) {
75             return new ActiveSource(source.logicalAddress, source.physicalAddress);
76         }
of(int logical, int physical)77         public static ActiveSource of(int logical, int physical) {
78             return new ActiveSource(logical, physical);
79         }
isValid()80         public boolean isValid() {
81             return HdmiUtils.isValidAddress(logicalAddress);
82         }
invalidate()83         public void invalidate() {
84             logicalAddress = Constants.ADDR_INVALID;
85             physicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
86         }
equals(int logical, int physical)87         public boolean equals(int logical, int physical) {
88             return logicalAddress == logical && physicalAddress == physical;
89         }
90         @Override
equals(Object obj)91         public boolean equals(Object obj) {
92             if (obj instanceof ActiveSource) {
93                 ActiveSource that = (ActiveSource) obj;
94                 return that.logicalAddress == logicalAddress &&
95                        that.physicalAddress == physicalAddress;
96             }
97             return false;
98         }
99         @Override
hashCode()100         public int hashCode() {
101             return logicalAddress * 29 + physicalAddress;
102         }
103         @Override
toString()104         public String toString() {
105             StringBuffer s = new StringBuffer();
106             String logicalAddressString = (logicalAddress == Constants.ADDR_INVALID)
107                     ? "invalid" : String.format("0x%02x", logicalAddress);
108             s.append("(").append(logicalAddressString);
109             String physicalAddressString = (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS)
110                     ? "invalid" : String.format("0x%04x", physicalAddress);
111             s.append(", ").append(physicalAddressString).append(")");
112             return s.toString();
113         }
114     }
115     // Logical address of the active source.
116     @GuardedBy("mLock")
117     protected final ActiveSource mActiveSource = new ActiveSource();
118 
119     // Active routing path. Physical address of the active source but not all the time, such as
120     // when the new active source does not claim itself to be one. Note that we don't keep
121     // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}.
122     @GuardedBy("mLock")
123     private int mActiveRoutingPath;
124 
125     protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
126     protected final Object mLock;
127 
128     // A collection of FeatureAction.
129     // Note that access to this collection should happen in service thread.
130     private final ArrayList<HdmiCecFeatureAction> mActions = new ArrayList<>();
131 
132     private final Handler mHandler = new Handler () {
133         @Override
134         public void handleMessage(Message msg) {
135             switch (msg.what) {
136                 case MSG_DISABLE_DEVICE_TIMEOUT:
137                     handleDisableDeviceTimeout();
138                     break;
139                 case MSG_USER_CONTROL_RELEASE_TIMEOUT:
140                     handleUserControlReleased();
141                     break;
142             }
143         }
144     };
145 
146     /**
147      * A callback interface to get notified when all pending action is cleared.
148      * It can be called when timeout happened.
149      */
150     interface PendingActionClearedCallback {
onCleared(HdmiCecLocalDevice device)151         void onCleared(HdmiCecLocalDevice device);
152     }
153 
154     protected PendingActionClearedCallback mPendingActionClearedCallback;
155 
HdmiCecLocalDevice(HdmiControlService service, int deviceType)156     protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
157         mService = service;
158         mDeviceType = deviceType;
159         mAddress = Constants.ADDR_UNREGISTERED;
160         mLock = service.getServiceLock();
161     }
162 
163     // Factory method that returns HdmiCecLocalDevice of corresponding type.
create(HdmiControlService service, int deviceType)164     static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) {
165         switch (deviceType) {
166         case HdmiDeviceInfo.DEVICE_TV:
167             return new HdmiCecLocalDeviceTv(service);
168         case HdmiDeviceInfo.DEVICE_PLAYBACK:
169             return new HdmiCecLocalDevicePlayback(service);
170         default:
171             return null;
172         }
173     }
174 
175     @ServiceThreadOnly
init()176     void init() {
177         assertRunOnServiceThread();
178         mPreferredAddress = getPreferredAddress();
179         mPendingActionClearedCallback = null;
180     }
181 
182     /**
183      * Called once a logical address of the local device is allocated.
184      */
onAddressAllocated(int logicalAddress, int reason)185     protected abstract void onAddressAllocated(int logicalAddress, int reason);
186 
187     /**
188      * Get the preferred logical address from system properties.
189      */
getPreferredAddress()190     protected abstract int getPreferredAddress();
191 
192     /**
193      * Set the preferred logical address to system properties.
194      */
setPreferredAddress(int addr)195     protected abstract void setPreferredAddress(int addr);
196 
197     /**
198      * Returns true if the TV input associated with the CEC device is ready
199      * to accept further processing such as input switching. This is used
200      * to buffer certain CEC commands and process it later if the input is not
201      * ready yet. For other types of local devices(non-TV), this method returns
202      * true by default to let the commands be processed right away.
203      */
isInputReady(int deviceId)204     protected boolean isInputReady(int deviceId) {
205         return true;
206     }
207 
208     /**
209      * Returns true if the local device allows the system to be put to standby.
210      * The default implementation returns true.
211      */
canGoToStandby()212     protected boolean canGoToStandby() {
213         return true;
214     }
215 
216     /**
217      * Dispatch incoming message.
218      *
219      * @param message incoming message
220      * @return true if consumed a message; otherwise, return false.
221      */
222     @ServiceThreadOnly
dispatchMessage(HdmiCecMessage message)223     boolean dispatchMessage(HdmiCecMessage message) {
224         assertRunOnServiceThread();
225         int dest = message.getDestination();
226         if (dest != mAddress && dest != Constants.ADDR_BROADCAST) {
227             return false;
228         }
229         // Cache incoming message. Note that it caches only white-listed one.
230         mCecMessageCache.cacheMessage(message);
231         return onMessage(message);
232     }
233 
234     @ServiceThreadOnly
onMessage(HdmiCecMessage message)235     protected final boolean onMessage(HdmiCecMessage message) {
236         assertRunOnServiceThread();
237         if (dispatchMessageToAction(message)) {
238             return true;
239         }
240         switch (message.getOpcode()) {
241             case Constants.MESSAGE_ACTIVE_SOURCE:
242                 return handleActiveSource(message);
243             case Constants.MESSAGE_INACTIVE_SOURCE:
244                 return handleInactiveSource(message);
245             case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
246                 return handleRequestActiveSource(message);
247             case Constants.MESSAGE_GET_MENU_LANGUAGE:
248                 return handleGetMenuLanguage(message);
249             case Constants.MESSAGE_SET_MENU_LANGUAGE:
250                 return handleSetMenuLanguage(message);
251             case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS:
252                 return handleGivePhysicalAddress();
253             case Constants.MESSAGE_GIVE_OSD_NAME:
254                 return handleGiveOsdName(message);
255             case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
256                 return handleGiveDeviceVendorId();
257             case Constants.MESSAGE_GET_CEC_VERSION:
258                 return handleGetCecVersion(message);
259             case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
260                 return handleReportPhysicalAddress(message);
261             case Constants.MESSAGE_ROUTING_CHANGE:
262                 return handleRoutingChange(message);
263             case Constants.MESSAGE_ROUTING_INFORMATION:
264                 return handleRoutingInformation(message);
265             case Constants.MESSAGE_INITIATE_ARC:
266                 return handleInitiateArc(message);
267             case Constants.MESSAGE_TERMINATE_ARC:
268                 return handleTerminateArc(message);
269             case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
270                 return handleSetSystemAudioMode(message);
271             case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
272                 return handleSystemAudioModeStatus(message);
273             case Constants.MESSAGE_REPORT_AUDIO_STATUS:
274                 return handleReportAudioStatus(message);
275             case Constants.MESSAGE_STANDBY:
276                 return handleStandby(message);
277             case Constants.MESSAGE_TEXT_VIEW_ON:
278                 return handleTextViewOn(message);
279             case Constants.MESSAGE_IMAGE_VIEW_ON:
280                 return handleImageViewOn(message);
281             case Constants.MESSAGE_USER_CONTROL_PRESSED:
282                 return handleUserControlPressed(message);
283             case Constants.MESSAGE_USER_CONTROL_RELEASED:
284                 return handleUserControlReleased();
285             case Constants.MESSAGE_SET_STREAM_PATH:
286                 return handleSetStreamPath(message);
287             case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
288                 return handleGiveDevicePowerStatus(message);
289             case Constants.MESSAGE_MENU_REQUEST:
290                 return handleMenuRequest(message);
291             case Constants.MESSAGE_MENU_STATUS:
292                 return handleMenuStatus(message);
293             case Constants.MESSAGE_VENDOR_COMMAND:
294                 return handleVendorCommand(message);
295             case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID:
296                 return handleVendorCommandWithId(message);
297             case Constants.MESSAGE_SET_OSD_NAME:
298                 return handleSetOsdName(message);
299             case Constants.MESSAGE_RECORD_TV_SCREEN:
300                 return handleRecordTvScreen(message);
301             case Constants.MESSAGE_TIMER_CLEARED_STATUS:
302                 return handleTimerClearedStatus(message);
303             case Constants.MESSAGE_REPORT_POWER_STATUS:
304                 return handleReportPowerStatus(message);
305             case Constants.MESSAGE_TIMER_STATUS:
306                 return handleTimerStatus(message);
307             case Constants.MESSAGE_RECORD_STATUS:
308                 return handleRecordStatus(message);
309             default:
310                 return false;
311         }
312     }
313 
314     @ServiceThreadOnly
dispatchMessageToAction(HdmiCecMessage message)315     private boolean dispatchMessageToAction(HdmiCecMessage message) {
316         assertRunOnServiceThread();
317         boolean processed = false;
318         // Use copied action list in that processCommand may remove itself.
319         for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) {
320             // Iterates all actions to check whether incoming message is consumed.
321             boolean result = action.processCommand(message);
322             processed = processed || result;
323         }
324         return processed;
325     }
326 
327     @ServiceThreadOnly
handleGivePhysicalAddress()328     protected boolean handleGivePhysicalAddress() {
329         assertRunOnServiceThread();
330 
331         int physicalAddress = mService.getPhysicalAddress();
332         HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
333                 mAddress, physicalAddress, mDeviceType);
334         mService.sendCecCommand(cecMessage);
335         return true;
336     }
337 
338     @ServiceThreadOnly
handleGiveDeviceVendorId()339     protected boolean handleGiveDeviceVendorId() {
340         assertRunOnServiceThread();
341         int vendorId = mService.getVendorId();
342         HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
343                 mAddress, vendorId);
344         mService.sendCecCommand(cecMessage);
345         return true;
346     }
347 
348     @ServiceThreadOnly
handleGetCecVersion(HdmiCecMessage message)349     protected boolean handleGetCecVersion(HdmiCecMessage message) {
350         assertRunOnServiceThread();
351         int version = mService.getCecVersion();
352         HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(),
353                 message.getSource(), version);
354         mService.sendCecCommand(cecMessage);
355         return true;
356     }
357 
358     @ServiceThreadOnly
handleActiveSource(HdmiCecMessage message)359     protected boolean handleActiveSource(HdmiCecMessage message) {
360         return false;
361     }
362 
363     @ServiceThreadOnly
handleInactiveSource(HdmiCecMessage message)364     protected boolean handleInactiveSource(HdmiCecMessage message) {
365         return false;
366     }
367 
368     @ServiceThreadOnly
handleRequestActiveSource(HdmiCecMessage message)369     protected boolean handleRequestActiveSource(HdmiCecMessage message) {
370         return false;
371     }
372 
373     @ServiceThreadOnly
handleGetMenuLanguage(HdmiCecMessage message)374     protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
375         assertRunOnServiceThread();
376         Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
377         // 'return false' will cause to reply with <Feature Abort>.
378         return false;
379     }
380 
381     @ServiceThreadOnly
handleSetMenuLanguage(HdmiCecMessage message)382     protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
383         assertRunOnServiceThread();
384         Slog.w(TAG, "Only Playback device can handle <Set Menu Language>:" + message.toString());
385         // 'return false' will cause to reply with <Feature Abort>.
386         return false;
387     }
388 
389     @ServiceThreadOnly
handleGiveOsdName(HdmiCecMessage message)390     protected boolean handleGiveOsdName(HdmiCecMessage message) {
391         assertRunOnServiceThread();
392         // Note that since this method is called after logical address allocation is done,
393         // mDeviceInfo should not be null.
394         HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand(
395                 mAddress, message.getSource(), mDeviceInfo.getDisplayName());
396         if (cecMessage != null) {
397             mService.sendCecCommand(cecMessage);
398         } else {
399             Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName());
400         }
401         return true;
402     }
403 
handleRoutingChange(HdmiCecMessage message)404     protected boolean handleRoutingChange(HdmiCecMessage message) {
405         return false;
406     }
407 
handleRoutingInformation(HdmiCecMessage message)408     protected boolean handleRoutingInformation(HdmiCecMessage message) {
409         return false;
410     }
411 
handleReportPhysicalAddress(HdmiCecMessage message)412     protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
413         return false;
414     }
415 
handleSystemAudioModeStatus(HdmiCecMessage message)416     protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
417         return false;
418     }
419 
handleSetSystemAudioMode(HdmiCecMessage message)420     protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
421         return false;
422     }
423 
handleTerminateArc(HdmiCecMessage message)424     protected boolean handleTerminateArc(HdmiCecMessage message) {
425         return false;
426     }
427 
handleInitiateArc(HdmiCecMessage message)428     protected boolean handleInitiateArc(HdmiCecMessage message) {
429         return false;
430     }
431 
handleReportAudioStatus(HdmiCecMessage message)432     protected boolean handleReportAudioStatus(HdmiCecMessage message) {
433         return false;
434     }
435 
436     @ServiceThreadOnly
handleStandby(HdmiCecMessage message)437     protected boolean handleStandby(HdmiCecMessage message) {
438         assertRunOnServiceThread();
439         // Seq #12
440         if (mService.isControlEnabled() && !mService.isProhibitMode()
441                 && mService.isPowerOnOrTransient()) {
442             mService.standby();
443             return true;
444         }
445         return false;
446     }
447 
448     @ServiceThreadOnly
handleUserControlPressed(HdmiCecMessage message)449     protected boolean handleUserControlPressed(HdmiCecMessage message) {
450         assertRunOnServiceThread();
451         mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
452         if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) {
453             mService.standby();
454             return true;
455         } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) {
456             mService.wakeUp();
457             return true;
458         }
459 
460         final long downTime = SystemClock.uptimeMillis();
461         final byte[] params = message.getParams();
462         final int keycode = HdmiCecKeycode.cecKeycodeAndParamsToAndroidKey(params);
463         int keyRepeatCount = 0;
464         if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
465             if (keycode == mLastKeycode) {
466                 keyRepeatCount = mLastKeyRepeatCount + 1;
467             } else {
468                 injectKeyEvent(downTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
469             }
470         }
471         mLastKeycode = keycode;
472         mLastKeyRepeatCount = keyRepeatCount;
473 
474         if (keycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
475             injectKeyEvent(downTime, KeyEvent.ACTION_DOWN, keycode, keyRepeatCount);
476             mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT),
477                     FOLLOWER_SAFETY_TIMEOUT);
478             return true;
479         }
480         return false;
481     }
482 
483     @ServiceThreadOnly
handleUserControlReleased()484     protected boolean handleUserControlReleased() {
485         assertRunOnServiceThread();
486         mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
487         mLastKeyRepeatCount = 0;
488         if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) {
489             final long upTime = SystemClock.uptimeMillis();
490             injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0);
491             mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
492             return true;
493         }
494         return false;
495     }
496 
injectKeyEvent(long time, int action, int keycode, int repeat)497     static void injectKeyEvent(long time, int action, int keycode, int repeat) {
498         KeyEvent keyEvent = KeyEvent.obtain(time, time, action, keycode,
499                 repeat, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM,
500                 InputDevice.SOURCE_HDMI, null);
501         InputManager.getInstance().injectInputEvent(keyEvent,
502                 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
503         keyEvent.recycle();
504    }
505 
isPowerOnOrToggleCommand(HdmiCecMessage message)506     static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
507         byte[] params = message.getParams();
508         return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
509                 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
510                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION
511                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
512     }
513 
isPowerOffOrToggleCommand(HdmiCecMessage message)514     static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
515         byte[] params = message.getParams();
516         return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
517                 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
518                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
519                         || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
520     }
521 
handleTextViewOn(HdmiCecMessage message)522     protected boolean handleTextViewOn(HdmiCecMessage message) {
523         return false;
524     }
525 
handleImageViewOn(HdmiCecMessage message)526     protected boolean handleImageViewOn(HdmiCecMessage message) {
527         return false;
528     }
529 
handleSetStreamPath(HdmiCecMessage message)530     protected boolean handleSetStreamPath(HdmiCecMessage message) {
531         return false;
532     }
533 
handleGiveDevicePowerStatus(HdmiCecMessage message)534     protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
535         mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus(
536                 mAddress, message.getSource(), mService.getPowerStatus()));
537         return true;
538     }
539 
handleMenuRequest(HdmiCecMessage message)540     protected boolean handleMenuRequest(HdmiCecMessage message) {
541         // Always report menu active to receive Remote Control.
542         mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
543                 mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED));
544         return true;
545     }
546 
handleMenuStatus(HdmiCecMessage message)547     protected boolean handleMenuStatus(HdmiCecMessage message) {
548         return false;
549     }
550 
handleVendorCommand(HdmiCecMessage message)551     protected boolean handleVendorCommand(HdmiCecMessage message) {
552         if (!mService.invokeVendorCommandListenersOnReceived(mDeviceType, message.getSource(),
553                 message.getDestination(), message.getParams(), false)) {
554             // Vendor command listener may not have been registered yet. Respond with
555             // <Feature Abort> [NOT_IN_CORRECT_MODE] so that the sender can try again later.
556             mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
557         }
558         return true;
559     }
560 
handleVendorCommandWithId(HdmiCecMessage message)561     protected boolean handleVendorCommandWithId(HdmiCecMessage message) {
562         byte[] params = message.getParams();
563         int vendorId = HdmiUtils.threeBytesToInt(params);
564         if (vendorId == mService.getVendorId()) {
565             if (!mService.invokeVendorCommandListenersOnReceived(mDeviceType, message.getSource(),
566                     message.getDestination(), params, true)) {
567                 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
568             }
569         } else if (message.getDestination() != Constants.ADDR_BROADCAST &&
570                 message.getSource() != Constants.ADDR_UNREGISTERED) {
571             Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
572             mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
573         } else {
574             Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
575         }
576         return true;
577     }
578 
sendStandby(int deviceId)579     protected void sendStandby(int deviceId) {
580         // Do nothing.
581     }
582 
handleSetOsdName(HdmiCecMessage message)583     protected boolean handleSetOsdName(HdmiCecMessage message) {
584         // The default behavior of <Set Osd Name> is doing nothing.
585         return true;
586     }
587 
handleRecordTvScreen(HdmiCecMessage message)588     protected boolean handleRecordTvScreen(HdmiCecMessage message) {
589         // The default behavior of <Record TV Screen> is replying <Feature Abort> with
590         // "Cannot provide source".
591         mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
592         return true;
593     }
594 
handleTimerClearedStatus(HdmiCecMessage message)595     protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
596         return false;
597     }
598 
handleReportPowerStatus(HdmiCecMessage message)599     protected boolean handleReportPowerStatus(HdmiCecMessage message) {
600         return false;
601     }
602 
handleTimerStatus(HdmiCecMessage message)603     protected boolean handleTimerStatus(HdmiCecMessage message) {
604         return false;
605     }
606 
handleRecordStatus(HdmiCecMessage message)607     protected boolean handleRecordStatus(HdmiCecMessage message) {
608         return false;
609     }
610 
611     @ServiceThreadOnly
handleAddressAllocated(int logicalAddress, int reason)612     final void handleAddressAllocated(int logicalAddress, int reason) {
613         assertRunOnServiceThread();
614         mAddress = mPreferredAddress = logicalAddress;
615         onAddressAllocated(logicalAddress, reason);
616         setPreferredAddress(logicalAddress);
617     }
618 
getType()619     int getType() {
620         return mDeviceType;
621     }
622 
623     @ServiceThreadOnly
getDeviceInfo()624     HdmiDeviceInfo getDeviceInfo() {
625         assertRunOnServiceThread();
626         return mDeviceInfo;
627     }
628 
629     @ServiceThreadOnly
setDeviceInfo(HdmiDeviceInfo info)630     void setDeviceInfo(HdmiDeviceInfo info) {
631         assertRunOnServiceThread();
632         mDeviceInfo = info;
633     }
634 
635     // Returns true if the logical address is same as the argument.
636     @ServiceThreadOnly
isAddressOf(int addr)637     boolean isAddressOf(int addr) {
638         assertRunOnServiceThread();
639         return addr == mAddress;
640     }
641 
642     // Resets the logical address to unregistered(15), meaning the logical device is invalid.
643     @ServiceThreadOnly
clearAddress()644     void clearAddress() {
645         assertRunOnServiceThread();
646         mAddress = Constants.ADDR_UNREGISTERED;
647     }
648 
649     @ServiceThreadOnly
addAndStartAction(final HdmiCecFeatureAction action)650     void addAndStartAction(final HdmiCecFeatureAction action) {
651         assertRunOnServiceThread();
652         mActions.add(action);
653         if (mService.isPowerStandby() || !mService.isAddressAllocated()) {
654             Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action);
655             return;
656         }
657         action.start();
658     }
659 
660     @ServiceThreadOnly
startQueuedActions()661     void startQueuedActions() {
662         assertRunOnServiceThread();
663         for (HdmiCecFeatureAction action : mActions) {
664             if (!action.started()) {
665                 Slog.i(TAG, "Starting queued action:" + action);
666                 action.start();
667             }
668         }
669     }
670 
671     // See if we have an action of a given type in progress.
672     @ServiceThreadOnly
hasAction(final Class<T> clazz)673     <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) {
674         assertRunOnServiceThread();
675         for (HdmiCecFeatureAction action : mActions) {
676             if (action.getClass().equals(clazz)) {
677                 return true;
678             }
679         }
680         return false;
681     }
682 
683     // Returns all actions matched with given class type.
684     @ServiceThreadOnly
getActions(final Class<T> clazz)685     <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
686         assertRunOnServiceThread();
687         List<T> actions = Collections.<T>emptyList();
688         for (HdmiCecFeatureAction action : mActions) {
689             if (action.getClass().equals(clazz)) {
690                 if (actions.isEmpty()) {
691                     actions = new ArrayList<T>();
692                 }
693                 actions.add((T) action);
694             }
695         }
696         return actions;
697     }
698 
699     /**
700      * Remove the given {@link HdmiCecFeatureAction} object from the action queue.
701      *
702      * @param action {@link HdmiCecFeatureAction} to remove
703      */
704     @ServiceThreadOnly
removeAction(final HdmiCecFeatureAction action)705     void removeAction(final HdmiCecFeatureAction action) {
706         assertRunOnServiceThread();
707         action.finish(false);
708         mActions.remove(action);
709         checkIfPendingActionsCleared();
710     }
711 
712     // Remove all actions matched with the given Class type.
713     @ServiceThreadOnly
removeAction(final Class<T> clazz)714     <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
715         assertRunOnServiceThread();
716         removeActionExcept(clazz, null);
717     }
718 
719     // Remove all actions matched with the given Class type besides |exception|.
720     @ServiceThreadOnly
removeActionExcept(final Class<T> clazz, final HdmiCecFeatureAction exception)721     <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz,
722             final HdmiCecFeatureAction exception) {
723         assertRunOnServiceThread();
724         Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
725         while (iter.hasNext()) {
726             HdmiCecFeatureAction action = iter.next();
727             if (action != exception && action.getClass().equals(clazz)) {
728                 action.finish(false);
729                 iter.remove();
730             }
731         }
732         checkIfPendingActionsCleared();
733     }
734 
checkIfPendingActionsCleared()735     protected void checkIfPendingActionsCleared() {
736         if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
737             PendingActionClearedCallback callback = mPendingActionClearedCallback;
738             // To prevent from calling the callback again during handling the callback itself.
739             mPendingActionClearedCallback = null;
740             callback.onCleared(this);
741         }
742     }
743 
assertRunOnServiceThread()744     protected void assertRunOnServiceThread() {
745         if (Looper.myLooper() != mService.getServiceLooper()) {
746             throw new IllegalStateException("Should run on service thread.");
747         }
748     }
749 
setAutoDeviceOff(boolean enabled)750     void setAutoDeviceOff(boolean enabled) {
751     }
752 
753     /**
754      * Called when a hot-plug event issued.
755      *
756      * @param portId id of port where a hot-plug event happened
757      * @param connected whether to connected or not on the event
758      */
onHotplug(int portId, boolean connected)759     void onHotplug(int portId, boolean connected) {
760     }
761 
getService()762     final HdmiControlService getService() {
763         return mService;
764     }
765 
766     @ServiceThreadOnly
isConnectedToArcPort(int path)767     final boolean isConnectedToArcPort(int path) {
768         assertRunOnServiceThread();
769         return mService.isConnectedToArcPort(path);
770     }
771 
getActiveSource()772     ActiveSource getActiveSource() {
773         synchronized (mLock) {
774             return mActiveSource;
775         }
776     }
777 
setActiveSource(ActiveSource newActive)778     void setActiveSource(ActiveSource newActive) {
779         setActiveSource(newActive.logicalAddress, newActive.physicalAddress);
780     }
781 
setActiveSource(HdmiDeviceInfo info)782     void setActiveSource(HdmiDeviceInfo info) {
783         setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress());
784     }
785 
setActiveSource(int logicalAddress, int physicalAddress)786     void setActiveSource(int logicalAddress, int physicalAddress) {
787         synchronized (mLock) {
788             mActiveSource.logicalAddress = logicalAddress;
789             mActiveSource.physicalAddress = physicalAddress;
790         }
791         mService.setLastInputForMhl(Constants.INVALID_PORT_ID);
792     }
793 
getActivePath()794     int getActivePath() {
795         synchronized (mLock) {
796             return mActiveRoutingPath;
797         }
798     }
799 
setActivePath(int path)800     void setActivePath(int path) {
801         synchronized (mLock) {
802             mActiveRoutingPath = path;
803         }
804         mService.setActivePortId(pathToPortId(path));
805     }
806 
807     /**
808      * Returns the ID of the active HDMI port. The active port is the one that has the active
809      * routing path connected to it directly or indirectly under the device hierarchy.
810      */
getActivePortId()811     int getActivePortId() {
812         synchronized (mLock) {
813             return mService.pathToPortId(mActiveRoutingPath);
814         }
815     }
816 
817     /**
818      * Update the active port.
819      *
820      * @param portId the new active port id
821      */
setActivePortId(int portId)822     void setActivePortId(int portId) {
823         // We update active routing path instead, since we get the active port id from
824         // the active routing path.
825         setActivePath(mService.portIdToPath(portId));
826     }
827 
828     @ServiceThreadOnly
getCecMessageCache()829     HdmiCecMessageCache getCecMessageCache() {
830         assertRunOnServiceThread();
831         return mCecMessageCache;
832     }
833 
834     @ServiceThreadOnly
pathToPortId(int newPath)835     int pathToPortId(int newPath) {
836         assertRunOnServiceThread();
837         return mService.pathToPortId(newPath);
838     }
839 
840     /**
841      * Called when the system goes to standby mode.
842      *
843      * @param initiatedByCec true if this power sequence is initiated
844      *        by the reception the CEC messages like &lt;Standby&gt;
845      * @param standbyAction Intent action that drives the standby process,
846      *        either {@link HdmiControlService#STANDBY_SCREEN_OFF} or
847      *        {@link HdmiControlService#STANDBY_SHUTDOWN}
848      */
onStandby(boolean initiatedByCec, int standbyAction)849     protected void onStandby(boolean initiatedByCec, int standbyAction) {}
850 
851     /**
852      * Disable device. {@code callback} is used to get notified when all pending
853      * actions are completed or timeout is issued.
854      *
855      * @param initiatedByCec true if this sequence is initiated
856      *        by the reception the CEC messages like &lt;Standby&gt;
857      * @param originalCallback callback interface to get notified when all pending actions are
858      *        cleared
859      */
disableDevice(boolean initiatedByCec, final PendingActionClearedCallback originalCallback)860     protected void disableDevice(boolean initiatedByCec,
861             final PendingActionClearedCallback originalCallback) {
862         mPendingActionClearedCallback = new PendingActionClearedCallback() {
863             @Override
864             public void onCleared(HdmiCecLocalDevice device) {
865                 mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
866                 originalCallback.onCleared(device);
867             }
868         };
869         mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
870                 DEVICE_CLEANUP_TIMEOUT);
871     }
872 
873     @ServiceThreadOnly
handleDisableDeviceTimeout()874     private void handleDisableDeviceTimeout() {
875         assertRunOnServiceThread();
876 
877         // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them.
878         // onCleard will be called at the last action's finish method.
879         Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
880         while (iter.hasNext()) {
881             HdmiCecFeatureAction action = iter.next();
882             action.finish(false);
883             iter.remove();
884         }
885         if (mPendingActionClearedCallback != null) {
886             mPendingActionClearedCallback.onCleared(this);
887         }
888     }
889 
890     /**
891      * Send a key event to other CEC device. The logical address of target device will be given by
892      * {@link #findKeyReceiverAddress}.
893      *
894      * @param keyCode key code defined in {@link android.view.KeyEvent}
895      * @param isPressed {@code true} for key down event
896      * @see #findKeyReceiverAddress()
897      */
898     @ServiceThreadOnly
sendKeyEvent(int keyCode, boolean isPressed)899     protected void sendKeyEvent(int keyCode, boolean isPressed) {
900         assertRunOnServiceThread();
901         if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
902             Slog.w(TAG, "Unsupported key: " + keyCode);
903             return;
904         }
905         List<SendKeyAction> action = getActions(SendKeyAction.class);
906         int logicalAddress = findKeyReceiverAddress();
907         if (logicalAddress == Constants.ADDR_INVALID || logicalAddress == mAddress) {
908             // Don't send key event to invalid device or itself.
909             Slog.w(TAG, "Discard key event: " + keyCode + ", pressed:" + isPressed
910                     + ", receiverAddr=" + logicalAddress);
911         } else if (!action.isEmpty()) {
912             action.get(0).processKeyEvent(keyCode, isPressed);
913         } else if (isPressed) {
914             addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
915         }
916     }
917 
918     /**
919      * Returns the logical address of the device which will receive key events via
920      * {@link #sendKeyEvent}.
921      *
922      * @see #sendKeyEvent(int, boolean)
923      */
findKeyReceiverAddress()924     protected int findKeyReceiverAddress() {
925         Slog.w(TAG, "findKeyReceiverAddress is not implemented");
926         return Constants.ADDR_INVALID;
927     }
928 
sendUserControlPressedAndReleased(int targetAddress, int cecKeycode)929     void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) {
930         mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlPressed(
931                 mAddress, targetAddress, cecKeycode));
932         mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlReleased(
933                 mAddress, targetAddress));
934     }
935 
936     /**
937      * Dump internal status of HdmiCecLocalDevice object.
938      */
dump(final IndentingPrintWriter pw)939     protected void dump(final IndentingPrintWriter pw) {
940         pw.println("mDeviceType: " + mDeviceType);
941         pw.println("mAddress: " + mAddress);
942         pw.println("mPreferredAddress: " + mPreferredAddress);
943         pw.println("mDeviceInfo: " + mDeviceInfo);
944         pw.println("mActiveSource: " + mActiveSource);
945         pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
946     }
947 }
948