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