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