blob: aff385dc23e1ab3c089290439400464a832017db [file] [log] [blame]
Jinsuk Kim91120c52014-05-08 17:12:51 +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 android.hardware.hdmi;
18
Amy17ee20f2018-10-11 11:08:23 -070019import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
20
Amy9444cdd2019-01-16 13:42:30 -080021import android.annotation.IntDef;
Amyf741f342019-02-28 13:38:23 -080022import android.annotation.NonNull;
Jinsuk Kim91120c52014-05-08 17:12:51 +090023import android.annotation.Nullable;
Jeff Sharkey98af2e42018-02-16 10:14:57 -070024import android.annotation.RequiresFeature;
Jeff Sharkeybfc4fcd2017-06-05 17:38:17 -060025import android.annotation.RequiresPermission;
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +090026import android.annotation.SdkConstant;
27import android.annotation.SdkConstant.SdkConstantType;
Jeff Sharkeybfc4fcd2017-06-05 17:38:17 -060028import android.annotation.SuppressLint;
Jinsuk Kim66d1eb22014-06-06 16:12:18 +090029import android.annotation.SystemApi;
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060030import android.annotation.SystemService;
Shubang67373192018-06-08 18:30:15 -070031import android.content.Context;
32import android.content.pm.PackageManager;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090033import android.os.RemoteException;
Amy17ee20f2018-10-11 11:08:23 -070034import android.os.SystemProperties;
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +090035import android.util.ArrayMap;
36import android.util.Log;
Jinsuk Kim78d695d2014-05-13 16:36:15 +090037
Amy9444cdd2019-01-16 13:42:30 -080038import com.android.internal.util.Preconditions;
39
Amy6f031af2018-10-30 16:38:33 -070040import java.util.List;
41
Jinsuk Kim91120c52014-05-08 17:12:51 +090042/**
43 * The {@link HdmiControlManager} class is used to send HDMI control messages
44 * to attached CEC devices.
45 *
46 * <p>Provides various HDMI client instances that represent HDMI-CEC logical devices
47 * hosted in the system. {@link #getTvClient()}, for instance will return an
48 * {@link HdmiTvClient} object if the system is configured to host one. Android system
49 * can host more than one logical CEC devices. If multiple types are configured they
50 * all work as if they were independent logical devices running in the system.
Jinsuk Kim66d1eb22014-06-06 16:12:18 +090051 *
52 * @hide
Jinsuk Kim91120c52014-05-08 17:12:51 +090053 */
Jinsuk Kim66d1eb22014-06-06 16:12:18 +090054@SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060055@SystemService(Context.HDMI_CONTROL_SERVICE)
Jeff Sharkey98af2e42018-02-16 10:14:57 -070056@RequiresFeature(PackageManager.FEATURE_HDMI_CEC)
Jinsuk Kim91120c52014-05-08 17:12:51 +090057public final class HdmiControlManager {
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +090058 private static final String TAG = "HdmiControlManager";
59
Jinsuk Kim91120c52014-05-08 17:12:51 +090060 @Nullable private final IHdmiControlService mService;
61
Amyff115f12018-11-21 14:26:16 -080062 private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
63
64 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
65
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +090066 /**
67 * Broadcast Action: Display OSD message.
68 * <p>Send when the service has a message to display on screen for events
69 * that need user's attention such as ARC status change.
Jungshik Jange5a93372014-07-25 13:41:14 +090070 * <p>Always contains the extra fields {@link #EXTRA_MESSAGE_ID}.
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +090071 * <p>Requires {@link android.Manifest.permission#HDMI_CEC} to receive.
72 */
73 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
74 public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
75
Jungshik Jang339227d2014-08-25 15:37:20 +090076 // --- Messages for ACTION_OSD_MESSAGE ---
77 /**
78 * Message that ARC enabled device is connected to invalid port (non-ARC port).
79 */
80 public static final int OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT = 1;
81
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +090082 /**
Jungshik Jang2e8f1b62014-09-03 08:28:02 +090083 * Message used by TV to receive volume status from Audio Receiver. It should check volume value
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +090084 * that is retrieved from extra value with the key {@link #EXTRA_MESSAGE_EXTRA_PARAM1}. If the
Jungshik Jang2e8f1b62014-09-03 08:28:02 +090085 * value is in range of [0,100], it is current volume of Audio Receiver. And there is another
86 * value, {@link #AVR_VOLUME_MUTED}, which is used to inform volume mute.
87 */
88 public static final int OSD_MESSAGE_AVR_VOLUME_CHANGED = 2;
89
90 /**
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +090091 * Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the ID of
92 * the message to display on screen.
93 */
94 public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID";
Jungshik Jang2e8f1b62014-09-03 08:28:02 +090095 /**
96 * Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the extra value
97 * of the message.
98 */
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +090099 public static final String EXTRA_MESSAGE_EXTRA_PARAM1 =
Jungshik Jang2e8f1b62014-09-03 08:28:02 +0900100 "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
101
102 /**
103 * Volume value for mute state.
104 */
105 public static final int AVR_VOLUME_MUTED = 101;
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +0900106
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900107 public static final int POWER_STATUS_UNKNOWN = -1;
108 public static final int POWER_STATUS_ON = 0;
109 public static final int POWER_STATUS_STANDBY = 1;
110 public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
111 public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
112
Amy9444cdd2019-01-16 13:42:30 -0800113 @IntDef ({
114 RESULT_SUCCESS,
115 RESULT_TIMEOUT,
116 RESULT_SOURCE_NOT_AVAILABLE,
117 RESULT_TARGET_NOT_AVAILABLE,
118 RESULT_ALREADY_IN_PROGRESS,
119 RESULT_EXCEPTION,
120 RESULT_INCORRECT_MODE,
121 RESULT_COMMUNICATION_FAILED,
122 })
123 public @interface ControlCallbackResult {}
124
125 /** Control operation is successfully handled by the framework. */
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900126 public static final int RESULT_SUCCESS = 0;
127 public static final int RESULT_TIMEOUT = 1;
Amy9444cdd2019-01-16 13:42:30 -0800128 /** Source device that the application is using is not available. */
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900129 public static final int RESULT_SOURCE_NOT_AVAILABLE = 2;
Amy9444cdd2019-01-16 13:42:30 -0800130 /** Target device that the application is controlling is not available. */
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900131 public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
Jinsuk Kimcb802872015-10-13 08:22:09 +0900132
133 @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900134 public static final int RESULT_EXCEPTION = 5;
135 public static final int RESULT_INCORRECT_MODE = 6;
Jinsuk Kimb38cd682014-07-07 08:05:03 +0900136 public static final int RESULT_COMMUNICATION_FAILED = 7;
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900137
Jungshik Jang61daf6b2014-08-08 11:38:28 +0900138 public static final int DEVICE_EVENT_ADD_DEVICE = 1;
139 public static final int DEVICE_EVENT_REMOVE_DEVICE = 2;
140 public static final int DEVICE_EVENT_UPDATE_DEVICE = 3;
141
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900142 // --- One Touch Recording success result
Jungshik Jangb6591b82014-07-23 16:10:23 +0900143 /** Recording currently selected source. Indicates the status of a recording. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900144 public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x01;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900145 /** Recording Digital Service. Indicates the status of a recording. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900146 public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 0x02;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900147 /** Recording Analogue Service. Indicates the status of a recording. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900148 public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 0x03;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900149 /** Recording External input. Indicates the status of a recording. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900150 public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 0x04;
151
152 // --- One Touch Record failure result
Jungshik Jangb6591b82014-07-23 16:10:23 +0900153 /** No recording – unable to record Digital Service. No suitable tuner. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900154 public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 0x05;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900155 /** No recording – unable to record Analogue Service. No suitable tuner. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900156 public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 0x06;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900157 /**
158 * No recording – unable to select required service. as suitable tuner, but the requested
159 * parameters are invalid or out of range for that tuner.
160 */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900161 public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 0x07;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900162 /** No recording – invalid External plug number */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900163 public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 0x09;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900164 /** No recording – invalid External Physical Address */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900165 public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x0A;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900166 /** No recording – CA system not supported */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900167 public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 0x0B;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900168 /** No Recording – No or Insufficient CA Entitlements” */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900169 public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x0C;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900170 /** No recording – Not allowed to copy source. Source is “copy never”. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900171 public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 0x0D;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900172 /** No recording – No further copies allowed */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900173 public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 0x0E;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900174 /** No recording – No media */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900175 public static final int ONE_TOUCH_RECORD_NO_MEDIA = 0x10;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900176 /** No recording – playing */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900177 public static final int ONE_TOUCH_RECORD_PLAYING = 0x11;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900178 /** No recording – already recording */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900179 public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 0x12;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900180 /** No recording – media protected */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900181 public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 0x13;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900182 /** No recording – no source signal */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900183 public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 0x14;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900184 /** No recording – media problem */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900185 public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 0x15;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900186 /** No recording – not enough space available */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900187 public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 0x16;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900188 /** No recording – Parental Lock On */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900189 public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 0x17;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900190 /** Recording terminated normally */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900191 public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 0x1A;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900192 /** Recording has already terminated */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900193 public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 0x1B;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900194 /** No recording – other reason */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900195 public static final int ONE_TOUCH_RECORD_OTHER_REASON = 0x1F;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900196 // From here extra message for recording that is not mentioned in CEC spec
197 /** No recording. Previous recording request in progress. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900198 public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 0x30;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900199 /** No recording. Please check recorder and connection. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900200 public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 0x31;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900201 /** Cannot record currently displayed source. */
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900202 public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x32;
203 /** CEC is disabled. */
204 public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 0x33;
Jungshik Jangb6591b82014-07-23 16:10:23 +0900205
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900206 // --- Types for timer recording
Jungshik Jangb6591b82014-07-23 16:10:23 +0900207 /** Timer recording type for digital service source. */
208 public static final int TIMER_RECORDING_TYPE_DIGITAL = 1;
209 /** Timer recording type for analogue service source. */
210 public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2;
211 /** Timer recording type for external source. */
212 public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3;
213
Jungshik Jange5a93372014-07-25 13:41:14 +0900214 // --- Timer Status Data
215 /** [Timer Status Data/Media Info] - Media present and not protected. */
216 public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_NOT_PROTECTED = 0x0;
217 /** [Timer Status Data/Media Info] - Media present, but protected. */
218 public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_PROTECTED = 0x1;
219 /** [Timer Status Data/Media Info] - Media not present. */
220 public static final int TIMER_STATUS_MEDIA_INFO_NOT_PRESENT = 0x2;
221
222 /** [Timer Status Data/Programmed Info] - Enough space available for recording. */
223 public static final int TIMER_STATUS_PROGRAMMED_INFO_ENOUGH_SPACE = 0x8;
224 /** [Timer Status Data/Programmed Info] - Not enough space available for recording. */
225 public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 0x9;
226 /** [Timer Status Data/Programmed Info] - Might not enough space available for recording. */
227 public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 0xB;
228 /** [Timer Status Data/Programmed Info] - No media info available. */
229 public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 0xA;
230
231 /** [Timer Status Data/Not Programmed Error Info] - No free timer available. */
232 public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_FREE_TIME = 0x1;
233 /** [Timer Status Data/Not Programmed Error Info] - Date out of range. */
234 public static final int TIMER_STATUS_NOT_PROGRAMMED_DATE_OUT_OF_RANGE = 0x2;
235 /** [Timer Status Data/Not Programmed Error Info] - Recording Sequence error. */
236 public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_SEQUENCE = 0x3;
237 /** [Timer Status Data/Not Programmed Error Info] - Invalid External Plug Number. */
238 public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PLUG_NUMBER = 0x4;
239 /** [Timer Status Data/Not Programmed Error Info] - Invalid External Physical Address. */
240 public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PHYSICAL_NUMBER = 0x5;
241 /** [Timer Status Data/Not Programmed Error Info] - CA system not supported. */
242 public static final int TIMER_STATUS_NOT_PROGRAMMED_CA_NOT_SUPPORTED = 0x6;
243 /** [Timer Status Data/Not Programmed Error Info] - No or insufficient CA Entitlements. */
244 public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_CA_ENTITLEMENTS = 0x7;
245 /** [Timer Status Data/Not Programmed Error Info] - Does not support resolution. */
246 public static final int TIMER_STATUS_NOT_PROGRAMMED_UNSUPPORTED_RESOLUTION = 0x8;
247 /** [Timer Status Data/Not Programmed Error Info] - Parental Lock On. */
248 public static final int TIMER_STATUS_NOT_PROGRAMMED_PARENTAL_LOCK_ON= 0x9;
249 /** [Timer Status Data/Not Programmed Error Info] - Clock Failure. */
250 public static final int TIMER_STATUS_NOT_PROGRAMMED_CLOCK_FAILURE = 0xA;
251 /** [Timer Status Data/Not Programmed Error Info] - Duplicate: already programmed. */
252 public static final int TIMER_STATUS_NOT_PROGRAMMED_DUPLICATED = 0xE;
253
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900254 // --- Extra result value for timer recording.
Jungshik Jange5a93372014-07-25 13:41:14 +0900255 /** No extra error. */
256 public static final int TIMER_RECORDING_RESULT_EXTRA_NO_ERROR = 0x00;
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900257 /** No timer recording - check recorder and connection. */
Jungshik Jange5a93372014-07-25 13:41:14 +0900258 public static final int TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 0x01;
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900259 /** No timer recording - cannot record selected source. */
Jungshik Jange5a93372014-07-25 13:41:14 +0900260 public static final int TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 0x02;
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900261 /** CEC is disabled. */
Jungshik Jange5a93372014-07-25 13:41:14 +0900262 public static final int TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED = 0x03;
263
264 // -- Timer cleared status data code used for result of onClearTimerRecordingResult.
265 /** Timer not cleared – recording. */
266 public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING = 0x00;
267 /** Timer not cleared – no matching. */
268 public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING = 0x01;
269 /** Timer not cleared – no info available. */
270 public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_INFO_AVAILABLE = 0x02;
271 /** Timer cleared. */
272 public static final int CLEAR_TIMER_STATUS_TIMER_CLEARED = 0x80;
273 /** Clear timer error - check recorder and connection. */
274 public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 0xA0;
275 /** Clear timer error - cannot clear timer for selected source. */
276 public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 0xA1;
277 /** Clear timer error - CEC is disabled. */
278 public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 0xA2;
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900279
Yuncheol Heo0608b932014-10-13 16:39:18 +0900280 /** The HdmiControlService is started. */
281 public static final int CONTROL_STATE_CHANGED_REASON_START = 0;
282 /** The state of HdmiControlService is changed by changing of settings. */
283 public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1;
284 /** The HdmiControlService is enabled to wake up. */
285 public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2;
286 /** The HdmiControlService will be disabled to standby. */
287 public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3;
288
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900289 // True if we have a logical device of type playback hosted in the system.
290 private final boolean mHasPlaybackDevice;
291 // True if we have a logical device of type TV hosted in the system.
292 private final boolean mHasTvDevice;
Shubang67373192018-06-08 18:30:15 -0700293 // True if we have a logical device of type audio system hosted in the system.
294 private final boolean mHasAudioSystemDevice;
Amy17ee20f2018-10-11 11:08:23 -0700295 // True if we have a logical device of type audio system hosted in the system.
296 private final boolean mHasSwitchDevice;
297 // True if it's a switch device.
298 private final boolean mIsSwitchDevice;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900299
Jinsuk Kim91120c52014-05-08 17:12:51 +0900300 /**
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +0900301 * {@hide} - hide this constructor because it has a parameter of type IHdmiControlService,
302 * which is a system private class. The right way to create an instance of this class is
303 * using the factory Context.getSystemService.
Jinsuk Kim91120c52014-05-08 17:12:51 +0900304 */
305 public HdmiControlManager(IHdmiControlService service) {
306 mService = service;
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900307 int[] types = null;
308 if (mService != null) {
309 try {
310 types = mService.getSupportedTypes();
311 } catch (RemoteException e) {
Jeff Sharkeyc53962d2016-03-01 19:27:23 -0700312 throw e.rethrowFromSystemServer();
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900313 }
314 }
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900315 mHasTvDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_TV);
316 mHasPlaybackDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PLAYBACK);
Shubang67373192018-06-08 18:30:15 -0700317 mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
Amy17ee20f2018-10-11 11:08:23 -0700318 mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
319 mIsSwitchDevice = SystemProperties.getBoolean(
320 PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900321 }
322
323 private static boolean hasDeviceType(int[] types, int type) {
324 if (types == null) {
325 return false;
326 }
327 for (int t : types) {
328 if (t == type) {
329 return true;
330 }
331 }
332 return false;
Jinsuk Kim91120c52014-05-08 17:12:51 +0900333 }
334
335 /**
Jinsuk Kim6ffb0382014-08-01 19:13:53 +0900336 * Gets an object that represents an HDMI-CEC logical device of a specified type.
337 *
338 * @param type CEC device type
339 * @return {@link HdmiClient} instance. {@code null} on failure.
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900340 * See {@link HdmiDeviceInfo#DEVICE_PLAYBACK}
341 * See {@link HdmiDeviceInfo#DEVICE_TV}
Shubang67373192018-06-08 18:30:15 -0700342 * See {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM}
Jinsuk Kim6ffb0382014-08-01 19:13:53 +0900343 */
344 @Nullable
Jeff Sharkeybfc4fcd2017-06-05 17:38:17 -0600345 @SuppressLint("Doclava125")
Jinsuk Kim6ffb0382014-08-01 19:13:53 +0900346 public HdmiClient getClient(int type) {
347 if (mService == null) {
348 return null;
349 }
350 switch (type) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900351 case HdmiDeviceInfo.DEVICE_TV:
Jinsuk Kim6ffb0382014-08-01 19:13:53 +0900352 return mHasTvDevice ? new HdmiTvClient(mService) : null;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900353 case HdmiDeviceInfo.DEVICE_PLAYBACK:
Jinsuk Kim6ffb0382014-08-01 19:13:53 +0900354 return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null;
Shubang67373192018-06-08 18:30:15 -0700355 case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
356 return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null;
Amy17ee20f2018-10-11 11:08:23 -0700357 case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
358 return (mHasSwitchDevice || mIsSwitchDevice)
359 ? new HdmiSwitchClient(mService) : null;
Jinsuk Kim6ffb0382014-08-01 19:13:53 +0900360 default:
361 return null;
362 }
363 }
364
365 /**
366 * Gets an object that represents an HDMI-CEC logical device of type playback on the system.
Jinsuk Kim91120c52014-05-08 17:12:51 +0900367 *
368 * <p>Used to send HDMI control messages to other devices like TV or audio amplifier through
369 * HDMI bus. It is also possible to communicate with other logical devices hosted in the same
370 * system if the system is configured to host more than one type of HDMI-CEC logical devices.
371 *
372 * @return {@link HdmiPlaybackClient} instance. {@code null} on failure.
373 */
374 @Nullable
Jeff Sharkeybfc4fcd2017-06-05 17:38:17 -0600375 @SuppressLint("Doclava125")
Jinsuk Kim91120c52014-05-08 17:12:51 +0900376 public HdmiPlaybackClient getPlaybackClient() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900377 return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK);
Jinsuk Kim91120c52014-05-08 17:12:51 +0900378 }
379
380 /**
Jinsuk Kim6ffb0382014-08-01 19:13:53 +0900381 * Gets an object that represents an HDMI-CEC logical device of type TV on the system.
Jinsuk Kim91120c52014-05-08 17:12:51 +0900382 *
383 * <p>Used to send HDMI control messages to other devices and manage them through
384 * HDMI bus. It is also possible to communicate with other logical devices hosted in the same
385 * system if the system is configured to host more than one type of HDMI-CEC logical devices.
386 *
387 * @return {@link HdmiTvClient} instance. {@code null} on failure.
388 */
389 @Nullable
Jeff Sharkeybfc4fcd2017-06-05 17:38:17 -0600390 @SuppressLint("Doclava125")
Jinsuk Kim91120c52014-05-08 17:12:51 +0900391 public HdmiTvClient getTvClient() {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900392 return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV);
Jinsuk Kim91120c52014-05-08 17:12:51 +0900393 }
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900394
395 /**
Shubang67373192018-06-08 18:30:15 -0700396 * Gets an object that represents an HDMI-CEC logical device of type audio system on the system.
397 *
398 * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also
399 * possible to communicate with other logical devices hosted in the same system if the system is
400 * configured to host more than one type of HDMI-CEC logical devices.
401 *
402 * @return {@link HdmiAudioSystemClient} instance. {@code null} on failure.
403 *
404 * TODO(b/110094868): unhide for Q
405 * @hide
406 */
407 @Nullable
408 @SuppressLint("Doclava125")
409 public HdmiAudioSystemClient getAudioSystemClient() {
410 return (HdmiAudioSystemClient) getClient(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
411 }
412
413 /**
Amy17ee20f2018-10-11 11:08:23 -0700414 * Gets an object that represents an HDMI-CEC logical device of type switch on the system.
415 *
Amy9444cdd2019-01-16 13:42:30 -0800416 * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus.
417 * It is also possible to communicate with other logical devices hosted in the same
418 * system if the system is configured to host more than one type of HDMI-CEC logical device.
Amy17ee20f2018-10-11 11:08:23 -0700419 *
420 * @return {@link HdmiSwitchClient} instance. {@code null} on failure.
Amy17ee20f2018-10-11 11:08:23 -0700421 * @hide
422 */
423 @Nullable
Amy9444cdd2019-01-16 13:42:30 -0800424 @SystemApi
Amy17ee20f2018-10-11 11:08:23 -0700425 @SuppressLint("Doclava125")
426 public HdmiSwitchClient getSwitchClient() {
427 return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
428 }
429
430 /**
Amy6f031af2018-10-30 16:38:33 -0700431 * Get a snapshot of the real-time status of the remote devices.
432 *
Amy9444cdd2019-01-16 13:42:30 -0800433 * <p>This only applies to devices with multiple HDMI inputs.
Amy6f031af2018-10-30 16:38:33 -0700434 *
Amy9444cdd2019-01-16 13:42:30 -0800435 * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices. An empty
436 * list will be returned if there is none.
437 *
Amy6f031af2018-10-30 16:38:33 -0700438 * @hide
439 */
Amy9444cdd2019-01-16 13:42:30 -0800440 @SystemApi
441 @Nullable
Amy6f031af2018-10-30 16:38:33 -0700442 public List<HdmiDeviceInfo> getConnectedDevicesList() {
443 try {
444 return mService.getDeviceList();
445 } catch (RemoteException e) {
446 throw e.rethrowFromSystemServer();
447 }
448 }
449
450 /**
Amy9444cdd2019-01-16 13:42:30 -0800451 * Power off the target device by sending CEC commands.
Amy6f031af2018-10-30 16:38:33 -0700452 *
Amy9444cdd2019-01-16 13:42:30 -0800453 * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
Amy6f031af2018-10-30 16:38:33 -0700454 *
Amy9444cdd2019-01-16 13:42:30 -0800455 * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off.
456 *
Amy6f031af2018-10-30 16:38:33 -0700457 * @hide
458 */
Amy9444cdd2019-01-16 13:42:30 -0800459 @SystemApi
Amyf741f342019-02-28 13:38:23 -0800460 public void powerOffRemoteDevice(@NonNull HdmiDeviceInfo deviceInfo) {
Amy9444cdd2019-01-16 13:42:30 -0800461 Preconditions.checkNotNull(deviceInfo);
Amy6f031af2018-10-30 16:38:33 -0700462 try {
463 mService.powerOffRemoteDevice(
464 deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
465 } catch (RemoteException e) {
466 throw e.rethrowFromSystemServer();
467 }
468 }
469
470 /**
Amy9444cdd2019-01-16 13:42:30 -0800471 * Power on the target device by sending CEC commands.
Amy6f031af2018-10-30 16:38:33 -0700472 *
Amy9444cdd2019-01-16 13:42:30 -0800473 * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
Amy6f031af2018-10-30 16:38:33 -0700474 *
Amy9444cdd2019-01-16 13:42:30 -0800475 * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on.
476 *
Amy6f031af2018-10-30 16:38:33 -0700477 * @hide
478 */
479 public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
Amy9444cdd2019-01-16 13:42:30 -0800480 Preconditions.checkNotNull(deviceInfo);
Amy6f031af2018-10-30 16:38:33 -0700481 try {
482 mService.powerOnRemoteDevice(
483 deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
484 } catch (RemoteException e) {
485 throw e.rethrowFromSystemServer();
486 }
487 }
488
489 /**
Amy9444cdd2019-01-16 13:42:30 -0800490 * Request the target device to be the new Active Source by sending CEC commands.
491 *
492 * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
Amy6f031af2018-10-30 16:38:33 -0700493 *
494 * @param deviceInfo HdmiDeviceInfo of the target device
495 *
Amy6f031af2018-10-30 16:38:33 -0700496 * @hide
497 */
Amy9444cdd2019-01-16 13:42:30 -0800498 @SystemApi
Amyf741f342019-02-28 13:38:23 -0800499 public void requestRemoteDeviceToBecomeActiveSource(@NonNull HdmiDeviceInfo deviceInfo) {
Amy9444cdd2019-01-16 13:42:30 -0800500 Preconditions.checkNotNull(deviceInfo);
Amy6f031af2018-10-30 16:38:33 -0700501 try {
502 mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
503 } catch (RemoteException e) {
504 throw e.rethrowFromSystemServer();
505 }
506 }
507
508 /**
Donghyun Chob3515642017-03-02 13:47:40 +0900509 * Controls standby mode of the system. It will also try to turn on/off the connected devices if
510 * necessary.
511 *
512 * @param isStandbyModeOn target status of the system's standby mode
513 */
Jeff Sharkeybfc4fcd2017-06-05 17:38:17 -0600514 @RequiresPermission(android.Manifest.permission.HDMI_CEC)
Donghyun Chob3515642017-03-02 13:47:40 +0900515 public void setStandbyMode(boolean isStandbyModeOn) {
516 try {
517 mService.setStandbyMode(isStandbyModeOn);
518 } catch (RemoteException e) {
519 throw e.rethrowFromSystemServer();
520 }
521 }
522
523 /**
Shubang Lu00b976a2018-08-01 18:11:46 -0700524 * Gets whether the system is in system audio mode.
525 *
526 * @hide
527 */
528 public boolean getSystemAudioMode() {
529 try {
530 return mService.getSystemAudioMode();
531 } catch (RemoteException e) {
532 throw e.rethrowFromSystemServer();
533 }
534 }
535
536 /**
Amyd58d0aa2018-10-18 14:08:57 -0700537 * Get the physical address of the device.
538 *
Amy9444cdd2019-01-16 13:42:30 -0800539 * <p>Physical address needs to be automatically adjusted when devices are phyiscally or
540 * electrically added or removed from the device tree. Please see HDMI Specification Version
541 * 1.4b 8.7 Physical Address for more details on the address discovery proccess.
542 *
Amyd58d0aa2018-10-18 14:08:57 -0700543 * @hide
544 */
Amy9444cdd2019-01-16 13:42:30 -0800545 @SystemApi
Amyd58d0aa2018-10-18 14:08:57 -0700546 public int getPhysicalAddress() {
Amyff115f12018-11-21 14:26:16 -0800547 if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) {
548 return mPhysicalAddress;
549 }
Amyd58d0aa2018-10-18 14:08:57 -0700550 try {
Amyff115f12018-11-21 14:26:16 -0800551 mPhysicalAddress = mService.getPhysicalAddress();
552 return mPhysicalAddress;
Amyd58d0aa2018-10-18 14:08:57 -0700553 } catch (RemoteException e) {
554 throw e.rethrowFromSystemServer();
555 }
556 }
557
558 /**
Amy9444cdd2019-01-16 13:42:30 -0800559 * Check if the target remote device is connected to the current device.
560 *
561 * <p>The API also returns true if the current device is the target.
Amyff115f12018-11-21 14:26:16 -0800562 *
563 * @param targetDevice {@link HdmiDeviceInfo} of the target device.
Amy9444cdd2019-01-16 13:42:30 -0800564 * @return true if {@code targetDevice} is directly or indirectly
565 * connected to the current device.
Amyff115f12018-11-21 14:26:16 -0800566 *
Amyff115f12018-11-21 14:26:16 -0800567 * @hide
568 */
Amy9444cdd2019-01-16 13:42:30 -0800569 @SystemApi
Amyf741f342019-02-28 13:38:23 -0800570 public boolean isRemoteDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
Amy9444cdd2019-01-16 13:42:30 -0800571 Preconditions.checkNotNull(targetDevice);
Amyff115f12018-11-21 14:26:16 -0800572 mPhysicalAddress = getPhysicalAddress();
573 if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
574 return false;
575 }
576 int targetPhysicalAddress = targetDevice.getPhysicalAddress();
577 if (targetPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
578 return false;
579 }
580 return HdmiUtils.getLocalPortFromPhysicalAddress(targetPhysicalAddress, mPhysicalAddress)
581 != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE;
582 }
583
584 /**
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900585 * Listener used to get hotplug event from HDMI port.
586 */
587 public interface HotplugEventListener {
588 void onReceived(HdmiHotplugEvent event);
589 }
590
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +0900591 private final ArrayMap<HotplugEventListener, IHdmiHotplugEventListener>
592 mHotplugEventListeners = new ArrayMap<>();
593
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900594 /**
Jinsuk Kim119160a2014-07-07 18:48:10 +0900595 * Listener used to get vendor-specific commands.
596 */
597 public interface VendorCommandListener {
598 /**
599 * Called when a vendor command is received.
600 *
601 * @param srcAddress source logical address
Yuncheol Heo0608b932014-10-13 16:39:18 +0900602 * @param destAddress destination logical address
Jinsuk Kim119160a2014-07-07 18:48:10 +0900603 * @param params vendor-specific parameters
604 * @param hasVendorId {@code true} if the command is &lt;Vendor Command
605 * With ID&gt;. The first 3 bytes of params is vendor id.
606 */
Yuncheol Heo0608b932014-10-13 16:39:18 +0900607 void onReceived(int srcAddress, int destAddress, byte[] params, boolean hasVendorId);
608
609 /**
610 * The callback is called:
611 * <ul>
612 * <li> before HdmiControlService is disabled.
613 * <li> after HdmiControlService is enabled and the local address is assigned.
614 * </ul>
615 * The client shouldn't hold the thread too long since this is a blocking call.
616 *
617 * @param enabled {@code true} if HdmiControlService is enabled.
618 * @param reason the reason code why the state of HdmiControlService is changed.
619 * @see #CONTROL_STATE_CHANGED_REASON_START
620 * @see #CONTROL_STATE_CHANGED_REASON_SETTING
621 * @see #CONTROL_STATE_CHANGED_REASON_WAKEUP
622 * @see #CONTROL_STATE_CHANGED_REASON_STANDBY
623 */
624 void onControlStateChanged(boolean enabled, int reason);
Jinsuk Kim119160a2014-07-07 18:48:10 +0900625 }
626
627 /**
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900628 * Adds a listener to get informed of {@link HdmiHotplugEvent}.
629 *
630 * <p>To stop getting the notification,
Jinsuk Kim9302a732014-05-22 13:24:55 +0900631 * use {@link #removeHotplugEventListener(HotplugEventListener)}.
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900632 *
633 * @param listener {@link HotplugEventListener} instance
Jinsuk Kim9302a732014-05-22 13:24:55 +0900634 * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener)
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900635 */
Jeff Sharkeybfc4fcd2017-06-05 17:38:17 -0600636 @RequiresPermission(android.Manifest.permission.HDMI_CEC)
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900637 public void addHotplugEventListener(HotplugEventListener listener) {
638 if (mService == null) {
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +0900639 Log.e(TAG, "HdmiControlService is not available");
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900640 return;
641 }
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +0900642 if (mHotplugEventListeners.containsKey(listener)) {
643 Log.e(TAG, "listener is already registered");
644 return;
645 }
646 IHdmiHotplugEventListener wrappedListener = getHotplugEventListenerWrapper(listener);
647 mHotplugEventListeners.put(listener, wrappedListener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900648 try {
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +0900649 mService.addHotplugEventListener(wrappedListener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900650 } catch (RemoteException e) {
Jeff Sharkeyc53962d2016-03-01 19:27:23 -0700651 throw e.rethrowFromSystemServer();
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900652 }
653 }
654
655 /**
656 * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}.
657 *
658 * @param listener {@link HotplugEventListener} instance to be removed
659 */
Jeff Sharkeybfc4fcd2017-06-05 17:38:17 -0600660 @RequiresPermission(android.Manifest.permission.HDMI_CEC)
Jinsuk Kim9302a732014-05-22 13:24:55 +0900661 public void removeHotplugEventListener(HotplugEventListener listener) {
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900662 if (mService == null) {
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +0900663 Log.e(TAG, "HdmiControlService is not available");
664 return;
665 }
666 IHdmiHotplugEventListener wrappedListener = mHotplugEventListeners.remove(listener);
667 if (wrappedListener == null) {
668 Log.e(TAG, "tried to remove not-registered listener");
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900669 return;
670 }
671 try {
Yuncheol Heo2b0da5c2014-10-22 14:32:27 +0900672 mService.removeHotplugEventListener(wrappedListener);
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900673 } catch (RemoteException e) {
Jeff Sharkeyc53962d2016-03-01 19:27:23 -0700674 throw e.rethrowFromSystemServer();
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900675 }
676 }
677
678 private IHdmiHotplugEventListener getHotplugEventListenerWrapper(
679 final HotplugEventListener listener) {
680 return new IHdmiHotplugEventListener.Stub() {
Jungshik Jange5a93372014-07-25 13:41:14 +0900681 @Override
Jinsuk Kim78d695d2014-05-13 16:36:15 +0900682 public void onReceived(HdmiHotplugEvent event) {
683 listener.onReceived(event);;
684 }
685 };
686 }
Jinsuk Kim91120c52014-05-08 17:12:51 +0900687}