blob: de6cdd48fb09f9ab3b5edcb347a6dc8ea0ff02c8 [file] [log] [blame]
Jungshik Jang0792d372014-04-23 17:57:26 +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
Jinsuk Kim0340bbc2014-06-05 11:07:47 +090019import android.hardware.hdmi.HdmiPortInfo;
Donghyun Chobc6e3722016-11-04 05:25:52 +090020import android.hardware.tv.cec.V1_0.Result;
21import android.hardware.tv.cec.V1_0.SendMessageResult;
Jungshik Jang0792d372014-04-23 17:57:26 +090022import android.os.Handler;
Jungshik Jang02bb4262014-05-23 16:48:31 +090023import android.os.Looper;
Jungshik Jang4085d0e2014-05-27 19:52:39 +090024import android.os.MessageQueue;
Yuncheol Heoece603b2014-05-23 20:10:19 +090025import android.util.Slog;
Jungshik Jang7d9a8432014-04-29 15:12:43 +090026import android.util.SparseArray;
Terry Heo959d2db2014-08-28 16:45:41 +090027import com.android.internal.util.IndentingPrintWriter;
Jungshik Janga5b74142014-06-23 18:03:10 +090028import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
29import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Jungshik Jang02bb4262014-05-23 16:48:31 +090030import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
Donghyun Cho0b485b22016-12-13 17:21:01 +090031import java.text.SimpleDateFormat;
Jungshik Jang7d9a8432014-04-29 15:12:43 +090032import java.util.ArrayList;
Donghyun Cho0b485b22016-12-13 17:21:01 +090033import java.util.Date;
Jungshik Jangb3ecb722014-09-11 16:09:45 +090034import java.util.LinkedList;
Jungshik Jang7d9a8432014-04-29 15:12:43 +090035import java.util.List;
Paul Duffinca4964c2017-02-07 15:04:10 +000036import java.util.function.Predicate;
Donghyun Cho0b485b22016-12-13 17:21:01 +090037import java.util.concurrent.ArrayBlockingQueue;
Donghyun Chobc6e3722016-11-04 05:25:52 +090038import libcore.util.EmptyArray;
39import sun.util.locale.LanguageTag;
Jungshik Jang0792d372014-04-23 17:57:26 +090040
41/**
42 * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
43 * and pass it to CEC HAL so that it sends message to other device. For incoming
44 * message it translates the message and delegates it to proper module.
45 *
Jungshik Jang02bb4262014-05-23 16:48:31 +090046 * <p>It should be careful to access member variables on IO thread because
47 * it can be accessed from system thread as well.
48 *
Jungshik Jang0792d372014-04-23 17:57:26 +090049 * <p>It can be created only by {@link HdmiCecController#create}
50 *
51 * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
52 */
Jungshik Janga9095ba2014-05-02 13:06:22 +090053final class HdmiCecController {
Jungshik Jang0792d372014-04-23 17:57:26 +090054 private static final String TAG = "HdmiCecController";
55
Jungshik Jang3ee65722014-06-03 16:22:30 +090056 /**
57 * Interface to report allocated logical address.
58 */
59 interface AllocateAddressCallback {
60 /**
61 * Called when a new logical address is allocated.
62 *
63 * @param deviceType requested device type to allocate logical address
64 * @param logicalAddress allocated logical address. If it is
Jungshik Jang42230722014-07-07 17:40:25 +090065 * {@link Constants#ADDR_UNREGISTERED}, it means that
Jungshik Jang3ee65722014-06-03 16:22:30 +090066 * it failed to allocate logical address for the given device type
67 */
68 void onAllocated(int deviceType, int logicalAddress);
69 }
70
Jungshik Jang3f74ab02014-04-30 14:31:02 +090071 private static final byte[] EMPTY_BODY = EmptyArray.BYTE;
72
Jungshik Jang3f74ab02014-04-30 14:31:02 +090073 private static final int NUM_LOGICAL_ADDRESS = 16;
74
Donghyun Cho0b485b22016-12-13 17:21:01 +090075 private static final int MAX_CEC_MESSAGE_HISTORY = 20;
76
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090077 // Predicate for whether the given logical address is remote device's one or not.
78 private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
79 @Override
Paul Duffinca4964c2017-02-07 15:04:10 +000080 public boolean test(Integer address) {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090081 return !isAllocatedLocalDeviceAddress(address);
82 }
83 };
84
85 // Predicate whether the given logical address is system audio's one or not
86 private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() {
87 @Override
Paul Duffinca4964c2017-02-07 15:04:10 +000088 public boolean test(Integer address) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090089 return HdmiUtils.getTypeFromAddress(address) == Constants.ADDR_AUDIO_SYSTEM;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +090090 }
91 };
92
Jungshik Jang0792d372014-04-23 17:57:26 +090093 // Handler instance to process synchronous I/O (mainly send) message.
94 private Handler mIoHandler;
95
96 // Handler instance to process various messages coming from other CEC
97 // device or issued by internal state change.
Jungshik Jange9c77c82014-04-24 20:30:09 +090098 private Handler mControlHandler;
Jungshik Jang0792d372014-04-23 17:57:26 +090099
100 // Stores the pointer to the native implementation of the service that
101 // interacts with HAL.
Jungshik Jang02bb4262014-05-23 16:48:31 +0900102 private volatile long mNativePtr;
Jungshik Jang0792d372014-04-23 17:57:26 +0900103
Jungshik Jang7df52862014-08-11 14:35:27 +0900104 private final HdmiControlService mService;
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900105
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900106 // Stores the local CEC devices in the system. Device type is used for key.
107 private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
Jungshik Jang7d9a8432014-04-29 15:12:43 +0900108
Donghyun Cho0b485b22016-12-13 17:21:01 +0900109 // Stores recent CEC messages history for debugging purpose.
110 private final ArrayBlockingQueue<MessageHistoryRecord> mMessageHistory =
111 new ArrayBlockingQueue<>(MAX_CEC_MESSAGE_HISTORY);
112
Amy28a9a9e2018-05-17 17:57:33 -0700113 private final NativeWrapper mNativeWrapperImpl;
114
Jungshik Jang0792d372014-04-23 17:57:26 +0900115 // Private constructor. Use HdmiCecController.create().
Amy28a9a9e2018-05-17 17:57:33 -0700116 private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) {
Jungshik Jang7df52862014-08-11 14:35:27 +0900117 mService = service;
Amy28a9a9e2018-05-17 17:57:33 -0700118 mNativeWrapperImpl = nativeWrapper;
Jungshik Jang0792d372014-04-23 17:57:26 +0900119 }
120
121 /**
122 * A factory method to get {@link HdmiCecController}. If it fails to initialize
123 * inner device or has no device it will return {@code null}.
124 *
125 * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
Jungshik Jange9c77c82014-04-24 20:30:09 +0900126 * @param service {@link HdmiControlService} instance used to create internal handler
127 * and to pass callback for incoming message or event.
Jungshik Jang0792d372014-04-23 17:57:26 +0900128 * @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
129 * returns {@code null}.
130 */
Jungshik Jange9c77c82014-04-24 20:30:09 +0900131 static HdmiCecController create(HdmiControlService service) {
Amy28a9a9e2018-05-17 17:57:33 -0700132 return createWithNativeWrapper(service, new NativeWrapperImpl());
133 }
Jungshik Jang0792d372014-04-23 17:57:26 +0900134
Amy28a9a9e2018-05-17 17:57:33 -0700135 /**
136 * A factory method with injection of native methods for testing.
137 */
138 static HdmiCecController createWithNativeWrapper(
139 HdmiControlService service, NativeWrapper nativeWrapper) {
140 HdmiCecController controller = new HdmiCecController(service, nativeWrapper);
141 long nativePtr = nativeWrapper
142 .nativeInit(controller, service.getServiceLooper().getQueue());
143 if (nativePtr == 0L) {
144 controller = null;
145 return null;
146 }
147
148 controller.init(nativePtr);
149 return controller;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900150 }
151
Jungshik Jang7df52862014-08-11 14:35:27 +0900152 private void init(long nativePtr) {
Yuncheol Heof1702482014-11-27 19:52:01 +0900153 mIoHandler = new Handler(mService.getIoLooper());
Jungshik Jang7df52862014-08-11 14:35:27 +0900154 mControlHandler = new Handler(mService.getServiceLooper());
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900155 mNativePtr = nativePtr;
Jungshik Jang0792d372014-04-23 17:57:26 +0900156 }
157
Jungshik Janga5b74142014-06-23 18:03:10 +0900158 @ServiceThreadOnly
Jungshik Jang3ee65722014-06-03 16:22:30 +0900159 void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900160 assertRunOnServiceThread();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900161 mLocalDevices.put(deviceType, device);
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900162 }
163
164 /**
165 * Allocate a new logical address of the given device type. Allocated
Jungshik Jang3ee65722014-06-03 16:22:30 +0900166 * address will be reported through {@link AllocateAddressCallback}.
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900167 *
168 * <p> Declared as package-private, accessed by {@link HdmiControlService} only.
169 *
170 * @param deviceType type of device to used to determine logical address
171 * @param preferredAddress a logical address preferred to be allocated.
Jungshik Jang42230722014-07-07 17:40:25 +0900172 * If sets {@link Constants#ADDR_UNREGISTERED}, scans
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900173 * the smallest logical address matched with the given device type.
174 * Otherwise, scan address will start from {@code preferredAddress}
175 * @param callback callback interface to report allocated logical address to caller
176 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900177 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900178 void allocateLogicalAddress(final int deviceType, final int preferredAddress,
Jungshik Jang3ee65722014-06-03 16:22:30 +0900179 final AllocateAddressCallback callback) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900180 assertRunOnServiceThread();
181
Jungshik Jangd643f762014-05-22 19:28:09 +0900182 runOnIoThread(new Runnable() {
183 @Override
184 public void run() {
185 handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
186 }
187 });
188 }
189
Jungshik Janga5b74142014-06-23 18:03:10 +0900190 @IoThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900191 private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
Jungshik Jang3ee65722014-06-03 16:22:30 +0900192 final AllocateAddressCallback callback) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900193 assertRunOnIoThread();
Jungshik Jangd643f762014-05-22 19:28:09 +0900194 int startAddress = preferredAddress;
195 // If preferred address is "unregistered", start address will be the smallest
196 // address matched with the given device type.
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900197 if (preferredAddress == Constants.ADDR_UNREGISTERED) {
Jungshik Jangd643f762014-05-22 19:28:09 +0900198 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900199 if (deviceType == HdmiUtils.getTypeFromAddress(i)) {
Jungshik Jangd643f762014-05-22 19:28:09 +0900200 startAddress = i;
201 break;
202 }
203 }
204 }
205
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900206 int logicalAddress = Constants.ADDR_UNREGISTERED;
Jungshik Jangd643f762014-05-22 19:28:09 +0900207 // Iterates all possible addresses which has the same device type.
208 for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
209 int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900210 if (curAddress != Constants.ADDR_UNREGISTERED
211 && deviceType == HdmiUtils.getTypeFromAddress(curAddress)) {
Donghyun Chodbf9f9c2016-07-04 20:39:26 +0900212 boolean acked = false;
Jungshik Jang8e93c842014-08-06 15:48:33 +0900213 for (int j = 0; j < HdmiConfig.ADDRESS_ALLOCATION_RETRY; ++j) {
Donghyun Chodbf9f9c2016-07-04 20:39:26 +0900214 if (sendPollMessage(curAddress, curAddress, 1)) {
215 acked = true;
216 break;
Jungshik Jang8e93c842014-08-06 15:48:33 +0900217 }
218 }
Donghyun Chodbf9f9c2016-07-04 20:39:26 +0900219 // If sending <Polling Message> failed, it becomes new logical address for the
220 // device because no device uses it as logical address of the device.
221 if (!acked) {
Jungshik Jangd643f762014-05-22 19:28:09 +0900222 logicalAddress = curAddress;
223 break;
224 }
225 }
226 }
227
228 final int assignedAddress = logicalAddress;
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900229 HdmiLogger.debug("New logical address for device [%d]: [preferred:%d, assigned:%d]",
230 deviceType, preferredAddress, assignedAddress);
Jungshik Jangd643f762014-05-22 19:28:09 +0900231 if (callback != null) {
232 runOnServiceThread(new Runnable() {
Jungshik Jang8e93c842014-08-06 15:48:33 +0900233 @Override
Jungshik Jangd643f762014-05-22 19:28:09 +0900234 public void run() {
235 callback.onAllocated(deviceType, assignedAddress);
236 }
237 });
238 }
Jungshik Jang3f74ab02014-04-30 14:31:02 +0900239 }
240
Jungshik Jange9c77c82014-04-24 20:30:09 +0900241 private static byte[] buildBody(int opcode, byte[] params) {
242 byte[] body = new byte[params.length + 1];
243 body[0] = (byte) opcode;
244 System.arraycopy(params, 0, body, 1, params.length);
245 return body;
246 }
Jungshik Jang0792d372014-04-23 17:57:26 +0900247
Jungshik Jang7d9a8432014-04-29 15:12:43 +0900248
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900249 HdmiPortInfo[] getPortInfos() {
Amy28a9a9e2018-05-17 17:57:33 -0700250 return mNativeWrapperImpl.nativeGetPortInfos(mNativePtr);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900251 }
252
Jungshik Janga9095ba2014-05-02 13:06:22 +0900253 /**
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900254 * Return the locally hosted logical device of a given type.
255 *
256 * @param deviceType logical device type
257 * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
258 * otherwise null.
259 */
260 HdmiCecLocalDevice getLocalDevice(int deviceType) {
261 return mLocalDevices.get(deviceType);
262 }
263
264 /**
Jungshik Janga9095ba2014-05-02 13:06:22 +0900265 * Add a new logical address to the device. Device's HW should be notified
266 * when a new logical address is assigned to a device, so that it can accept
267 * a command having available destinations.
268 *
269 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
270 *
271 * @param newLogicalAddress a logical address to be added
272 * @return 0 on success. Otherwise, returns negative value
273 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900274 @ServiceThreadOnly
Jungshik Janga9095ba2014-05-02 13:06:22 +0900275 int addLogicalAddress(int newLogicalAddress) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900276 assertRunOnServiceThread();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900277 if (HdmiUtils.isValidAddress(newLogicalAddress)) {
Amy28a9a9e2018-05-17 17:57:33 -0700278 return mNativeWrapperImpl.nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900279 } else {
Donghyun Chobc6e3722016-11-04 05:25:52 +0900280 return Result.FAILURE_INVALID_ARGS;
Jungshik Janga9095ba2014-05-02 13:06:22 +0900281 }
282 }
283
284 /**
285 * Clear all logical addresses registered in the device.
286 *
287 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
288 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900289 @ServiceThreadOnly
Jungshik Janga9095ba2014-05-02 13:06:22 +0900290 void clearLogicalAddress() {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900291 assertRunOnServiceThread();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900292 for (int i = 0; i < mLocalDevices.size(); ++i) {
293 mLocalDevices.valueAt(i).clearAddress();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900294 }
Amy28a9a9e2018-05-17 17:57:33 -0700295 mNativeWrapperImpl.nativeClearLogicalAddress(mNativePtr);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900296 }
297
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900298 @ServiceThreadOnly
299 void clearLocalDevices() {
300 assertRunOnServiceThread();
301 mLocalDevices.clear();
302 }
303
Jungshik Janga9095ba2014-05-02 13:06:22 +0900304 /**
305 * Return the physical address of the device.
306 *
307 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
308 *
309 * @return CEC physical address of the device. The range of success address
310 * is between 0x0000 and 0xFFFF. If failed it returns -1
311 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900312 @ServiceThreadOnly
Jungshik Janga9095ba2014-05-02 13:06:22 +0900313 int getPhysicalAddress() {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900314 assertRunOnServiceThread();
Amy28a9a9e2018-05-17 17:57:33 -0700315 return mNativeWrapperImpl.nativeGetPhysicalAddress(mNativePtr);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900316 }
317
318 /**
319 * Return CEC version of the device.
320 *
321 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
322 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900323 @ServiceThreadOnly
Jungshik Janga9095ba2014-05-02 13:06:22 +0900324 int getVersion() {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900325 assertRunOnServiceThread();
Amy28a9a9e2018-05-17 17:57:33 -0700326 return mNativeWrapperImpl.nativeGetVersion(mNativePtr);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900327 }
328
329 /**
330 * Return vendor id of the device.
331 *
332 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
333 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900334 @ServiceThreadOnly
Jungshik Janga9095ba2014-05-02 13:06:22 +0900335 int getVendorId() {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900336 assertRunOnServiceThread();
Amy28a9a9e2018-05-17 17:57:33 -0700337 return mNativeWrapperImpl.nativeGetVendorId(mNativePtr);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900338 }
339
Jungshik Jang02bb4262014-05-23 16:48:31 +0900340 /**
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900341 * Set an option to CEC HAL.
Jungshik Jang092b4452014-06-11 15:19:17 +0900342 *
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900343 * @param flag key of option
Donghyun Chobc6e3722016-11-04 05:25:52 +0900344 * @param enabled whether to enable/disable the given option.
Jungshik Jang092b4452014-06-11 15:19:17 +0900345 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900346 @ServiceThreadOnly
Donghyun Chobc6e3722016-11-04 05:25:52 +0900347 void setOption(int flag, boolean enabled) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900348 assertRunOnServiceThread();
Donghyun Chobc6e3722016-11-04 05:25:52 +0900349 HdmiLogger.debug("setOption: [flag:%d, enabled:%b]", flag, enabled);
Amy28a9a9e2018-05-17 17:57:33 -0700350 mNativeWrapperImpl.nativeSetOption(mNativePtr, flag, enabled);
Donghyun Chobc6e3722016-11-04 05:25:52 +0900351 }
352
353 /**
354 * Informs CEC HAL about the current system language.
355 *
356 * @param language Three-letter code defined in ISO/FDIS 639-2. Must be lowercase letters.
357 */
358 @ServiceThreadOnly
359 void setLanguage(String language) {
360 assertRunOnServiceThread();
361 if (!LanguageTag.isLanguage(language)) {
362 return;
363 }
Amy28a9a9e2018-05-17 17:57:33 -0700364 mNativeWrapperImpl.nativeSetLanguage(mNativePtr, language);
Jungshik Jang092b4452014-06-11 15:19:17 +0900365 }
366
367 /**
368 * Configure ARC circuit in the hardware logic to start or stop the feature.
369 *
Jinsuk Kim1481a422014-12-17 16:15:05 +0900370 * @param port ID of HDMI port to which AVR is connected
Jungshik Jang092b4452014-06-11 15:19:17 +0900371 * @param enabled whether to enable/disable ARC
372 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900373 @ServiceThreadOnly
Donghyun Chobc6e3722016-11-04 05:25:52 +0900374 void enableAudioReturnChannel(int port, boolean enabled) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900375 assertRunOnServiceThread();
Amy28a9a9e2018-05-17 17:57:33 -0700376 mNativeWrapperImpl.nativeEnableAudioReturnChannel(mNativePtr, port, enabled);
Jungshik Jang092b4452014-06-11 15:19:17 +0900377 }
378
379 /**
380 * Return the connection status of the specified port
381 *
382 * @param port port number to check connection status
383 * @return true if connected; otherwise, return false
384 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900385 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900386 boolean isConnected(int port) {
387 assertRunOnServiceThread();
Amy28a9a9e2018-05-17 17:57:33 -0700388 return mNativeWrapperImpl.nativeIsConnected(mNativePtr, port);
Jungshik Jang092b4452014-06-11 15:19:17 +0900389 }
390
391 /**
Jungshik Jang02bb4262014-05-23 16:48:31 +0900392 * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
393 * devices.
394 *
395 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
396 *
397 * @param callback an interface used to get a list of all remote devices' address
Jungshik Jang1de51422014-07-03 11:14:26 +0900398 * @param sourceAddress a logical address of source device where sends polling message
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900399 * @param pickStrategy strategy how to pick polling candidates
Jungshik Jang02bb4262014-05-23 16:48:31 +0900400 * @param retryCount the number of retry used to send polling message to remote devices
401 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900402 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +0900403 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
404 int retryCount) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900405 assertRunOnServiceThread();
Jungshik Jang02bb4262014-05-23 16:48:31 +0900406
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900407 // Extract polling candidates. No need to poll against local devices.
408 List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
Jungshik Jangb3ecb722014-09-11 16:09:45 +0900409 ArrayList<Integer> allocated = new ArrayList<>();
410 runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
Jungshik Jang02bb4262014-05-23 16:48:31 +0900411 }
412
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900413 /**
414 * Return a list of all {@link HdmiCecLocalDevice}s.
415 *
416 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
417 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900418 @ServiceThreadOnly
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900419 List<HdmiCecLocalDevice> getLocalDeviceList() {
420 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900421 return HdmiUtils.sparseArrayToList(mLocalDevices);
Jungshik Jangcc5ef8c2014-05-27 13:27:36 +0900422 }
423
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900424 private List<Integer> pickPollCandidates(int pickStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900425 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900426 Predicate<Integer> pickPredicate = null;
427 switch (strategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900428 case Constants.POLL_STRATEGY_SYSTEM_AUDIO:
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900429 pickPredicate = mSystemAudioAddressPredicate;
430 break;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900431 case Constants.POLL_STRATEGY_REMOTES_DEVICES:
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900432 default: // The default is POLL_STRATEGY_REMOTES_DEVICES.
433 pickPredicate = mRemoteDeviceAddressPredicate;
434 break;
435 }
436
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900437 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
Jungshik Jangb3ecb722014-09-11 16:09:45 +0900438 LinkedList<Integer> pollingCandidates = new LinkedList<>();
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900439 switch (iterationStrategy) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900440 case Constants.POLL_ITERATION_IN_ORDER:
441 for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) {
Paul Duffinca4964c2017-02-07 15:04:10 +0000442 if (pickPredicate.test(i)) {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900443 pollingCandidates.add(i);
444 }
445 }
446 break;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900447 case Constants.POLL_ITERATION_REVERSE_ORDER:
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900448 default: // The default is reverse order.
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900449 for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) {
Paul Duffinca4964c2017-02-07 15:04:10 +0000450 if (pickPredicate.test(i)) {
Jungshik Jang0f8b4b72014-05-28 17:58:58 +0900451 pollingCandidates.add(i);
452 }
453 }
454 break;
455 }
456 return pollingCandidates;
457 }
458
Jungshik Janga5b74142014-06-23 18:03:10 +0900459 @ServiceThreadOnly
Jungshik Jang02bb4262014-05-23 16:48:31 +0900460 private boolean isAllocatedLocalDeviceAddress(int address) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900461 assertRunOnServiceThread();
Jinsuk Kim7fe2ae02014-05-26 17:33:05 +0900462 for (int i = 0; i < mLocalDevices.size(); ++i) {
463 if (mLocalDevices.valueAt(i).isAddressOf(address)) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900464 return true;
465 }
466 }
467 return false;
468 }
469
Jungshik Janga5b74142014-06-23 18:03:10 +0900470 @ServiceThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +0900471 private void runDevicePolling(final int sourceAddress,
472 final List<Integer> candidates, final int retryCount,
Jungshik Jangb3ecb722014-09-11 16:09:45 +0900473 final DevicePollingCallback callback, final List<Integer> allocated) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900474 assertRunOnServiceThread();
Jungshik Jangb3ecb722014-09-11 16:09:45 +0900475 if (candidates.isEmpty()) {
476 if (callback != null) {
477 HdmiLogger.debug("[P]:AllocatedAddress=%s", allocated.toString());
478 callback.onPollingFinished(allocated);
479 }
480 return;
481 }
482
483 final Integer candidate = candidates.remove(0);
484 // Proceed polling action for the next address once polling action for the
485 // previous address is done.
Jungshik Jang02bb4262014-05-23 16:48:31 +0900486 runOnIoThread(new Runnable() {
487 @Override
488 public void run() {
Jungshik Jangb3ecb722014-09-11 16:09:45 +0900489 if (sendPollMessage(sourceAddress, candidate, retryCount)) {
490 allocated.add(candidate);
491 }
492 runOnServiceThread(new Runnable() {
493 @Override
494 public void run() {
495 runDevicePolling(sourceAddress, candidates, retryCount, callback,
496 allocated);
Jungshik Jang02bb4262014-05-23 16:48:31 +0900497 }
Jungshik Jangb3ecb722014-09-11 16:09:45 +0900498 });
Jungshik Jang02bb4262014-05-23 16:48:31 +0900499 }
500 });
501 }
502
Jungshik Janga5b74142014-06-23 18:03:10 +0900503 @IoThreadOnly
Jungshik Jang1de51422014-07-03 11:14:26 +0900504 private boolean sendPollMessage(int sourceAddress, int destinationAddress, int retryCount) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900505 assertRunOnIoThread();
506 for (int i = 0; i < retryCount; ++i) {
Jungshik Jang1de51422014-07-03 11:14:26 +0900507 // <Polling Message> is a message which has empty body.
Donghyun Chodbf9f9c2016-07-04 20:39:26 +0900508 int ret =
Amy28a9a9e2018-05-17 17:57:33 -0700509 mNativeWrapperImpl.nativeSendCecCommand(
510 mNativePtr, sourceAddress, destinationAddress, EMPTY_BODY);
Donghyun Chobc6e3722016-11-04 05:25:52 +0900511 if (ret == SendMessageResult.SUCCESS) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900512 return true;
Donghyun Chobc6e3722016-11-04 05:25:52 +0900513 } else if (ret != SendMessageResult.NACK) {
Donghyun Chodbf9f9c2016-07-04 20:39:26 +0900514 // Unusual failure
515 HdmiLogger.warning("Failed to send a polling message(%d->%d) with return code %d",
516 sourceAddress, destinationAddress, ret);
Jungshik Jang02bb4262014-05-23 16:48:31 +0900517 }
518 }
519 return false;
520 }
521
522 private void assertRunOnIoThread() {
523 if (Looper.myLooper() != mIoHandler.getLooper()) {
524 throw new IllegalStateException("Should run on io thread.");
525 }
526 }
527
528 private void assertRunOnServiceThread() {
529 if (Looper.myLooper() != mControlHandler.getLooper()) {
530 throw new IllegalStateException("Should run on service thread.");
531 }
532 }
533
534 // Run a Runnable on IO thread.
535 // It should be careful to access member variables on IO thread because
536 // it can be accessed from system thread as well.
Jungshik Jangd643f762014-05-22 19:28:09 +0900537 private void runOnIoThread(Runnable runnable) {
538 mIoHandler.post(runnable);
539 }
540
541 private void runOnServiceThread(Runnable runnable) {
542 mControlHandler.post(runnable);
543 }
544
Yuncheol Heof1702482014-11-27 19:52:01 +0900545 @ServiceThreadOnly
546 void flush(final Runnable runnable) {
547 assertRunOnServiceThread();
548 runOnIoThread(new Runnable() {
549 @Override
550 public void run() {
551 // This ensures the runnable for cleanup is performed after all the pending
552 // commands are processed by IO thread.
553 runOnServiceThread(runnable);
554 }
555 });
556 }
557
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900558 private boolean isAcceptableAddress(int address) {
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900559 // Can access command targeting devices available in local device or broadcast command.
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900560 if (address == Constants.ADDR_BROADCAST) {
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900561 return true;
562 }
Jungshik Jang02bb4262014-05-23 16:48:31 +0900563 return isAllocatedLocalDeviceAddress(address);
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900564 }
565
Jungshik Janga5b74142014-06-23 18:03:10 +0900566 @ServiceThreadOnly
Jungshik Jange9c77c82014-04-24 20:30:09 +0900567 private void onReceiveCommand(HdmiCecMessage message) {
Jungshik Jang02bb4262014-05-23 16:48:31 +0900568 assertRunOnServiceThread();
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900569 if (isAcceptableAddress(message.getDestination()) && mService.handleCecCommand(message)) {
Jungshik Janga1fa91f2014-05-08 20:56:41 +0900570 return;
571 }
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900572 // Not handled message, so we will reply it with <Feature Abort>.
573 maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
574 }
Jungshik Jang1827fdc2014-07-17 13:58:14 +0900575
Yuncheol Heo6aae6522014-08-05 14:48:37 +0900576 @ServiceThreadOnly
577 void maySendFeatureAbortCommand(HdmiCecMessage message, int reason) {
578 assertRunOnServiceThread();
579 // Swap the source and the destination.
580 int src = message.getDestination();
581 int dest = message.getSource();
582 if (src == Constants.ADDR_BROADCAST || dest == Constants.ADDR_UNREGISTERED) {
583 // Don't reply <Feature Abort> from the unregistered devices or for the broadcasted
584 // messages. See CEC 12.2 Protocol General Rules for detail.
585 return;
586 }
587 int originalOpcode = message.getOpcode();
588 if (originalOpcode == Constants.MESSAGE_FEATURE_ABORT) {
589 return;
590 }
591 sendCommand(
592 HdmiCecMessageBuilder.buildFeatureAbortCommand(src, dest, originalOpcode, reason));
Jungshik Jange9c77c82014-04-24 20:30:09 +0900593 }
594
Jungshik Janga5b74142014-06-23 18:03:10 +0900595 @ServiceThreadOnly
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900596 void sendCommand(HdmiCecMessage cecMessage) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900597 assertRunOnServiceThread();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900598 sendCommand(cecMessage, null);
599 }
600
Jungshik Janga5b74142014-06-23 18:03:10 +0900601 @ServiceThreadOnly
Jungshik Jangd643f762014-05-22 19:28:09 +0900602 void sendCommand(final HdmiCecMessage cecMessage,
603 final HdmiControlService.SendMessageCallback callback) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900604 assertRunOnServiceThread();
Donghyun Cho0b485b22016-12-13 17:21:01 +0900605 addMessageToHistory(false /* isReceived */, cecMessage);
Jungshik Jangd643f762014-05-22 19:28:09 +0900606 runOnIoThread(new Runnable() {
607 @Override
608 public void run() {
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900609 HdmiLogger.debug("[S]:" + cecMessage);
Jungshik Jangd643f762014-05-22 19:28:09 +0900610 byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
Jungshik Jang8ed86c42014-07-11 11:56:46 +0900611 int i = 0;
Donghyun Chobc6e3722016-11-04 05:25:52 +0900612 int errorCode = SendMessageResult.SUCCESS;
Jungshik Jang8ed86c42014-07-11 11:56:46 +0900613 do {
Amy28a9a9e2018-05-17 17:57:33 -0700614 errorCode = mNativeWrapperImpl.nativeSendCecCommand(mNativePtr,
615 cecMessage.getSource(), cecMessage.getDestination(), body);
Donghyun Chobc6e3722016-11-04 05:25:52 +0900616 if (errorCode == SendMessageResult.SUCCESS) {
Jungshik Jang8ed86c42014-07-11 11:56:46 +0900617 break;
618 }
619 } while (i++ < HdmiConfig.RETRANSMISSION_COUNT);
620
621 final int finalError = errorCode;
Donghyun Chobc6e3722016-11-04 05:25:52 +0900622 if (finalError != SendMessageResult.SUCCESS) {
Donghyun Cho0b485b22016-12-13 17:21:01 +0900623 Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError);
Yuncheol Heoece603b2014-05-23 20:10:19 +0900624 }
Jungshik Jangd643f762014-05-22 19:28:09 +0900625 if (callback != null) {
626 runOnServiceThread(new Runnable() {
627 @Override
628 public void run() {
Jungshik Jang8ed86c42014-07-11 11:56:46 +0900629 callback.onSendCompleted(finalError);
Jungshik Jangd643f762014-05-22 19:28:09 +0900630 }
631 });
632 }
633 }
634 });
Jungshik Jange9c77c82014-04-24 20:30:09 +0900635 }
636
Jungshik Jang0792d372014-04-23 17:57:26 +0900637 /**
Jungshik Jange9c77c82014-04-24 20:30:09 +0900638 * Called by native when incoming CEC message arrived.
Jungshik Jang0792d372014-04-23 17:57:26 +0900639 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900640 @ServiceThreadOnly
Jungshik Jange9c77c82014-04-24 20:30:09 +0900641 private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
Jungshik Jang4085d0e2014-05-27 19:52:39 +0900642 assertRunOnServiceThread();
Jungshik Jangc94ac5c2014-08-27 13:48:37 +0900643 HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900644 HdmiLogger.debug("[R]:" + command);
Donghyun Cho0b485b22016-12-13 17:21:01 +0900645 addMessageToHistory(true /* isReceived */, command);
Jungshik Jangc94ac5c2014-08-27 13:48:37 +0900646 onReceiveCommand(command);
Jungshik Jange9c77c82014-04-24 20:30:09 +0900647 }
648
649 /**
650 * Called by native when a hotplug event issues.
651 */
Jungshik Jang42230722014-07-07 17:40:25 +0900652 @ServiceThreadOnly
653 private void handleHotplug(int port, boolean connected) {
654 assertRunOnServiceThread();
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900655 HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected);
Jungshik Jang42230722014-07-07 17:40:25 +0900656 mService.onHotplug(port, connected);
Jungshik Jang0792d372014-04-23 17:57:26 +0900657 }
658
Donghyun Cho0b485b22016-12-13 17:21:01 +0900659 @ServiceThreadOnly
660 private void addMessageToHistory(boolean isReceived, HdmiCecMessage message) {
661 assertRunOnServiceThread();
662 MessageHistoryRecord record = new MessageHistoryRecord(isReceived, message);
663 if (!mMessageHistory.offer(record)) {
664 mMessageHistory.poll();
665 mMessageHistory.offer(record);
666 }
667 }
668
Terry Heo959d2db2014-08-28 16:45:41 +0900669 void dump(final IndentingPrintWriter pw) {
670 for (int i = 0; i < mLocalDevices.size(); ++i) {
671 pw.println("HdmiCecLocalDevice #" + i + ":");
672 pw.increaseIndent();
673 mLocalDevices.valueAt(i).dump(pw);
674 pw.decreaseIndent();
675 }
Donghyun Cho0b485b22016-12-13 17:21:01 +0900676 pw.println("CEC message history:");
677 pw.increaseIndent();
678 final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
679 for (MessageHistoryRecord record : mMessageHistory) {
680 record.dump(pw, sdf);
681 }
682 pw.decreaseIndent();
Terry Heo959d2db2014-08-28 16:45:41 +0900683 }
684
Amy28a9a9e2018-05-17 17:57:33 -0700685 protected interface NativeWrapper {
686 long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
687 int nativeSendCecCommand(long controllerPtr, int srcAddress, int dstAddress, byte[] body);
688 int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
689 void nativeClearLogicalAddress(long controllerPtr);
690 int nativeGetPhysicalAddress(long controllerPtr);
691 int nativeGetVersion(long controllerPtr);
692 int nativeGetVendorId(long controllerPtr);
693 HdmiPortInfo[] nativeGetPortInfos(long controllerPtr);
694 void nativeSetOption(long controllerPtr, int flag, boolean enabled);
695 void nativeSetLanguage(long controllerPtr, String language);
696 void nativeEnableAudioReturnChannel(long controllerPtr, int port, boolean flag);
697 boolean nativeIsConnected(long controllerPtr, int port);
698 }
699
Jungshik Jang4085d0e2014-05-27 19:52:39 +0900700 private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900701 private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
Amy28a9a9e2018-05-17 17:57:33 -0700702 int dstAddress, byte[] body);
Jungshik Janga9095ba2014-05-02 13:06:22 +0900703 private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
704 private static native void nativeClearLogicalAddress(long controllerPtr);
705 private static native int nativeGetPhysicalAddress(long controllerPtr);
706 private static native int nativeGetVersion(long controllerPtr);
707 private static native int nativeGetVendorId(long controllerPtr);
Jinsuk Kim0340bbc2014-06-05 11:07:47 +0900708 private static native HdmiPortInfo[] nativeGetPortInfos(long controllerPtr);
Donghyun Chobc6e3722016-11-04 05:25:52 +0900709 private static native void nativeSetOption(long controllerPtr, int flag, boolean enabled);
710 private static native void nativeSetLanguage(long controllerPtr, String language);
Amy28a9a9e2018-05-17 17:57:33 -0700711 private static native void nativeEnableAudioReturnChannel(long controllerPtr,
712 int port, boolean flag);
Jungshik Jang092b4452014-06-11 15:19:17 +0900713 private static native boolean nativeIsConnected(long controllerPtr, int port);
Donghyun Cho0b485b22016-12-13 17:21:01 +0900714
Amy28a9a9e2018-05-17 17:57:33 -0700715 private static final class NativeWrapperImpl implements NativeWrapper {
716
717 @Override
718 public long nativeInit(HdmiCecController handler, MessageQueue messageQueue) {
719 return HdmiCecController.nativeInit(handler, messageQueue);
720 }
721
722 @Override
723 public int nativeSendCecCommand(long controllerPtr, int srcAddress, int dstAddress,
724 byte[] body) {
725 return HdmiCecController.nativeSendCecCommand(controllerPtr, srcAddress, dstAddress, body);
726 }
727
728 @Override
729 public int nativeAddLogicalAddress(long controllerPtr, int logicalAddress) {
730 return HdmiCecController.nativeAddLogicalAddress(controllerPtr, logicalAddress);
731 }
732
733 @Override
734 public void nativeClearLogicalAddress(long controllerPtr) {
735 HdmiCecController.nativeClearLogicalAddress(controllerPtr);
736 }
737
738 @Override
739 public int nativeGetPhysicalAddress(long controllerPtr) {
740 return HdmiCecController.nativeGetPhysicalAddress(controllerPtr);
741 }
742
743 @Override
744 public int nativeGetVersion(long controllerPtr) {
745 return HdmiCecController.nativeGetVersion(controllerPtr);
746 }
747
748 @Override
749 public int nativeGetVendorId(long controllerPtr) {
750 return HdmiCecController.nativeGetVendorId(controllerPtr);
751 }
752
753 @Override
754 public HdmiPortInfo[] nativeGetPortInfos(long controllerPtr) {
755 return HdmiCecController.nativeGetPortInfos(controllerPtr);
756 }
757
758 @Override
759 public void nativeSetOption(long controllerPtr, int flag, boolean enabled) {
760 HdmiCecController.nativeSetOption(controllerPtr, flag, enabled);
761 }
762
763 @Override
764 public void nativeSetLanguage(long controllerPtr, String language) {
765 HdmiCecController.nativeSetLanguage(controllerPtr, language);
766 }
767
768 @Override
769 public void nativeEnableAudioReturnChannel(long controllerPtr, int port, boolean flag) {
770 HdmiCecController.nativeEnableAudioReturnChannel(controllerPtr, port, flag);
771 }
772
773 @Override
774 public boolean nativeIsConnected(long controllerPtr, int port) {
775 return HdmiCecController.nativeIsConnected(controllerPtr, port);
776 }
777 }
778
Donghyun Cho0b485b22016-12-13 17:21:01 +0900779 private final class MessageHistoryRecord {
780 private final long mTime;
781 private final boolean mIsReceived; // true if received message and false if sent message
782 private final HdmiCecMessage mMessage;
783
784 public MessageHistoryRecord(boolean isReceived, HdmiCecMessage message) {
785 mTime = System.currentTimeMillis();
786 mIsReceived = isReceived;
787 mMessage = message;
788 }
789
790 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
791 pw.print(mIsReceived ? "[R]" : "[S]");
792 pw.print(" time=");
793 pw.print(sdf.format(new Date(mTime)));
794 pw.print(" message=");
795 pw.println(mMessage);
796 }
797 }
Jungshik Jang0792d372014-04-23 17:57:26 +0900798}