blob: 414f6bbfb9955d6c7104f9930311b0ad0470fdf0 [file] [log] [blame]
Jinsuk Kim2918e9e2014-05-20 16:45:45 +09001/*
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
17package com.android.server.hdmi;
18
Amy1d0b1372018-05-24 14:36:25 -070019import android.annotation.Nullable;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090020import android.hardware.hdmi.HdmiDeviceInfo;
shubangd932cb52018-09-14 17:55:16 -070021import android.hardware.hdmi.IHdmiControlCallback;
Terry Heo3e1564e2014-08-12 14:41:00 +090022import android.hardware.input.InputManager;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090023import android.os.Handler;
Jungshik Jang79c58a42014-06-16 16:45:36 +090024import android.os.Looper;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090025import android.os.Message;
shubangd932cb52018-09-14 17:55:16 -070026import android.os.RemoteException;
Terry Heo3e1564e2014-08-12 14:41:00 +090027import android.os.SystemClock;
Jungshik Jang092b4452014-06-11 15:19:17 +090028import android.util.Slog;
Terry Heo3e1564e2014-08-12 14:41:00 +090029import android.view.InputDevice;
30import android.view.KeyCharacterMap;
31import android.view.KeyEvent;
Amyaefab642018-08-22 19:10:14 -070032
Jungshik Jang79c58a42014-06-16 16:45:36 +090033import com.android.internal.annotations.GuardedBy;
Terry Heo959d2db2014-08-28 16:45:41 +090034import com.android.internal.util.IndentingPrintWriter;
Amy79b54e92018-09-11 17:02:28 -070035import com.android.server.hdmi.Constants.LocalActivePort;
Jungshik Janga5b74142014-06-23 18:03:10 +090036import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Amy1d0b1372018-05-24 14:36:25 -070037import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
Amyaefab642018-08-22 19:10:14 -070038
Jungshik Jang79c58a42014-06-16 16:45:36 +090039import java.util.ArrayList;
Jinsuk Kim43c23e22014-07-29 13:59:14 +090040import java.util.Collections;
Jungshik Jang79c58a42014-06-16 16:45:36 +090041import java.util.Iterator;
Jungshik Jang79c58a42014-06-16 16:45:36 +090042import java.util.List;
43
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090044/**
Nick Chalkof32fcea2018-07-17 16:17:40 -070045 * Class that models a logical CEC device hosted in this system. Handles initialization, CEC
46 * commands that call for actions customized per device type.
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090047 */
48abstract class HdmiCecLocalDevice {
Jungshik Jang092b4452014-06-11 15:19:17 +090049 private static final String TAG = "HdmiCecLocalDevice";
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090050
Jungshik Jang4fc1d102014-07-09 19:24:50 +090051 private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1;
Terry Heo3e1564e2014-08-12 14:41:00 +090052 private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090053 // 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;
Terry Heo3e1564e2014-08-12 14:41:00 +090056 // 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;
Jungshik Jang4fc1d102014-07-09 19:24:50 +090059
Jungshik Jang3ee65722014-06-03 16:22:30 +090060 protected final HdmiControlService mService;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090061 protected final int mDeviceType;
62 protected int mAddress;
63 protected int mPreferredAddress;
Amy0361dd72018-11-07 16:54:25 -080064 @GuardedBy("mLock")
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090065 protected HdmiDeviceInfo mDeviceInfo;
Terry Heo3e1564e2014-08-12 14:41:00 +090066 protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
67 protected int mLastKeyRepeatCount = 0;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090068
Jinsuk Kim72b7d732014-07-24 09:15:35 +090069 static class ActiveSource {
70 int logicalAddress;
71 int physicalAddress;
72
Jinsuk Kim43c23e22014-07-29 13:59:14 +090073 public ActiveSource() {
74 invalidate();
75 }
Nick Chalkof32fcea2018-07-17 16:17:40 -070076
Jinsuk Kim72b7d732014-07-24 09:15:35 +090077 public ActiveSource(int logical, int physical) {
78 logicalAddress = logical;
79 physicalAddress = physical;
80 }
Nick Chalkof32fcea2018-07-17 16:17:40 -070081
Jinsuk Kim04f813c2015-04-02 16:56:49 +090082 public static ActiveSource of(ActiveSource source) {
83 return new ActiveSource(source.logicalAddress, source.physicalAddress);
84 }
Nick Chalkof32fcea2018-07-17 16:17:40 -070085
Jinsuk Kim72b7d732014-07-24 09:15:35 +090086 public static ActiveSource of(int logical, int physical) {
87 return new ActiveSource(logical, physical);
88 }
Nick Chalkof32fcea2018-07-17 16:17:40 -070089
Jinsuk Kim72b7d732014-07-24 09:15:35 +090090 public boolean isValid() {
91 return HdmiUtils.isValidAddress(logicalAddress);
92 }
Nick Chalkof32fcea2018-07-17 16:17:40 -070093
Jinsuk Kim43c23e22014-07-29 13:59:14 +090094 public void invalidate() {
95 logicalAddress = Constants.ADDR_INVALID;
96 physicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
97 }
Nick Chalkof32fcea2018-07-17 16:17:40 -070098
Jinsuk Kim72b7d732014-07-24 09:15:35 +090099 public boolean equals(int logical, int physical) {
100 return logicalAddress == logical && physicalAddress == physical;
101 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700102
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900103 @Override
104 public boolean equals(Object obj) {
105 if (obj instanceof ActiveSource) {
106 ActiveSource that = (ActiveSource) obj;
Nick Chalkof32fcea2018-07-17 16:17:40 -0700107 return that.logicalAddress == logicalAddress
108 && that.physicalAddress == physicalAddress;
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900109 }
110 return false;
111 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700112
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900113 @Override
114 public int hashCode() {
115 return logicalAddress * 29 + physicalAddress;
116 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700117
Terry Heo959d2db2014-08-28 16:45:41 +0900118 @Override
119 public String toString() {
120 StringBuffer s = new StringBuffer();
Nick Chalkof32fcea2018-07-17 16:17:40 -0700121 String logicalAddressString =
122 (logicalAddress == Constants.ADDR_INVALID)
123 ? "invalid"
124 : String.format("0x%02x", logicalAddress);
Jinsuk Kim04f813c2015-04-02 16:56:49 +0900125 s.append("(").append(logicalAddressString);
Nick Chalkof32fcea2018-07-17 16:17:40 -0700126 String physicalAddressString =
127 (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS)
128 ? "invalid"
129 : String.format("0x%04x", physicalAddress);
Jinsuk Kim04f813c2015-04-02 16:56:49 +0900130 s.append(", ").append(physicalAddressString).append(")");
Terry Heo959d2db2014-08-28 16:45:41 +0900131 return s.toString();
132 }
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900133 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900134
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
Jungshik Jang79c58a42014-06-16 16:45:36 +0900141 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.
Jungshik Jang5352081c2014-09-22 15:14:49 +0900146 private final ArrayList<HdmiCecFeatureAction> mActions = new ArrayList<>();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900147
Nick Chalkof32fcea2018-07-17 16:17:40 -0700148 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 };
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900162
163 /**
Nick Chalkof32fcea2018-07-17 16:17:40 -0700164 * A callback interface to get notified when all pending action is cleared. It can be called
165 * when timeout happened.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900166 */
167 interface PendingActionClearedCallback {
168 void onCleared(HdmiCecLocalDevice device);
169 }
170
171 protected PendingActionClearedCallback mPendingActionClearedCallback;
172
Jungshik Jang3ee65722014-06-03 16:22:30 +0900173 protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
174 mService = service;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900175 mDeviceType = deviceType;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900176 mAddress = Constants.ADDR_UNREGISTERED;
Jungshik Jang79c58a42014-06-16 16:45:36 +0900177 mLock = service.getServiceLock();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900178 }
179
180 // Factory method that returns HdmiCecLocalDevice of corresponding type.
Jungshik Jang3ee65722014-06-03 16:22:30 +0900181 static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) {
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900182 switch (deviceType) {
Nick Chalkof32fcea2018-07-17 16:17:40 -0700183 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;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900191 }
192 }
193
Jungshik Janga5b74142014-06-23 18:03:10 +0900194 @ServiceThreadOnly
Jungshik Jang3ee65722014-06-03 16:22:30 +0900195 void init() {
Jungshik Janga5b74142014-06-23 18:03:10 +0900196 assertRunOnServiceThread();
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900197 mPreferredAddress = getPreferredAddress();
Jinsuk Kim7e4b4802015-04-20 13:17:13 +0900198 mPendingActionClearedCallback = null;
Jungshik Jang3ee65722014-06-03 16:22:30 +0900199 }
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900200
Nick Chalkof32fcea2018-07-17 16:17:40 -0700201 /** Called once a logical address of the local device is allocated. */
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900202 protected abstract void onAddressAllocated(int logicalAddress, int reason);
Jungshik Jang8b308d92014-05-29 21:52:28 +0900203
Nick Chalkof32fcea2018-07-17 16:17:40 -0700204 /** Get the preferred logical address from system properties. */
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900205 protected abstract int getPreferredAddress();
206
Nick Chalkof32fcea2018-07-17 16:17:40 -0700207 /** Set the preferred logical address to system properties. */
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900208 protected abstract void setPreferredAddress(int addr);
209
210 /**
Nick Chalkof32fcea2018-07-17 16:17:40 -0700211 * 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.
Jinsuk Kim6e26f7f2015-01-07 16:45:14 +0900217 */
218 protected boolean isInputReady(int deviceId) {
219 return true;
220 }
221
222 /**
Jinsuk Kime26d8332015-01-09 08:55:41 +0900223 * Returns true if the local device allows the system to be put to standby.
Nick Chalkof32fcea2018-07-17 16:17:40 -0700224 *
225 * <p>The default implementation returns true.
Jinsuk Kime26d8332015-01-09 08:55:41 +0900226 */
227 protected boolean canGoToStandby() {
228 return true;
229 }
230
231 /**
Jungshik Jang092b4452014-06-11 15:19:17 +0900232 * Dispatch incoming message.
233 *
234 * @param message incoming message
235 * @return true if consumed a message; otherwise, return false.
236 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900237 @ServiceThreadOnly
Yuncheol Heo25c20292014-07-31 17:59:39 +0900238 boolean dispatchMessage(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900239 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900240 int dest = message.getDestination();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900241 if (dest != mAddress && dest != Constants.ADDR_BROADCAST) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900242 return false;
243 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900244 // Cache incoming message. Note that it caches only white-listed one.
245 mCecMessageCache.cacheMessage(message);
Jungshik Jang092b4452014-06-11 15:19:17 +0900246 return onMessage(message);
247 }
248
Jungshik Janga5b74142014-06-23 18:03:10 +0900249 @ServiceThreadOnly
Jungshik Jang60cffce2014-06-12 18:03:04 +0900250 protected final boolean onMessage(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900251 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900252 if (dispatchMessageToAction(message)) {
253 return true;
254 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900255 switch (message.getOpcode()) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900256 case Constants.MESSAGE_ACTIVE_SOURCE:
Jinsuk Kim83335712014-06-24 07:57:00 +0900257 return handleActiveSource(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900258 case Constants.MESSAGE_INACTIVE_SOURCE:
Jinsuk Kim83335712014-06-24 07:57:00 +0900259 return handleInactiveSource(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900260 case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE:
Jinsuk Kim83335712014-06-24 07:57:00 +0900261 return handleRequestActiveSource(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900262 case Constants.MESSAGE_GET_MENU_LANGUAGE:
Jungshik Jang092b4452014-06-11 15:19:17 +0900263 return handleGetMenuLanguage(message);
Terry Heo795415b2014-10-01 15:03:53 +0900264 case Constants.MESSAGE_SET_MENU_LANGUAGE:
265 return handleSetMenuLanguage(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900266 case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS:
Amy1d0b1372018-05-24 14:36:25 -0700267 return handleGivePhysicalAddress(null);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900268 case Constants.MESSAGE_GIVE_OSD_NAME:
Jungshik Jang092b4452014-06-11 15:19:17 +0900269 return handleGiveOsdName(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900270 case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID:
Amy1d0b1372018-05-24 14:36:25 -0700271 return handleGiveDeviceVendorId(null);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900272 case Constants.MESSAGE_GET_CEC_VERSION:
Jungshik Jang092b4452014-06-11 15:19:17 +0900273 return handleGetCecVersion(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900274 case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS:
Jungshik Jang60cffce2014-06-12 18:03:04 +0900275 return handleReportPhysicalAddress(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900276 case Constants.MESSAGE_ROUTING_CHANGE:
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900277 return handleRoutingChange(message);
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900278 case Constants.MESSAGE_ROUTING_INFORMATION:
279 return handleRoutingInformation(message);
Amy34ee91d2018-06-04 12:00:09 -0700280 case Constants.MESSAGE_REQUEST_ARC_INITIATION:
281 return handleRequestArcInitiate(message);
282 case Constants.MESSAGE_REQUEST_ARC_TERMINATION:
283 return handleRequestArcTermination(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900284 case Constants.MESSAGE_INITIATE_ARC:
Jungshik Jang79c58a42014-06-16 16:45:36 +0900285 return handleInitiateArc(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900286 case Constants.MESSAGE_TERMINATE_ARC:
Jungshik Jang79c58a42014-06-16 16:45:36 +0900287 return handleTerminateArc(message);
Amy34ee91d2018-06-04 12:00:09 -0700288 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);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900294 case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
Jungshik Jang79c58a42014-06-16 16:45:36 +0900295 return handleSetSystemAudioMode(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900296 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
Jungshik Jang79c58a42014-06-16 16:45:36 +0900297 return handleSystemAudioModeStatus(message);
Amy34ee91d2018-06-04 12:00:09 -0700298 case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
299 return handleGiveSystemAudioModeStatus(message);
300 case Constants.MESSAGE_GIVE_AUDIO_STATUS:
301 return handleGiveAudioStatus(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900302 case Constants.MESSAGE_REPORT_AUDIO_STATUS:
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900303 return handleReportAudioStatus(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900304 case Constants.MESSAGE_STANDBY:
Yuncheol Heo38db6292014-07-01 14:15:14 +0900305 return handleStandby(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900306 case Constants.MESSAGE_TEXT_VIEW_ON:
Yuncheol Heo38db6292014-07-01 14:15:14 +0900307 return handleTextViewOn(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900308 case Constants.MESSAGE_IMAGE_VIEW_ON:
Yuncheol Heo38db6292014-07-01 14:15:14 +0900309 return handleImageViewOn(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900310 case Constants.MESSAGE_USER_CONTROL_PRESSED:
Yuncheol Heo38db6292014-07-01 14:15:14 +0900311 return handleUserControlPressed(message);
Terry Heo3e1564e2014-08-12 14:41:00 +0900312 case Constants.MESSAGE_USER_CONTROL_RELEASED:
313 return handleUserControlReleased();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900314 case Constants.MESSAGE_SET_STREAM_PATH:
Yuncheol Heo38db6292014-07-01 14:15:14 +0900315 return handleSetStreamPath(message);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900316 case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
Yuncheol Heo38db6292014-07-01 14:15:14 +0900317 return handleGiveDevicePowerStatus(message);
Terry Heo3e1564e2014-08-12 14:41:00 +0900318 case Constants.MESSAGE_MENU_REQUEST:
Yuncheol Heo184b1242014-09-12 15:09:07 +0900319 return handleMenuRequest(message);
320 case Constants.MESSAGE_MENU_STATUS:
321 return handleMenuStatus(message);
Jinsuk Kim119160a2014-07-07 18:48:10 +0900322 case Constants.MESSAGE_VENDOR_COMMAND:
323 return handleVendorCommand(message);
324 case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID:
325 return handleVendorCommandWithId(message);
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900326 case Constants.MESSAGE_SET_OSD_NAME:
327 return handleSetOsdName(message);
Jungshik Jangb6591b82014-07-23 16:10:23 +0900328 case Constants.MESSAGE_RECORD_TV_SCREEN:
329 return handleRecordTvScreen(message);
Jungshik Jange5a93372014-07-25 13:41:14 +0900330 case Constants.MESSAGE_TIMER_CLEARED_STATUS:
331 return handleTimerClearedStatus(message);
Jungshik Jang4480efa2014-09-04 17:08:34 +0900332 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);
Nick Chalko6f5d69e2018-07-17 16:07:11 -0700338 case Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR:
339 return handleRequestShortAudioDescriptor(message);
340 case Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR:
341 return handleReportShortAudioDescriptor(message);
Jungshik Jang092b4452014-06-11 15:19:17 +0900342 default:
343 return false;
344 }
345 }
346
Jungshik Janga5b74142014-06-23 18:03:10 +0900347 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900348 private boolean dispatchMessageToAction(HdmiCecMessage message) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900349 assertRunOnServiceThread();
Jungshik Jang5352081c2014-09-22 15:14:49 +0900350 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;
Jungshik Jang79c58a42014-06-16 16:45:36 +0900356 }
Jungshik Jang5352081c2014-09-22 15:14:49 +0900357 return processed;
Jungshik Jang79c58a42014-06-16 16:45:36 +0900358 }
359
Jungshik Janga5b74142014-06-23 18:03:10 +0900360 @ServiceThreadOnly
Amy1d0b1372018-05-24 14:36:25 -0700361 protected boolean handleGivePhysicalAddress(@Nullable SendMessageCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900362 assertRunOnServiceThread();
363
Jungshik Jang092b4452014-06-11 15:19:17 +0900364 int physicalAddress = mService.getPhysicalAddress();
Nick Chalkof32fcea2018-07-17 16:17:40 -0700365 HdmiCecMessage cecMessage =
366 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
367 mAddress, physicalAddress, mDeviceType);
Amy1d0b1372018-05-24 14:36:25 -0700368 mService.sendCecCommand(cecMessage, callback);
Jungshik Jang092b4452014-06-11 15:19:17 +0900369 return true;
370 }
371
Jungshik Janga5b74142014-06-23 18:03:10 +0900372 @ServiceThreadOnly
Amy1d0b1372018-05-24 14:36:25 -0700373 protected boolean handleGiveDeviceVendorId(@Nullable SendMessageCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900374 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900375 int vendorId = mService.getVendorId();
Nick Chalkof32fcea2018-07-17 16:17:40 -0700376 HdmiCecMessage cecMessage =
377 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, vendorId);
Amy1d0b1372018-05-24 14:36:25 -0700378 mService.sendCecCommand(cecMessage, callback);
Jungshik Jang092b4452014-06-11 15:19:17 +0900379 return true;
380 }
381
Jungshik Janga5b74142014-06-23 18:03:10 +0900382 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900383 protected boolean handleGetCecVersion(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900384 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900385 int version = mService.getCecVersion();
Nick Chalkof32fcea2018-07-17 16:17:40 -0700386 HdmiCecMessage cecMessage =
387 HdmiCecMessageBuilder.buildCecVersion(
388 message.getDestination(), message.getSource(), version);
Jungshik Jang092b4452014-06-11 15:19:17 +0900389 mService.sendCecCommand(cecMessage);
390 return true;
391 }
392
Jungshik Janga5b74142014-06-23 18:03:10 +0900393 @ServiceThreadOnly
Jinsuk Kim83335712014-06-24 07:57:00 +0900394 protected boolean handleActiveSource(HdmiCecMessage message) {
395 return false;
396 }
397
398 @ServiceThreadOnly
399 protected boolean handleInactiveSource(HdmiCecMessage message) {
400 return false;
401 }
402
403 @ServiceThreadOnly
404 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
405 return false;
406 }
407
408 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900409 protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900410 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900411 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900412 // 'return false' will cause to reply with <Feature Abort>.
413 return false;
Jungshik Jang092b4452014-06-11 15:19:17 +0900414 }
415
Jungshik Janga5b74142014-06-23 18:03:10 +0900416 @ServiceThreadOnly
Terry Heo795415b2014-10-01 15:03:53 +0900417 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
Jungshik Jang092b4452014-06-11 15:19:17 +0900425 protected boolean handleGiveOsdName(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900426 assertRunOnServiceThread();
Jungshik Jang092b4452014-06-11 15:19:17 +0900427 // Note that since this method is called after logical address allocation is done,
428 // mDeviceInfo should not be null.
Nick Chalkof32fcea2018-07-17 16:17:40 -0700429 HdmiCecMessage cecMessage =
430 HdmiCecMessageBuilder.buildSetOsdNameCommand(
431 mAddress, message.getSource(), mDeviceInfo.getDisplayName());
Jungshik Jang092b4452014-06-11 15:19:17 +0900432 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
Amyaefab642018-08-22 19:10:14 -0700440 // Audio System device with no Playback device type
441 // needs to refactor this function if it's also a switch
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900442 protected boolean handleRoutingChange(HdmiCecMessage message) {
443 return false;
444 }
445
Amyaefab642018-08-22 19:10:14 -0700446 // Audio System device with no Playback device type
447 // needs to refactor this function if it's also a switch
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900448 protected boolean handleRoutingInformation(HdmiCecMessage message) {
449 return false;
450 }
451
Jungshik Jang60cffce2014-06-12 18:03:04 +0900452 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
453 return false;
454 }
455
Jungshik Jang79c58a42014-06-16 16:45:36 +0900456 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
457 return false;
458 }
459
Amy34ee91d2018-06-04 12:00:09 -0700460 protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
461 return false;
462 }
463
Jungshik Jang79c58a42014-06-16 16:45:36 +0900464 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
465 return false;
466 }
467
Amy34ee91d2018-06-04 12:00:09 -0700468 protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
469 return false;
470 }
471
Jungshik Jang79c58a42014-06-16 16:45:36 +0900472 protected boolean handleTerminateArc(HdmiCecMessage message) {
473 return false;
474 }
475
476 protected boolean handleInitiateArc(HdmiCecMessage message) {
477 return false;
478 }
479
Amy34ee91d2018-06-04 12:00:09 -0700480 protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
481 return false;
482 }
483
484 protected boolean handleRequestArcTermination(HdmiCecMessage message) {
485 return false;
486 }
487
488 protected boolean handleReportArcInitiate(HdmiCecMessage message) {
489 return false;
490 }
491
492 protected boolean handleReportArcTermination(HdmiCecMessage message) {
493 return false;
494 }
495
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900496 protected boolean handleReportAudioStatus(HdmiCecMessage message) {
497 return false;
498 }
499
Amy34ee91d2018-06-04 12:00:09 -0700500 protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
501 return false;
502 }
503
Nick Chalko6f5d69e2018-07-17 16:07:11 -0700504 protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
505 return false;
506 }
507
508 protected boolean handleReportShortAudioDescriptor(HdmiCecMessage message) {
509 return false;
510 }
511
Jungshik Janga5b74142014-06-23 18:03:10 +0900512 @ServiceThreadOnly
Yuncheol Heo38db6292014-07-01 14:15:14 +0900513 protected boolean handleStandby(HdmiCecMessage message) {
514 assertRunOnServiceThread();
515 // Seq #12
Nick Chalkof32fcea2018-07-17 16:17:40 -0700516 if (mService.isControlEnabled()
517 && !mService.isProhibitMode()
Yuncheol Heo38db6292014-07-01 14:15:14 +0900518 && mService.isPowerOnOrTransient()) {
519 mService.standby();
520 return true;
521 }
522 return false;
523 }
524
525 @ServiceThreadOnly
526 protected boolean handleUserControlPressed(HdmiCecMessage message) {
527 assertRunOnServiceThread();
Terry Heo3e1564e2014-08-12 14:41:00 +0900528 mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900529 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 }
Terry Heo3e1564e2014-08-12 14:41:00 +0900536
537 final long downTime = SystemClock.uptimeMillis();
538 final byte[] params = message.getParams();
Jungshik Jang73483b6b2014-09-26 14:00:59 +0900539 final int keycode = HdmiCecKeycode.cecKeycodeAndParamsToAndroidKey(params);
Terry Heo3e1564e2014-08-12 14:41:00 +0900540 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);
Nick Chalkof32fcea2018-07-17 16:17:40 -0700553 mHandler.sendMessageDelayed(
554 Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT),
Terry Heo3e1564e2014-08-12 14:41:00 +0900555 FOLLOWER_SAFETY_TIMEOUT);
556 return true;
557 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900558 return false;
559 }
560
Terry Heo3e1564e2014-08-12 14:41:00 +0900561 @ServiceThreadOnly
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
575 static void injectKeyEvent(long time, int action, int keycode, int repeat) {
Nick Chalkof32fcea2018-07-17 16:17:40 -0700576 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);
Terry Heo3e1564e2014-08-12 14:41:00 +0900591 keyEvent.recycle();
Nick Chalkof32fcea2018-07-17 16:17:40 -0700592 }
Terry Heo3e1564e2014-08-12 14:41:00 +0900593
Yuncheol Heo25c20292014-07-31 17:59:39 +0900594 static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900595 byte[] params = message.getParams();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900596 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
Jungshik Jang210d73d2014-07-04 11:11:29 +0900597 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER
598 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION
599 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900600 }
601
Yuncheol Heo25c20292014-07-31 17:59:39 +0900602 static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900603 byte[] params = message.getParams();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900604 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED
Shuichi.Noguchi55269f72018-01-13 23:59:19 +0900605 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION
Jungshik Jang210d73d2014-07-04 11:11:29 +0900606 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900607 }
608
609 protected boolean handleTextViewOn(HdmiCecMessage message) {
610 return false;
611 }
612
613 protected boolean handleImageViewOn(HdmiCecMessage message) {
614 return false;
615 }
616
617 protected boolean handleSetStreamPath(HdmiCecMessage message) {
618 return false;
619 }
620
621 protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) {
Nick Chalkof32fcea2018-07-17 16:17:40 -0700622 mService.sendCecCommand(
623 HdmiCecMessageBuilder.buildReportPowerStatus(
624 mAddress, message.getSource(), mService.getPowerStatus()));
Yuncheol Heo38db6292014-07-01 14:15:14 +0900625 return true;
626 }
627
Yuncheol Heo184b1242014-09-12 15:09:07 +0900628 protected boolean handleMenuRequest(HdmiCecMessage message) {
Terry Heo3e1564e2014-08-12 14:41:00 +0900629 // Always report menu active to receive Remote Control.
Nick Chalkof32fcea2018-07-17 16:17:40 -0700630 mService.sendCecCommand(
631 HdmiCecMessageBuilder.buildReportMenuStatus(
632 mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED));
Terry Heo3e1564e2014-08-12 14:41:00 +0900633 return true;
634 }
635
Yuncheol Heo184b1242014-09-12 15:09:07 +0900636 protected boolean handleMenuStatus(HdmiCecMessage message) {
637 return false;
638 }
639
Jinsuk Kim119160a2014-07-07 18:48:10 +0900640 protected boolean handleVendorCommand(HdmiCecMessage message) {
Nick Chalkof32fcea2018-07-17 16:17:40 -0700641 if (!mService.invokeVendorCommandListenersOnReceived(
642 mDeviceType,
643 message.getSource(),
644 message.getDestination(),
645 message.getParams(),
646 false)) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +0900647 // 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 }
Jinsuk Kim119160a2014-07-07 18:48:10 +0900651 return true;
652 }
653
654 protected boolean handleVendorCommandWithId(HdmiCecMessage message) {
655 byte[] params = message.getParams();
656 int vendorId = HdmiUtils.threeBytesToInt(params);
657 if (vendorId == mService.getVendorId()) {
Nick Chalkof32fcea2018-07-17 16:17:40 -0700658 if (!mService.invokeVendorCommandListenersOnReceived(
659 mDeviceType, message.getSource(), message.getDestination(), params, true)) {
Jinsuk Kimd4a94db2014-09-12 13:51:10 +0900660 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
661 }
Nick Chalkof32fcea2018-07-17 16:17:40 -0700662 } else if (message.getDestination() != Constants.ADDR_BROADCAST
663 && message.getSource() != Constants.ADDR_UNREGISTERED) {
Jinsuk Kim119160a2014-07-07 18:48:10 +0900664 Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>");
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900665 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
Jinsuk Kim119160a2014-07-07 18:48:10 +0900666 } else {
667 Slog.v(TAG, "Wrong broadcast vendor command. Ignoring");
668 }
669 return true;
670 }
671
Jinsuk Kimd4a94db2014-09-12 13:51:10 +0900672 protected void sendStandby(int deviceId) {
673 // Do nothing.
674 }
675
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900676 protected boolean handleSetOsdName(HdmiCecMessage message) {
677 // The default behavior of <Set Osd Name> is doing nothing.
678 return true;
679 }
680
Jungshik Jangb6591b82014-07-23 16:10:23 +0900681 protected boolean handleRecordTvScreen(HdmiCecMessage message) {
Yuncheol Heo25c20292014-07-31 17:59:39 +0900682 // The default behavior of <Record TV Screen> is replying <Feature Abort> with
683 // "Cannot provide source".
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900684 mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE);
Jungshik Jangb6591b82014-07-23 16:10:23 +0900685 return true;
686 }
687
Jungshik Jange5a93372014-07-25 13:41:14 +0900688 protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
689 return false;
690 }
691
Jungshik Jang4480efa2014-09-04 17:08:34 +0900692 protected boolean handleReportPowerStatus(HdmiCecMessage message) {
693 return false;
694 }
695
696 protected boolean handleTimerStatus(HdmiCecMessage message) {
697 return false;
698 }
699
700 protected boolean handleRecordStatus(HdmiCecMessage message) {
701 return false;
702 }
703
Yuncheol Heo38db6292014-07-01 14:15:14 +0900704 @ServiceThreadOnly
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900705 final void handleAddressAllocated(int logicalAddress, int reason) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900706 assertRunOnServiceThread();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900707 mAddress = mPreferredAddress = logicalAddress;
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900708 onAddressAllocated(logicalAddress, reason);
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900709 setPreferredAddress(logicalAddress);
Jungshik Jang1a4485d2014-05-26 11:02:36 +0900710 }
711
Yuncheol Heob5021862014-09-02 10:36:04 +0900712 int getType() {
713 return mDeviceType;
714 }
715
Amy0361dd72018-11-07 16:54:25 -0800716 @GuardedBy("mLock")
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900717 HdmiDeviceInfo getDeviceInfo() {
Amy0361dd72018-11-07 16:54:25 -0800718 synchronized (mLock) {
719 return mDeviceInfo;
720 }
Jungshik Jang1a4485d2014-05-26 11:02:36 +0900721 }
722
Amy0361dd72018-11-07 16:54:25 -0800723 @GuardedBy("mLock")
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900724 void setDeviceInfo(HdmiDeviceInfo info) {
Amy0361dd72018-11-07 16:54:25 -0800725 synchronized (mLock) {
726 mDeviceInfo = info;
727 }
Jungshik Jang1a4485d2014-05-26 11:02:36 +0900728 }
729
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900730 // Returns true if the logical address is same as the argument.
Jungshik Janga5b74142014-06-23 18:03:10 +0900731 @ServiceThreadOnly
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900732 boolean isAddressOf(int addr) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900733 assertRunOnServiceThread();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900734 return addr == mAddress;
735 }
736
737 // Resets the logical address to unregistered(15), meaning the logical device is invalid.
Jungshik Janga5b74142014-06-23 18:03:10 +0900738 @ServiceThreadOnly
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900739 void clearAddress() {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900740 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900741 mAddress = Constants.ADDR_UNREGISTERED;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900742 }
743
Jungshik Janga5b74142014-06-23 18:03:10 +0900744 @ServiceThreadOnly
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900745 void addAndStartAction(final HdmiCecFeatureAction action) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900746 assertRunOnServiceThread();
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900747 mActions.add(action);
Donghyun Chofc462b92016-05-13 21:06:02 +0900748 if (mService.isPowerStandby() || !mService.isAddressAllocated()) {
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900749 Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900750 return;
751 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900752 action.start();
753 }
754
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900755 @ServiceThreadOnly
756 void startQueuedActions() {
757 assertRunOnServiceThread();
Shuichi.Noguchic4426af2017-12-19 19:44:48 +0900758 // Use copied action list in that start() may remove itself.
759 for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) {
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900760 if (!action.started()) {
761 Slog.i(TAG, "Starting queued action:" + action);
762 action.start();
763 }
764 }
765 }
766
Jungshik Jang79c58a42014-06-16 16:45:36 +0900767 // See if we have an action of a given type in progress.
Jungshik Janga5b74142014-06-23 18:03:10 +0900768 @ServiceThreadOnly
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900769 <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900770 assertRunOnServiceThread();
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900771 for (HdmiCecFeatureAction action : mActions) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900772 if (action.getClass().equals(clazz)) {
773 return true;
774 }
775 }
776 return false;
777 }
778
779 // Returns all actions matched with given class type.
Jungshik Janga5b74142014-06-23 18:03:10 +0900780 @ServiceThreadOnly
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900781 <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900782 assertRunOnServiceThread();
Jinsuk Kim43c23e22014-07-29 13:59:14 +0900783 List<T> actions = Collections.<T>emptyList();
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900784 for (HdmiCecFeatureAction action : mActions) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900785 if (action.getClass().equals(clazz)) {
Jinsuk Kim43c23e22014-07-29 13:59:14 +0900786 if (actions.isEmpty()) {
787 actions = new ArrayList<T>();
788 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900789 actions.add((T) action);
790 }
791 }
792 return actions;
793 }
794
795 /**
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900796 * Remove the given {@link HdmiCecFeatureAction} object from the action queue.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900797 *
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900798 * @param action {@link HdmiCecFeatureAction} to remove
Jungshik Jang79c58a42014-06-16 16:45:36 +0900799 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900800 @ServiceThreadOnly
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900801 void removeAction(final HdmiCecFeatureAction action) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900802 assertRunOnServiceThread();
Yuncheol Heoc516d652014-07-11 18:23:24 +0900803 action.finish(false);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900804 mActions.remove(action);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900805 checkIfPendingActionsCleared();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900806 }
807
808 // Remove all actions matched with the given Class type.
Jungshik Janga5b74142014-06-23 18:03:10 +0900809 @ServiceThreadOnly
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900810 <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900811 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900812 removeActionExcept(clazz, null);
813 }
814
815 // Remove all actions matched with the given Class type besides |exception|.
Jungshik Janga5b74142014-06-23 18:03:10 +0900816 @ServiceThreadOnly
Nick Chalkof32fcea2018-07-17 16:17:40 -0700817 <T extends HdmiCecFeatureAction> void removeActionExcept(
818 final Class<T> clazz, final HdmiCecFeatureAction exception) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900819 assertRunOnServiceThread();
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900820 Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900821 while (iter.hasNext()) {
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900822 HdmiCecFeatureAction action = iter.next();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900823 if (action != exception && action.getClass().equals(clazz)) {
Yuncheol Heoc516d652014-07-11 18:23:24 +0900824 action.finish(false);
825 iter.remove();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900826 }
827 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900828 checkIfPendingActionsCleared();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900829 }
830
Yuncheol Heo38db6292014-07-01 14:15:14 +0900831 protected void checkIfPendingActionsCleared() {
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900832 if (mActions.isEmpty() && mPendingActionClearedCallback != null) {
Yuncheol Heo26ba7fd2014-07-29 18:21:25 +0900833 PendingActionClearedCallback callback = mPendingActionClearedCallback;
834 // To prevent from calling the callback again during handling the callback itself.
835 mPendingActionClearedCallback = null;
836 callback.onCleared(this);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900837 }
838 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900839
Jungshik Jang79c58a42014-06-16 16:45:36 +0900840 protected void assertRunOnServiceThread() {
841 if (Looper.myLooper() != mService.getServiceLooper()) {
842 throw new IllegalStateException("Should run on service thread.");
843 }
844 }
845
Nick Chalkof32fcea2018-07-17 16:17:40 -0700846 void setAutoDeviceOff(boolean enabled) {}
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900847
Jungshik Jang79c58a42014-06-16 16:45:36 +0900848 /**
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 */
Nick Chalkof32fcea2018-07-17 16:17:40 -0700854 void onHotplug(int portId, boolean connected) {}
Jungshik Jang79c58a42014-06-16 16:45:36 +0900855
856 final HdmiControlService getService() {
857 return mService;
858 }
859
Jungshik Janga5b74142014-06-23 18:03:10 +0900860 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900861 final boolean isConnectedToArcPort(int path) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900862 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900863 return mService.isConnectedToArcPort(path);
864 }
865
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900866 ActiveSource getActiveSource() {
Amy123ec402018-09-25 10:56:31 -0700867 return mService.getActiveSource();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900868 }
869
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900870 void setActiveSource(ActiveSource newActive) {
871 setActiveSource(newActive.logicalAddress, newActive.physicalAddress);
872 }
873
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900874 void setActiveSource(HdmiDeviceInfo info) {
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900875 setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress());
876 }
877
878 void setActiveSource(int logicalAddress, int physicalAddress) {
Amy123ec402018-09-25 10:56:31 -0700879 mService.setActiveSource(logicalAddress, physicalAddress);
Jinsuk Kime9f6ed32014-08-20 17:45:22 +0900880 mService.setLastInputForMhl(Constants.INVALID_PORT_ID);
Jinsuk Kim83335712014-06-24 07:57:00 +0900881 }
882
Jungshik Jang79c58a42014-06-16 16:45:36 +0900883 int getActivePath() {
884 synchronized (mLock) {
885 return mActiveRoutingPath;
886 }
887 }
888
Jinsuk Kim83335712014-06-24 07:57:00 +0900889 void setActivePath(int path) {
890 synchronized (mLock) {
891 mActiveRoutingPath = path;
892 }
Jungshik Jang867b4e02014-08-12 13:41:30 +0900893 mService.setActivePortId(pathToPortId(path));
Jinsuk Kim83335712014-06-24 07:57:00 +0900894 }
895
Jungshik Jang79c58a42014-06-16 16:45:36 +0900896 /**
Jinsuk Kima062a932014-06-18 10:00:39 +0900897 * 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.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900899 */
Jinsuk Kima062a932014-06-18 10:00:39 +0900900 int getActivePortId() {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900901 synchronized (mLock) {
902 return mService.pathToPortId(mActiveRoutingPath);
903 }
904 }
905
Jinsuk Kima062a932014-06-18 10:00:39 +0900906 /**
907 * Update the active port.
908 *
909 * @param portId the new active port id
910 */
911 void setActivePortId(int portId) {
Jungshik Jang867b4e02014-08-12 13:41:30 +0900912 // We update active routing path instead, since we get the active port id from
913 // the active routing path.
914 setActivePath(mService.portIdToPath(portId));
Jinsuk Kima062a932014-06-18 10:00:39 +0900915 }
916
Amyc00cd4e2018-10-16 20:21:33 -0700917 // Returns the id of the port that the target device is connected to.
918 int getPortId(int physicalAddress) {
919 return mService.pathToPortId(physicalAddress);
920 }
921
Jungshik Janga5b74142014-06-23 18:03:10 +0900922 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900923 HdmiCecMessageCache getCecMessageCache() {
924 assertRunOnServiceThread();
925 return mCecMessageCache;
926 }
927
Jungshik Janga5b74142014-06-23 18:03:10 +0900928 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900929 int pathToPortId(int newPath) {
930 assertRunOnServiceThread();
931 return mService.pathToPortId(newPath);
932 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900933
934 /**
Yuncheol Heo38db6292014-07-01 14:15:14 +0900935 * Called when the system goes to standby mode.
936 *
Nick Chalkof32fcea2018-07-17 16:17:40 -0700937 * @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}
Yuncheol Heo38db6292014-07-01 14:15:14 +0900941 */
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900942 protected void onStandby(boolean initiatedByCec, int standbyAction) {}
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900943
944 /**
Nick Chalkof32fcea2018-07-17 16:17:40 -0700945 * Disable device. {@code callback} is used to get notified when all pending actions are
946 * completed or timeout is issued.
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900947 *
Nick Chalkof32fcea2018-07-17 16:17:40 -0700948 * @param initiatedByCec true if this sequence is initiated by the reception the CEC messages
949 * like &lt;Standby&gt;
Jinsuk Kim7e4b4802015-04-20 13:17:13 +0900950 * @param originalCallback callback interface to get notified when all pending actions are
Nick Chalkof32fcea2018-07-17 16:17:40 -0700951 * cleared
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900952 */
Nick Chalkof32fcea2018-07-17 16:17:40 -0700953 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);
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900965 }
966
967 @ServiceThreadOnly
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.
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900973 Iterator<HdmiCecFeatureAction> iter = mActions.iterator();
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900974 while (iter.hasNext()) {
Jungshik Jangb509c2e2014-08-07 13:45:01 +0900975 HdmiCecFeatureAction action = iter.next();
Yuncheol Heoc516d652014-07-11 18:23:24 +0900976 action.finish(false);
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900977 iter.remove();
978 }
Jinsuk Kim7e4b4802015-04-20 13:17:13 +0900979 if (mPendingActionClearedCallback != null) {
980 mPendingActionClearedCallback.onCleared(this);
981 }
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900982 }
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900983
984 /**
Donghyun Cho7609bc32016-12-02 15:31:52 +0900985 * Send a key event to other CEC device. The logical address of target device will be given by
986 * {@link #findKeyReceiverAddress}.
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900987 *
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900988 * @param keyCode key code defined in {@link android.view.KeyEvent}
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900989 * @param isPressed {@code true} for key down event
Donghyun Cho7609bc32016-12-02 15:31:52 +0900990 * @see #findKeyReceiverAddress()
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900991 */
Donghyun Cho7609bc32016-12-02 15:31:52 +0900992 @ServiceThreadOnly
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900993 protected void sendKeyEvent(int keyCode, boolean isPressed) {
Donghyun Cho7609bc32016-12-02 15:31:52 +0900994 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.
Nick Chalkof32fcea2018-07-17 16:17:40 -07001003 Slog.w(
1004 TAG,
1005 "Discard key event: "
1006 + keyCode
1007 + ", pressed:"
1008 + isPressed
1009 + ", receiverAddr="
1010 + logicalAddress);
Donghyun Cho7609bc32016-12-02 15:31:52 +09001011 } 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 /**
Nick Chalkof32fcea2018-07-17 16:17:40 -07001019 * Returns the logical address of the device which will receive key events via {@link
1020 * #sendKeyEvent}.
Donghyun Cho7609bc32016-12-02 15:31:52 +09001021 *
1022 * @see #sendKeyEvent(int, boolean)
1023 */
1024 protected int findKeyReceiverAddress() {
1025 Slog.w(TAG, "findKeyReceiverAddress is not implemented");
1026 return Constants.ADDR_INVALID;
Jinsuk Kimc068bb52014-07-07 16:59:20 +09001027 }
Terry Heo959d2db2014-08-28 16:45:41 +09001028
shubangd932cb52018-09-14 17:55:16 -07001029 @ServiceThreadOnly
1030 void invokeCallback(IHdmiControlCallback callback, int result) {
1031 assertRunOnServiceThread();
1032 if (callback == null) {
1033 return;
1034 }
1035 try {
1036 callback.onComplete(result);
1037 } catch (RemoteException e) {
1038 Slog.e(TAG, "Invoking callback failed:" + e);
1039 }
1040 }
1041
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001042 void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) {
Nick Chalkof32fcea2018-07-17 16:17:40 -07001043 mService.sendCecCommand(
1044 HdmiCecMessageBuilder.buildUserControlPressed(mAddress, targetAddress, cecKeycode));
1045 mService.sendCecCommand(
1046 HdmiCecMessageBuilder.buildUserControlReleased(mAddress, targetAddress));
Jungshik Jang2e8f1b62014-09-03 08:28:02 +09001047 }
1048
Nick Chalkof32fcea2018-07-17 16:17:40 -07001049 /** Dump internal status of HdmiCecLocalDevice object. */
Terry Heo959d2db2014-08-28 16:45:41 +09001050 protected void dump(final IndentingPrintWriter pw) {
1051 pw.println("mDeviceType: " + mDeviceType);
1052 pw.println("mAddress: " + mAddress);
1053 pw.println("mPreferredAddress: " + mPreferredAddress);
1054 pw.println("mDeviceInfo: " + mDeviceInfo);
Amy123ec402018-09-25 10:56:31 -07001055 pw.println("mActiveSource: " + getActiveSource());
Terry Heo959d2db2014-08-28 16:45:41 +09001056 pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath));
1057 }
Amyaefab642018-08-22 19:10:14 -07001058
Amy79b54e92018-09-11 17:02:28 -07001059 /** Calculates the physical address for {@code activePortId}.
1060 *
1061 * <p>This method assumes current device physical address is valid.
1062 * <p>If the current device is already the leaf of the whole CEC system
1063 * and can't have devices under it, will return its own physical address.
1064 *
1065 * @param activePortId is the local active port Id
1066 * @return the calculated physical address of the port
1067 */
1068 protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) {
1069 int myPhysicalAddress = mService.getPhysicalAddress();
1070 int finalMask = activePortId << 8;
1071 int mask;
1072 for (mask = 0x0F00; mask > 0x000F; mask >>= 4) {
1073 if ((myPhysicalAddress & mask) == 0) {
1074 break;
1075 } else {
1076 finalMask >>= 4;
1077 }
1078 }
1079 return finalMask | myPhysicalAddress;
1080 }
Jinsuk Kim2918e9e2014-05-20 16:45:45 +09001081}