blob: 7ae2198c8d0d3ac3671dac22e8ce57193ac97ecb [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
Jungshik Jange5a93372014-07-25 13:41:14 +090019import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE;
20import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION;
21import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE;
Jungshik Jang12e5dce2014-07-24 15:27:44 +090022import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
23import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
24import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
Jungshik Jang339227d2014-08-25 15:37:20 +090025import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT;
Jungshik Jange5a93372014-07-25 13:41:14 +090026import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED;
27import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
28import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
29import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
30import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
31import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
Jungshik Jang12e5dce2014-07-24 15:27:44 +090032
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090033import android.hardware.hdmi.HdmiControlManager;
Jinsuk Kim50084862014-08-07 13:11:40 +090034import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jangb6591b82014-07-23 16:10:23 +090035import android.hardware.hdmi.HdmiRecordSources;
Jungshik Jang12e5dce2014-07-24 15:27:44 +090036import android.hardware.hdmi.HdmiTimerRecordSources;
Jungshik Jang60cffce2014-06-12 18:03:04 +090037import android.hardware.hdmi.IHdmiControlCallback;
Jungshik Jangb69aafb2014-07-11 16:29:06 +090038import android.media.AudioManager;
Jungshik Janga858d222014-06-23 17:17:47 +090039import android.media.AudioSystem;
Jinsuk Kima6ce7702014-05-11 06:54:49 +090040import android.os.RemoteException;
Jinsuk Kimaf2acf02014-07-11 18:43:04 +090041import android.os.SystemProperties;
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +090042import android.provider.Settings.Global;
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +090043import android.util.ArraySet;
Jungshik Jang092b4452014-06-11 15:19:17 +090044import android.util.Slog;
Jungshik Jang79c58a42014-06-16 16:45:36 +090045import android.util.SparseArray;
Jungshik Jang092b4452014-06-11 15:19:17 +090046
Jungshik Jang79c58a42014-06-16 16:45:36 +090047import com.android.internal.annotations.GuardedBy;
Terry Heo959d2db2014-08-28 16:45:41 +090048import com.android.internal.util.IndentingPrintWriter;
Jungshik Jang60cffce2014-06-12 18:03:04 +090049import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
Jungshik Janga5b74142014-06-23 18:03:10 +090050import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Jungshik Jange5a93372014-07-25 13:41:14 +090051import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
Jungshik Jang60cffce2014-06-12 18:03:04 +090052
Jungshik Jang8f2ed352014-07-07 15:02:47 +090053import java.io.UnsupportedEncodingException;
Jungshik Jang79c58a42014-06-16 16:45:36 +090054import java.util.ArrayList;
Jungshik Jangb6591b82014-07-23 16:10:23 +090055import java.util.Arrays;
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +090056import java.util.Collection;
Jinsuk Kim0a3316b2014-06-14 09:33:55 +090057import java.util.Collections;
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +090058import java.util.Iterator;
Jinsuk Kim0a3316b2014-06-14 09:33:55 +090059import java.util.List;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090060
61/**
62 * Represent a logical device of type TV residing in Android system.
63 */
64final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
Jungshik Jang092b4452014-06-11 15:19:17 +090065 private static final String TAG = "HdmiCecLocalDeviceTv";
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090066
Jungshik Jang12e5dce2014-07-24 15:27:44 +090067 // Whether ARC is available or not. "true" means that ARC is established between TV and
Jungshik Janga13da0d2014-06-30 16:26:06 +090068 // AVR as audio receiver.
69 @ServiceThreadOnly
70 private boolean mArcEstablished = false;
71
Jungshik Jang339227d2014-08-25 15:37:20 +090072 // Whether ARC feature is enabled or not. The default value is true.
73 // TODO: once adding system setting for it, read the value to it.
74 private boolean mArcFeatureEnabled = true;
Jungshik Jang79c58a42014-06-16 16:45:36 +090075
Jungshik Jang377dcbd2014-07-15 15:49:02 +090076 // Whether System audio mode is activated or not.
77 // This becomes true only when all system audio sequences are finished.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +090078 @GuardedBy("mLock")
Jungshik Jang377dcbd2014-07-15 15:49:02 +090079 private boolean mSystemAudioActivated = false;
Jungshik Jang79c58a42014-06-16 16:45:36 +090080
Jinsuk Kim83335712014-06-24 07:57:00 +090081 // The previous port id (input) before switching to the new one. This is remembered in order to
82 // be able to switch to it upon receiving <Inactive Source> from currently active source.
83 // This remains valid only when the active source was switched via one touch play operation
84 // (either by TV or source device). Manual port switching invalidates this value to
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090085 // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything.
Jinsuk Kim83335712014-06-24 07:57:00 +090086 @GuardedBy("mLock")
87 private int mPrevPortId;
88
Jungshik Jang8fa36b12014-06-25 15:51:36 +090089 @GuardedBy("mLock")
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090090 private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME;
Jungshik Jang8fa36b12014-06-25 15:51:36 +090091
92 @GuardedBy("mLock")
93 private boolean mSystemAudioMute = false;
94
Jungshik Jangfa8e90d2014-06-23 14:40:32 +090095 // Copy of mDeviceInfos to guarantee thread-safety.
96 @GuardedBy("mLock")
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090097 private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +090098 // All external cec input(source) devices. Does not include system audio device.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +090099 @GuardedBy("mLock")
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900100 private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList();
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900101
Jungshik Jang79c58a42014-06-16 16:45:36 +0900102 // Map-like container of all cec devices including local ones.
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900103 // device id is used as key of container.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900104 // This is not thread-safe. For external purpose use mSafeDeviceInfos.
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900105 private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900106
Jinsuk Kim160a6e52014-07-02 06:16:36 +0900107 // If true, TV going to standby mode puts other devices also to standby.
108 private boolean mAutoDeviceOff;
109
Jinsuk Kim544b62b2014-07-14 14:01:23 +0900110 // If true, TV wakes itself up when receiving <Text/Image View On>.
111 private boolean mAutoWakeup;
112
Yuncheol Heo25c20292014-07-31 17:59:39 +0900113 private final HdmiCecStandbyModeHandler mStandbyHandler;
114
Jinsuk Kimd5307192014-08-22 09:07:40 +0900115 // If true, do not do routing control/send active source for internal source.
116 // Set to true when the device was woken up by <Text/Image View On>.
117 private boolean mSkipRoutingControl;
118
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +0900119 // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
120 // other CEC devices since they might not have logical address.
121 private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>();
122
Jungshik Jang473119f2014-08-27 16:43:22 +0900123 private final HdmiLogger mSafeLogger = new HdmiLogger(TAG);
124
Jungshik Jang3ee65722014-06-03 16:22:30 +0900125 HdmiCecLocalDeviceTv(HdmiControlService service) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900126 super(service, HdmiDeviceInfo.DEVICE_TV);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900127 mPrevPortId = Constants.INVALID_PORT_ID;
Jinsuk Kim544b62b2014-07-14 14:01:23 +0900128 mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
129 true);
130 mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true);
Yuncheol Heo25c20292014-07-31 17:59:39 +0900131 mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
Jungshik Jang8b308d92014-05-29 21:52:28 +0900132 }
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900133
Jungshik Jang8b308d92014-05-29 21:52:28 +0900134 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900135 @ServiceThreadOnly
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900136 protected void onAddressAllocated(int logicalAddress, int reason) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900137 assertRunOnServiceThread();
Jungshik Jang3ee65722014-06-03 16:22:30 +0900138 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
139 mAddress, mService.getPhysicalAddress(), mDeviceType));
140 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
141 mAddress, mService.getVendorId()));
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +0900142 mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too.
Jinsuk Kimd5307192014-08-22 09:07:40 +0900143 mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE);
Yuncheol Heofc44e4e2014-08-04 19:41:09 +0900144 launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC &&
145 reason != HdmiControlService.INITIATED_BY_BOOT_UP);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900146 launchDeviceDiscovery();
Jungshik Jang6f34f5a2014-07-08 21:17:29 +0900147 }
148
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900149 @Override
150 @ServiceThreadOnly
151 protected int getPreferredAddress() {
152 assertRunOnServiceThread();
153 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
154 Constants.ADDR_UNREGISTERED);
155 }
156
157 @Override
158 @ServiceThreadOnly
159 protected void setPreferredAddress(int addr) {
160 assertRunOnServiceThread();
Jinsuk Kim544b62b2014-07-14 14:01:23 +0900161 SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV, String.valueOf(addr));
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900162 }
163
Yuncheol Heo25c20292014-07-31 17:59:39 +0900164 @Override
165 @ServiceThreadOnly
166 boolean dispatchMessage(HdmiCecMessage message) {
167 assertRunOnServiceThread();
168 if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) {
169 return true;
170 }
171 return super.onMessage(message);
172 }
173
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900174 /**
175 * Performs the action 'device select', or 'one touch play' initiated by TV.
176 *
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900177 * @param id id of HDMI device to select
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900178 * @param callback callback object to report the result with
179 */
Jungshik Janga5b74142014-06-23 18:03:10 +0900180 @ServiceThreadOnly
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900181 void deviceSelect(int id, IHdmiControlCallback callback) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900182 assertRunOnServiceThread();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900183 HdmiDeviceInfo targetDevice = mDeviceInfos.get(id);
184 if (targetDevice == null) {
185 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
186 return;
187 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900188 int targetAddress = targetDevice.getLogicalAddress();
Jinsuk Kim58500f42014-08-05 12:48:35 +0900189 ActiveSource active = getActiveSource();
190 if (active.isValid() && targetAddress == active.logicalAddress) {
191 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
192 return;
193 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900194 if (targetAddress == Constants.ADDR_INTERNAL) {
Jinsuk Kim5ad57162014-07-21 13:31:45 +0900195 handleSelectInternalSource();
196 // Switching to internal source is always successful even when CEC control is disabled.
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900197 setActiveSource(targetAddress, mService.getPhysicalAddress());
Jinsuk Kim7e742062014-07-30 13:19:13 +0900198 setActivePath(mService.getPhysicalAddress());
Jinsuk Kim5ad57162014-07-21 13:31:45 +0900199 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
Jinsuk Kim83335712014-06-24 07:57:00 +0900200 return;
201 }
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900202 if (!mService.isControlEnabled()) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900203 setActiveSource(targetDevice);
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900204 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
205 return;
206 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900207 removeAction(DeviceSelectAction.class);
208 addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900209 }
210
Jungshik Janga5b74142014-06-23 18:03:10 +0900211 @ServiceThreadOnly
Jinsuk Kim5ad57162014-07-21 13:31:45 +0900212 private void handleSelectInternalSource() {
Jinsuk Kima062a932014-06-18 10:00:39 +0900213 assertRunOnServiceThread();
Jinsuk Kim83335712014-06-24 07:57:00 +0900214 // Seq #18
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900215 if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900216 updateActiveSource(mAddress, mService.getPhysicalAddress());
Jinsuk Kimd5307192014-08-22 09:07:40 +0900217 if (mSkipRoutingControl) {
218 mSkipRoutingControl = false;
219 return;
220 }
Jinsuk Kim83335712014-06-24 07:57:00 +0900221 HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
222 mAddress, mService.getPhysicalAddress());
223 mService.sendCecCommand(activeSource);
224 }
225 }
226
227 @ServiceThreadOnly
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900228 void updateActiveSource(int logicalAddress, int physicalAddress) {
229 assertRunOnServiceThread();
230 updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress));
231 }
232
233 @ServiceThreadOnly
234 void updateActiveSource(ActiveSource newActive) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900235 assertRunOnServiceThread();
236 // Seq #14
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900237 if (mActiveSource.equals(newActive)) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900238 return;
239 }
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900240 setActiveSource(newActive);
241 int logicalAddress = newActive.logicalAddress;
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900242 if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) {
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900243 if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900244 setPrevPortId(getActivePortId());
245 }
246 // TODO: Show the OSD banner related to the new active source device.
247 } else {
248 // TODO: If displayed, remove the OSD banner related to the previous
249 // active source device.
250 }
251 }
252
Jinsuk Kim2b152012014-07-25 08:22:26 +0900253 int getPortId(int physicalAddress) {
254 return mService.pathToPortId(physicalAddress);
255 }
256
Jinsuk Kim83335712014-06-24 07:57:00 +0900257 /**
258 * Returns the previous port id kept to handle input switching on <Inactive Source>.
259 */
260 int getPrevPortId() {
261 synchronized (mLock) {
262 return mPrevPortId;
263 }
264 }
265
266 /**
267 * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
268 * taken for <Inactive Source>.
269 */
270 void setPrevPortId(int portId) {
271 synchronized (mLock) {
272 mPrevPortId = portId;
273 }
274 }
275
276 @ServiceThreadOnly
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900277 void updateActiveInput(int path, boolean notifyInputChange) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900278 assertRunOnServiceThread();
279 // Seq #15
Jinsuk Kim43c23e22014-07-29 13:59:14 +0900280 if (path == getActivePath()) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900281 return;
282 }
Jinsuk Kim7c3a9562014-08-01 11:07:42 +0900283 setPrevPortId(getActivePortId());
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900284 setActivePath(path);
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900285 // TODO: Handle PAP/PIP case.
286 // Show OSD port change banner
287 if (notifyInputChange) {
288 ActiveSource activeSource = getActiveSource();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900289 HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress);
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900290 if (info == null) {
Jinsuk Kim4b4b9402014-09-02 10:30:10 +0900291 info = new HdmiDeviceInfo(Constants.ADDR_INVALID, path, getActivePortId(),
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900292 HdmiDeviceInfo.DEVICE_RESERVED, 0, null);
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900293 }
294 mService.invokeInputChangeListener(info);
295 }
Jinsuk Kim83335712014-06-24 07:57:00 +0900296 }
297
298 @ServiceThreadOnly
299 void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
300 assertRunOnServiceThread();
301 // Seq #20
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900302 if (!mService.isValidPortId(portId)) {
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900303 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
Jinsuk Kima062a932014-06-18 10:00:39 +0900304 return;
305 }
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900306 if (portId == getActivePortId()) {
307 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
308 return;
309 }
Jinsuk Kim43c23e22014-07-29 13:59:14 +0900310 mActiveSource.invalidate();
Jinsuk Kim09ffc842014-07-11 17:04:32 +0900311 if (!mService.isControlEnabled()) {
312 setActivePortId(portId);
313 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
314 return;
315 }
Jinsuk Kim1f8d1c52014-08-12 15:39:53 +0900316 int oldPath = getActivePortId() != Constants.INVALID_PORT_ID
317 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress();
Jinsuk Kimd5307192014-08-22 09:07:40 +0900318 setActivePath(oldPath);
319 if (mSkipRoutingControl) {
320 mSkipRoutingControl = false;
321 return;
322 }
Jinsuk Kima062a932014-06-18 10:00:39 +0900323 int newPath = mService.portIdToPath(portId);
324 HdmiCecMessage routingChange =
325 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
326 mService.sendCecCommand(routingChange);
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900327 removeAction(RoutingControlAction.class);
Jinsuk Kim26269582014-08-21 14:31:40 +0900328 addAndStartAction(new RoutingControlAction(this, newPath, true, callback));
Jinsuk Kima062a932014-06-18 10:00:39 +0900329 }
330
Jungshik Jangf67113f2014-08-22 16:27:19 +0900331 @ServiceThreadOnly
Jinsuk Kimcae66272014-07-04 13:26:44 +0900332 int getPowerStatus() {
Jungshik Jangf67113f2014-08-22 16:27:19 +0900333 assertRunOnServiceThread();
Jinsuk Kimcae66272014-07-04 13:26:44 +0900334 return mService.getPowerStatus();
335 }
336
Jinsuk Kima062a932014-06-18 10:00:39 +0900337 /**
338 * Sends key to a target CEC device.
339 *
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900340 * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900341 * @param isPressed true if this is key press event
Jinsuk Kima062a932014-06-18 10:00:39 +0900342 */
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900343 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900344 @ServiceThreadOnly
Jinsuk Kimc068bb52014-07-07 16:59:20 +0900345 protected void sendKeyEvent(int keyCode, boolean isPressed) {
Jinsuk Kima062a932014-06-18 10:00:39 +0900346 assertRunOnServiceThread();
Dongil Seob64c2ba2014-08-26 19:41:39 +0900347 if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) {
348 Slog.w(TAG, "Unsupported key: " + keyCode);
349 return;
350 }
Jinsuk Kima062a932014-06-18 10:00:39 +0900351 List<SendKeyAction> action = getActions(SendKeyAction.class);
352 if (!action.isEmpty()) {
353 action.get(0).processKeyEvent(keyCode, isPressed);
354 } else {
Jinsuk Kim1f8d1c52014-08-12 15:39:53 +0900355 if (isPressed && getActiveSource().isValid()) {
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900356 int logicalAddress = getActiveSource().logicalAddress;
357 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode));
Jinsuk Kima062a932014-06-18 10:00:39 +0900358 } else {
Jinsuk Kim1f8d1c52014-08-12 15:39:53 +0900359 Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed);
Jinsuk Kima062a932014-06-18 10:00:39 +0900360 }
361 }
362 }
363
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900364 private static void invokeCallback(IHdmiControlCallback callback, int result) {
Jinsuk Kim4893c7e2014-06-19 14:13:22 +0900365 if (callback == null) {
366 return;
367 }
Jinsuk Kima6ce7702014-05-11 06:54:49 +0900368 try {
369 callback.onComplete(result);
370 } catch (RemoteException e) {
371 Slog.e(TAG, "Invoking callback failed:" + e);
372 }
373 }
374
Jungshik Jang092b4452014-06-11 15:19:17 +0900375 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900376 @ServiceThreadOnly
Jinsuk Kim83335712014-06-24 07:57:00 +0900377 protected boolean handleActiveSource(HdmiCecMessage message) {
378 assertRunOnServiceThread();
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900379 int logicalAddress = message.getSource();
380 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900381 if (getCecDeviceInfo(logicalAddress) == null) {
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900382 handleNewDeviceAtTheTailOfActivePath(physicalAddress);
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900383 } else {
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900384 ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress);
385 ActiveSourceHandler.create(this, null).process(activeSource);
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900386 }
Jinsuk Kim83335712014-06-24 07:57:00 +0900387 return true;
388 }
389
390 @Override
391 @ServiceThreadOnly
392 protected boolean handleInactiveSource(HdmiCecMessage message) {
393 assertRunOnServiceThread();
394 // Seq #10
395
396 // Ignore <Inactive Source> from non-active source device.
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900397 if (getActiveSource().logicalAddress != message.getSource()) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900398 return true;
399 }
Jinsuk Kim4d43d932014-07-03 16:43:58 +0900400 if (isProhibitMode()) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900401 return true;
402 }
403 int portId = getPrevPortId();
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900404 if (portId != Constants.INVALID_PORT_ID) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900405 // TODO: Do this only if TV is not showing multiview like PIP/PAP.
406
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900407 HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource());
Jinsuk Kim83335712014-06-24 07:57:00 +0900408 if (inactiveSource == null) {
409 return true;
410 }
411 if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
412 return true;
413 }
414 // TODO: Switch the TV freeze mode off
415
Jinsuk Kim83335712014-06-24 07:57:00 +0900416 doManualPortSwitching(portId, null);
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900417 setPrevPortId(Constants.INVALID_PORT_ID);
Jinsuk Kim83335712014-06-24 07:57:00 +0900418 }
419 return true;
420 }
421
422 @Override
423 @ServiceThreadOnly
424 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
425 assertRunOnServiceThread();
426 // Seq #19
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900427 if (mAddress == getActiveSource().logicalAddress) {
Jinsuk Kim83335712014-06-24 07:57:00 +0900428 mService.sendCecCommand(
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900429 HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath()));
Jinsuk Kim83335712014-06-24 07:57:00 +0900430 }
431 return true;
432 }
433
434 @Override
435 @ServiceThreadOnly
Jungshik Jang092b4452014-06-11 15:19:17 +0900436 protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900437 assertRunOnServiceThread();
Jungshik Jangf67113f2014-08-22 16:27:19 +0900438 if (!broadcastMenuLanguage(mService.getLanguage())) {
Jungshik Jang092b4452014-06-11 15:19:17 +0900439 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
440 }
441 return true;
442 }
443
Terry Heo1ca0a432014-08-18 10:30:32 +0900444 @ServiceThreadOnly
445 boolean broadcastMenuLanguage(String language) {
446 assertRunOnServiceThread();
447 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
448 mAddress, language);
449 if (command != null) {
450 mService.sendCecCommand(command);
451 return true;
452 }
453 return false;
454 }
455
Jungshik Jang60cffce2014-06-12 18:03:04 +0900456 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900457 @ServiceThreadOnly
Jungshik Jang60cffce2014-06-12 18:03:04 +0900458 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900459 assertRunOnServiceThread();
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +0900460 int path = HdmiUtils.twoBytesToInt(message.getParams());
461 int address = message.getSource();
Jinsuk Kimbcfa0672014-08-11 11:56:58 +0900462 int type = message.getParams()[2];
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +0900463
Jinsuk Kimbcfa0672014-08-11 11:56:58 +0900464 if (updateCecSwitchInfo(address, type, path)) return true;
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +0900465
Jungshik Jang092b4452014-06-11 15:19:17 +0900466 // Ignore if [Device Discovery Action] is going on.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900467 if (hasAction(DeviceDiscoveryAction.class)) {
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +0900468 Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
Jungshik Jang092b4452014-06-11 15:19:17 +0900469 return true;
470 }
471
Jinsuk Kim4b4b9402014-09-02 10:30:10 +0900472 if (!isInDeviceList(address, path)) {
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900473 handleNewDeviceAtTheTailOfActivePath(path);
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900474 }
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900475 startNewDeviceAction(ActiveSource.of(address, path));
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900476 return true;
477 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900478
Jinsuk Kimbcfa0672014-08-11 11:56:58 +0900479 boolean updateCecSwitchInfo(int address, int type, int path) {
480 if (address == Constants.ADDR_UNREGISTERED
481 && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) {
482 mCecSwitches.add(path);
483 updateSafeDeviceInfoList();
484 return true; // Pure switch does not need further processing. Return here.
485 }
486 if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
487 mCecSwitches.add(path);
488 }
489 return false;
490 }
491
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900492 void startNewDeviceAction(ActiveSource activeSource) {
Jungshik Jang97affee2014-07-11 17:04:43 +0900493 for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
494 // If there is new device action which has the same logical address and path
495 // ignore new request.
496 // NewDeviceAction is created whenever it receives <Report Physical Address>.
497 // And there is a chance starting NewDeviceAction for the same source.
498 // Usually, new device sends <Report Physical Address> when it's plugged
499 // in. However, TV can detect a new device from HotPlugDetectionAction,
500 // which sends <Give Physical Address> to the source for newly detected
501 // device.
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900502 if (action.isActionOf(activeSource)) {
Jungshik Jang97affee2014-07-11 17:04:43 +0900503 return;
504 }
505 }
506
Jinsuk Kim72b7d732014-07-24 09:15:35 +0900507 addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress,
508 activeSource.physicalAddress));
Jungshik Jang97affee2014-07-11 17:04:43 +0900509 }
510
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900511 private void handleNewDeviceAtTheTailOfActivePath(int path) {
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900512 // Seq #22
513 if (isTailOfActivePath(path, getActivePath())) {
514 removeAction(RoutingControlAction.class);
515 int newPath = mService.portIdToPath(getActivePortId());
Jinsuk Kim7c3a9562014-08-01 11:07:42 +0900516 setActivePath(newPath);
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900517 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
518 mAddress, getActivePath(), newPath));
Jinsuk Kim7c3a9562014-08-01 11:07:42 +0900519 addAndStartAction(new RoutingControlAction(this, newPath, false, null));
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900520 }
521 }
522
523 /**
524 * Whether the given path is located in the tail of current active path.
525 *
526 * @param path to be tested
527 * @param activePath current active path
528 * @return true if the given path is located in the tail of current active path; otherwise,
529 * false
530 */
531 static boolean isTailOfActivePath(int path, int activePath) {
532 // If active routing path is internal source, return false.
533 if (activePath == 0) {
534 return false;
535 }
536 for (int i = 12; i >= 0; i -= 4) {
537 int curActivePath = (activePath >> i) & 0xF;
538 if (curActivePath == 0) {
539 return true;
540 } else {
541 int curPath = (path >> i) & 0xF;
542 if (curPath != curActivePath) {
543 return false;
544 }
545 }
546 }
547 return false;
548 }
549
550 @Override
551 @ServiceThreadOnly
552 protected boolean handleRoutingChange(HdmiCecMessage message) {
553 assertRunOnServiceThread();
554 // Seq #21
555 byte[] params = message.getParams();
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900556 int currentPath = HdmiUtils.twoBytesToInt(params);
557 if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) {
Jinsuk Kim43c23e22014-07-29 13:59:14 +0900558 mActiveSource.invalidate();
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900559 removeAction(RoutingControlAction.class);
Jinsuk Kim43c23e22014-07-29 13:59:14 +0900560 int newPath = HdmiUtils.twoBytesToInt(params, 2);
Jinsuk Kim04fd2802014-07-03 14:04:02 +0900561 addAndStartAction(new RoutingControlAction(this, newPath, true, null));
Jinsuk Kim92b77cf2014-06-27 16:39:26 +0900562 }
Jungshik Jang092b4452014-06-11 15:19:17 +0900563 return true;
564 }
Jinsuk Kim0a3316b2014-06-14 09:33:55 +0900565
566 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900567 @ServiceThreadOnly
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900568 protected boolean handleReportAudioStatus(HdmiCecMessage message) {
569 assertRunOnServiceThread();
570
571 byte params[] = message.getParams();
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900572 int mute = params[0] & 0x80;
573 int volume = params[0] & 0x7F;
574 setAudioStatus(mute == 0x80, volume);
575 return true;
576 }
577
Yuncheol Heo38db6292014-07-01 14:15:14 +0900578 @Override
579 @ServiceThreadOnly
580 protected boolean handleTextViewOn(HdmiCecMessage message) {
581 assertRunOnServiceThread();
Jinsuk Kim544b62b2014-07-14 14:01:23 +0900582 if (mService.isPowerStandbyOrTransient() && mAutoWakeup) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900583 mService.wakeUp();
584 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900585 return true;
586 }
587
588 @Override
589 @ServiceThreadOnly
590 protected boolean handleImageViewOn(HdmiCecMessage message) {
591 assertRunOnServiceThread();
592 // Currently, it's the same as <Text View On>.
593 return handleTextViewOn(message);
594 }
595
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900596 @Override
597 @ServiceThreadOnly
598 protected boolean handleSetOsdName(HdmiCecMessage message) {
599 int source = message.getSource();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900600 HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900601 // If the device is not in device list, ignore it.
602 if (deviceInfo == null) {
603 Slog.e(TAG, "No source device info for <Set Osd Name>." + message);
604 return true;
605 }
606 String osdName = null;
607 try {
608 osdName = new String(message.getParams(), "US-ASCII");
609 } catch (UnsupportedEncodingException e) {
610 Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
611 return true;
612 }
613
614 if (deviceInfo.getDisplayName().equals(osdName)) {
615 Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
616 return true;
617 }
618
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900619 addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
Jinsuk Kim2b152012014-07-25 08:22:26 +0900620 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
621 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
Jungshik Jang8f2ed352014-07-07 15:02:47 +0900622 return true;
623 }
624
Jungshik Janga5b74142014-06-23 18:03:10 +0900625 @ServiceThreadOnly
Jungshik Jang60cffce2014-06-12 18:03:04 +0900626 private void launchDeviceDiscovery() {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900627 assertRunOnServiceThread();
628 clearDeviceInfoList();
629 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
Jungshik Jang60cffce2014-06-12 18:03:04 +0900630 new DeviceDiscoveryCallback() {
631 @Override
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900632 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
633 for (HdmiDeviceInfo info : deviceInfos) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900634 addCecDevice(info);
Jungshik Jang60cffce2014-06-12 18:03:04 +0900635 }
636
637 // Since we removed all devices when it's start and
638 // device discovery action does not poll local devices,
639 // we should put device info of local device manually here
640 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
Jungshik Jang79c58a42014-06-16 16:45:36 +0900641 addCecDevice(device.getDeviceInfo());
Jungshik Jang60cffce2014-06-12 18:03:04 +0900642 }
643
Jungshik Jang79c58a42014-06-16 16:45:36 +0900644 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this));
Jungshik Jang410ca9c2014-08-07 18:04:14 +0900645 addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this));
Jungshik Jang187d0172014-06-17 17:48:42 +0900646
647 // If there is AVR, initiate System Audio Auto initiation action,
648 // which turns on and off system audio according to last system
649 // audio setting.
Jungshik Jang339227d2014-08-25 15:37:20 +0900650 HdmiDeviceInfo avr = getAvrDeviceInfo();
651 if (avr != null) {
652 onNewAvrAdded(avr);
Jungshik Jang187d0172014-06-17 17:48:42 +0900653 }
Jungshik Jang60cffce2014-06-12 18:03:04 +0900654 }
655 });
Jungshik Jang79c58a42014-06-16 16:45:36 +0900656 addAndStartAction(action);
657 }
658
Jungshik Jang339227d2014-08-25 15:37:20 +0900659 @ServiceThreadOnly
660 void onNewAvrAdded(HdmiDeviceInfo avr) {
661 assertRunOnServiceThread();
662 if (getSystemAudioModeSetting()) {
663 addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
664 }
665 if (isArcFeatureEnabled()) {
666 startArcAction(true);
667 }
668 }
669
Jungshik Jang79c58a42014-06-16 16:45:36 +0900670 // Clear all device info.
Jungshik Janga5b74142014-06-23 18:03:10 +0900671 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900672 private void clearDeviceInfoList() {
673 assertRunOnServiceThread();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900674 for (HdmiDeviceInfo info : mSafeExternalInputs) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +0900675 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
Jinsuk Kim49b47bb2014-07-22 10:40:35 +0900676 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900677 mDeviceInfos.clear();
Jungshik Jangfa8e90d2014-06-23 14:40:32 +0900678 updateSafeDeviceInfoList();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900679 }
680
Jungshik Janga5b74142014-06-23 18:03:10 +0900681 @ServiceThreadOnly
Yuncheol Heoc516d652014-07-11 18:23:24 +0900682 // Seq #32
Jungshik Jangea67c182014-06-19 22:19:20 +0900683 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
684 assertRunOnServiceThread();
Yuncheol Heoc516d652014-07-11 18:23:24 +0900685 if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
686 setSystemAudioMode(false, true);
687 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
688 return;
689 }
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900690 HdmiDeviceInfo avr = getAvrDeviceInfo();
Jungshik Jangea67c182014-06-19 22:19:20 +0900691 if (avr == null) {
Yuncheol Heoc516d652014-07-11 18:23:24 +0900692 setSystemAudioMode(false, true);
Yuncheol Heod05f67f2014-07-11 16:06:40 +0900693 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
Jungshik Jangea67c182014-06-19 22:19:20 +0900694 return;
695 }
696
697 addAndStartAction(
698 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback));
Jungshik Jangea67c182014-06-19 22:19:20 +0900699 }
700
Jungshik Jangca5be9a2014-07-01 18:01:26 +0900701 // # Seq 25
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900702 void setSystemAudioMode(boolean on, boolean updateSetting) {
Jungshik Jang473119f2014-08-27 16:43:22 +0900703 mSafeLogger.debug(String.format("System Audio Mode change[old:%b new:%b]",
704 mSystemAudioActivated, on));
705
Jungshik Jang377dcbd2014-07-15 15:49:02 +0900706 if (updateSetting) {
707 mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on);
708 }
709 updateAudioManagerForSystemAudio(on);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900710 synchronized (mLock) {
Jungshik Jang377dcbd2014-07-15 15:49:02 +0900711 if (mSystemAudioActivated != on) {
712 mSystemAudioActivated = on;
Jungshik Jangea67c182014-06-19 22:19:20 +0900713 mService.announceSystemAudioModeChange(on);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900714 }
715 }
716 }
717
Jungshik Jang377dcbd2014-07-15 15:49:02 +0900718 private void updateAudioManagerForSystemAudio(boolean on) {
Jungshik Jang377dcbd2014-07-15 15:49:02 +0900719 mService.getAudioManager().setHdmiSystemAudioSupported(on);
720 }
721
722 boolean isSystemAudioActivated() {
723 if (getAvrDeviceInfo() == null) {
724 return false;
Jungshik Jang79c58a42014-06-16 16:45:36 +0900725 }
Jungshik Jang377dcbd2014-07-15 15:49:02 +0900726 synchronized (mLock) {
727 return mSystemAudioActivated;
728 }
729 }
730
731 boolean getSystemAudioModeSetting() {
732 return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900733 }
734
735 /**
736 * Change ARC status into the given {@code enabled} status.
737 *
738 * @return {@code true} if ARC was in "Enabled" status
739 */
Jungshik Janga13da0d2014-06-30 16:26:06 +0900740 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900741 boolean setArcStatus(boolean enabled) {
Jungshik Janga13da0d2014-06-30 16:26:06 +0900742 assertRunOnServiceThread();
743 boolean oldStatus = mArcEstablished;
744 // 1. Enable/disable ARC circuit.
745 mService.setAudioReturnChannel(enabled);
746 // 2. Notify arc status to audio service.
747 notifyArcStatusToAudioService(enabled);
748 // 3. Update arc status;
749 mArcEstablished = enabled;
750 return oldStatus;
Jungshik Jang79c58a42014-06-16 16:45:36 +0900751 }
752
Jungshik Janga858d222014-06-23 17:17:47 +0900753 private void notifyArcStatusToAudioService(boolean enabled) {
754 // Note that we don't set any name to ARC.
755 mService.getAudioManager().setWiredDeviceConnectionState(
756 AudioSystem.DEVICE_OUT_HDMI_ARC,
757 enabled ? 1 : 0, "");
758 }
759
Jungshik Jang79c58a42014-06-16 16:45:36 +0900760 /**
761 * Returns whether ARC is enabled or not.
762 */
Jungshik Janga13da0d2014-06-30 16:26:06 +0900763 @ServiceThreadOnly
764 boolean isArcEstabilished() {
765 assertRunOnServiceThread();
766 return mArcFeatureEnabled && mArcEstablished;
767 }
768
769 @ServiceThreadOnly
770 void changeArcFeatureEnabled(boolean enabled) {
771 assertRunOnServiceThread();
772
773 if (mArcFeatureEnabled != enabled) {
Jungshik Jang339227d2014-08-25 15:37:20 +0900774 mArcFeatureEnabled = enabled;
Jungshik Janga13da0d2014-06-30 16:26:06 +0900775 if (enabled) {
776 if (!mArcEstablished) {
777 startArcAction(true);
778 }
779 } else {
780 if (mArcEstablished) {
781 startArcAction(false);
782 }
783 }
Jungshik Janga13da0d2014-06-30 16:26:06 +0900784 }
785 }
786
787 @ServiceThreadOnly
788 boolean isArcFeatureEnabled() {
789 assertRunOnServiceThread();
790 return mArcFeatureEnabled;
791 }
792
793 @ServiceThreadOnly
Jungshik Jang339227d2014-08-25 15:37:20 +0900794 void startArcAction(boolean enabled) {
Jungshik Janga13da0d2014-06-30 16:26:06 +0900795 assertRunOnServiceThread();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900796 HdmiDeviceInfo info = getAvrDeviceInfo();
Jungshik Janga13da0d2014-06-30 16:26:06 +0900797 if (info == null) {
Jungshik Jang339227d2014-08-25 15:37:20 +0900798 Slog.w(TAG, "Failed to start arc action; No AVR device.");
Jungshik Janga13da0d2014-06-30 16:26:06 +0900799 return;
800 }
Jungshik Jang339227d2014-08-25 15:37:20 +0900801 if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
802 Slog.w(TAG, "Failed to start arc action; ARC configuration check failed.");
803 if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
804 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
805 }
Jungshik Janga13da0d2014-06-30 16:26:06 +0900806 return;
807 }
808
809 // Terminate opposite action and start action if not exist.
810 if (enabled) {
811 removeAction(RequestArcTerminationAction.class);
812 if (!hasAction(RequestArcInitiationAction.class)) {
813 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
814 }
815 } else {
816 removeAction(RequestArcInitiationAction.class);
817 if (!hasAction(RequestArcTerminationAction.class)) {
818 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
819 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900820 }
821 }
822
Jungshik Jang339227d2014-08-25 15:37:20 +0900823 private boolean isDirectConnectAddress(int physicalAddress) {
824 return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress;
825 }
826
Jungshik Jang79c58a42014-06-16 16:45:36 +0900827 void setAudioStatus(boolean mute, int volume) {
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900828 synchronized (mLock) {
829 mSystemAudioMute = mute;
830 mSystemAudioVolume = volume;
Jungshik Jangb69aafb2014-07-11 16:29:06 +0900831 int maxVolume = mService.getAudioManager().getStreamMaxVolume(
832 AudioManager.STREAM_MUSIC);
833 mService.setAudioStatus(mute,
834 VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900835 }
836 }
837
838 @ServiceThreadOnly
839 void changeVolume(int curVolume, int delta, int maxVolume) {
840 assertRunOnServiceThread();
Jungshik Jang377dcbd2014-07-15 15:49:02 +0900841 if (delta == 0 || !isSystemAudioActivated()) {
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900842 return;
843 }
844
845 int targetVolume = curVolume + delta;
846 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
847 synchronized (mLock) {
848 // If new volume is the same as current system audio volume, just ignore it.
849 // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
850 if (cecVolume == mSystemAudioVolume) {
851 // Update tv volume with system volume value.
852 mService.setAudioStatus(false,
853 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
854 return;
855 }
856 }
857
858 // Remove existing volume action.
859 removeAction(VolumeControlAction.class);
860
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900861 HdmiDeviceInfo avr = getAvrDeviceInfo();
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900862 addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(),
863 cecVolume, delta > 0));
864 }
865
866 @ServiceThreadOnly
867 void changeMute(boolean mute) {
868 assertRunOnServiceThread();
Jungshik Jang377dcbd2014-07-15 15:49:02 +0900869 if (!isSystemAudioActivated()) {
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900870 return;
871 }
872
873 // Remove existing volume action.
874 removeAction(VolumeControlAction.class);
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900875 HdmiDeviceInfo avr = getAvrDeviceInfo();
Jungshik Jang8fa36b12014-06-25 15:51:36 +0900876 addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute));
877 }
878
Jungshik Jang79c58a42014-06-16 16:45:36 +0900879 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900880 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900881 protected boolean handleInitiateArc(HdmiCecMessage message) {
882 assertRunOnServiceThread();
Jungshik Jang339227d2014-08-25 15:37:20 +0900883
884 if (!canStartArcUpdateAction(message.getSource(), true)) {
885 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
886 if (!isConnectedToArcPort(message.getSource())) {
887 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
888 }
889 return true;
890 }
891
Jungshik Jang79c58a42014-06-16 16:45:36 +0900892 // In case where <Initiate Arc> is started by <Request ARC Initiation>
893 // need to clean up RequestArcInitiationAction.
894 removeAction(RequestArcInitiationAction.class);
895 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
896 message.getSource(), true);
897 addAndStartAction(action);
898 return true;
899 }
900
Jungshik Jang339227d2014-08-25 15:37:20 +0900901 private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) {
902 HdmiDeviceInfo avr = getAvrDeviceInfo();
903 if (avr != null
904 && (avrAddress == avr.getLogicalAddress())
905 && isConnectedToArcPort(avr.getPhysicalAddress())
906 && isDirectConnectAddress(avr.getPhysicalAddress())) {
907 if (shouldCheckArcFeatureEnabled) {
908 return isArcFeatureEnabled();
909 } else {
910 return true;
911 }
912 } else {
913 return false;
914 }
915 }
916
Jungshik Jang79c58a42014-06-16 16:45:36 +0900917 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900918 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900919 protected boolean handleTerminateArc(HdmiCecMessage message) {
920 assertRunOnServiceThread();
Jungshik Jang339227d2014-08-25 15:37:20 +0900921 // In cast of termination, do not check ARC configuration in that AVR device
922 // might be removed already.
923
Jungshik Jang79c58a42014-06-16 16:45:36 +0900924 // In case where <Terminate Arc> is started by <Request ARC Termination>
925 // need to clean up RequestArcInitiationAction.
Jungshik Jang79c58a42014-06-16 16:45:36 +0900926 removeAction(RequestArcTerminationAction.class);
927 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
928 message.getSource(), false);
929 addAndStartAction(action);
930 return true;
931 }
932
933 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900934 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900935 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
936 assertRunOnServiceThread();
937 if (!isMessageForSystemAudio(message)) {
Jungshik Jang473119f2014-08-27 16:43:22 +0900938 mSafeLogger.warning("Invalid <Set System Audio Mode> message:" + message);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900939 return false;
940 }
941 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this,
Jungshik Jang7f0a1c52014-06-23 16:00:07 +0900942 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900943 addAndStartAction(action);
944 return true;
945 }
946
947 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900948 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900949 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
950 assertRunOnServiceThread();
951 if (!isMessageForSystemAudio(message)) {
Jungshik Jang473119f2014-08-27 16:43:22 +0900952 mSafeLogger.warning("Invalid <System Audio Mode Status> message:" + message);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900953 return false;
954 }
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +0900955 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900956 return true;
957 }
958
Jungshik Jangb6591b82014-07-23 16:10:23 +0900959 // Seq #53
960 @Override
961 @ServiceThreadOnly
962 protected boolean handleRecordTvScreen(HdmiCecMessage message) {
963 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
964 if (!actions.isEmpty()) {
965 // Assumes only one OneTouchRecordAction.
966 OneTouchRecordAction action = actions.get(0);
967 if (action.getRecorderAddress() != message.getSource()) {
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900968 announceOneTouchRecordResult(
969 HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
Jungshik Jangb6591b82014-07-23 16:10:23 +0900970 }
971 return super.handleRecordTvScreen(message);
972 }
973
974 int recorderAddress = message.getSource();
975 byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress);
976 startOneTouchRecord(recorderAddress, recordSource);
977 return true;
978 }
979
Jungshik Jange5a93372014-07-25 13:41:14 +0900980 @Override
981 protected boolean handleTimerClearedStatus(HdmiCecMessage message) {
982 byte[] params = message.getParams();
Jungshik Jange9e0f072014-08-05 17:48:59 +0900983 int timerClearedStatusData = params[0] & 0xFF;
Jungshik Jange5a93372014-07-25 13:41:14 +0900984 announceTimerRecordingResult(timerClearedStatusData);
985 return true;
986 }
987
Jungshik Jang12e5dce2014-07-24 15:27:44 +0900988 void announceOneTouchRecordResult(int result) {
989 mService.invokeOneTouchRecordResult(result);
990 }
991
992 void announceTimerRecordingResult(int result) {
993 mService.invokeTimerRecordingResult(result);
994 }
995
Jungshik Jangfaa49bc2014-08-05 16:10:21 +0900996 void announceClearTimerRecordingResult(int result) {
997 mService.invokeClearTimerRecordingResult(result);
998 }
999
Jungshik Jang79c58a42014-06-16 16:45:36 +09001000 private boolean isMessageForSystemAudio(HdmiCecMessage message) {
Jungshik Jang473119f2014-08-27 16:43:22 +09001001 return message.getSource() == Constants.ADDR_AUDIO_SYSTEM
1002 && (message.getDestination() == Constants.ADDR_TV
1003 || message.getDestination() == Constants.ADDR_BROADCAST)
1004 && getAvrDeviceInfo() != null;
Jungshik Jang79c58a42014-06-16 16:45:36 +09001005 }
1006
1007 /**
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001008 * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
Jungshik Jang79c58a42014-06-16 16:45:36 +09001009 * logical address as new device info's.
1010 *
1011 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1012 *
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001013 * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
1014 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
Jungshik Jang79c58a42014-06-16 16:45:36 +09001015 * that has the same logical address as new one has.
1016 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001017 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001018 private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001019 assertRunOnServiceThread();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001020 HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
Jungshik Jang79c58a42014-06-16 16:45:36 +09001021 if (oldDeviceInfo != null) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001022 removeDeviceInfo(deviceInfo.getId());
Jungshik Jang79c58a42014-06-16 16:45:36 +09001023 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001024 mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001025 updateSafeDeviceInfoList();
Jungshik Jang79c58a42014-06-16 16:45:36 +09001026 return oldDeviceInfo;
1027 }
1028
1029 /**
1030 * Remove a device info corresponding to the given {@code logicalAddress}.
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001031 * It returns removed {@link HdmiDeviceInfo} if exists.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001032 *
1033 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
1034 *
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001035 * @param id id of device to be removed
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001036 * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
Jungshik Jang79c58a42014-06-16 16:45:36 +09001037 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001038 @ServiceThreadOnly
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001039 private HdmiDeviceInfo removeDeviceInfo(int id) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001040 assertRunOnServiceThread();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001041 HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001042 if (deviceInfo != null) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001043 mDeviceInfos.remove(id);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001044 }
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001045 updateSafeDeviceInfoList();
Jungshik Jang79c58a42014-06-16 16:45:36 +09001046 return deviceInfo;
1047 }
1048
1049 /**
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001050 * Return a list of all {@link HdmiDeviceInfo}.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001051 *
1052 * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
Jungshik Jang339227d2014-08-25 15:37:20 +09001053 * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which
Jungshik Jang8e93c842014-08-06 15:48:33 +09001054 * does not include local device.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001055 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001056 @ServiceThreadOnly
Jinsuk Kim4b4b9402014-09-02 10:30:10 +09001057 List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001058 assertRunOnServiceThread();
Jinsuk Kim4b4b9402014-09-02 10:30:10 +09001059 if (includeLocalDevice) {
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001060 return HdmiUtils.sparseArrayToList(mDeviceInfos);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001061 } else {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001062 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
Jungshik Jang79c58a42014-06-16 16:45:36 +09001063 for (int i = 0; i < mDeviceInfos.size(); ++i) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001064 HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001065 if (!isLocalDeviceAddress(info.getLogicalAddress())) {
1066 infoList.add(info);
1067 }
1068 }
1069 return infoList;
1070 }
1071 }
1072
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001073 /**
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001074 * Return external input devices.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001075 */
Jinsuk Kimed086452014-08-18 15:01:53 +09001076 List<HdmiDeviceInfo> getSafeExternalInputsLocked() {
1077 return mSafeExternalInputs;
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001078 }
1079
Jungshik Janga5b74142014-06-23 18:03:10 +09001080 @ServiceThreadOnly
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001081 private void updateSafeDeviceInfoList() {
1082 assertRunOnServiceThread();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001083 List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
1084 List<HdmiDeviceInfo> externalInputs = getInputDevices();
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001085 synchronized (mLock) {
1086 mSafeAllDeviceInfos = copiedDevices;
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001087 mSafeExternalInputs = externalInputs;
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001088 }
1089 }
1090
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001091 /**
1092 * Return a list of external cec input (source) devices.
1093 *
1094 * <p>Note that this effectively excludes non-source devices like system audio,
1095 * secondary TV.
1096 */
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001097 private List<HdmiDeviceInfo> getInputDevices() {
1098 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001099 for (int i = 0; i < mDeviceInfos.size(); ++i) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001100 HdmiDeviceInfo info = mDeviceInfos.valueAt(i);
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001101 if (isLocalDeviceAddress(info.getLogicalAddress())) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001102 continue;
1103 }
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +09001104 if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
Jinsuk Kim9c37e1f2014-07-02 08:29:26 +09001105 infoList.add(info);
1106 }
1107 }
1108 return infoList;
1109 }
1110
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +09001111 // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch.
1112 // Returns true if the policy is set to true, and the device to check does not have
1113 // a parent CEC device (which should be the CEC-enabled switch) in the list.
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001114 private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) {
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +09001115 return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH
1116 && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches);
1117 }
1118
1119 private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) {
1120 for (int switchPath : switches) {
1121 if (isParentPath(switchPath, path)) {
1122 return true;
1123 }
1124 }
1125 return false;
1126 }
1127
1128 private static boolean isParentPath(int parentPath, int childPath) {
1129 // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
1130 // If child's last non-zero nibble is removed, the result equals to the parent.
1131 for (int i = 0; i <= 12; i += 4) {
1132 int nibble = (childPath >> i) & 0xF;
1133 if (nibble != 0) {
1134 int parentNibble = (parentPath >> i) & 0xF;
1135 return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4);
1136 }
1137 }
1138 return false;
1139 }
1140
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001141 private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
Jinsuk Kimbcfa0672014-08-11 11:56:58 +09001142 if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001143 mService.invokeDeviceEventListeners(info, status);
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +09001144 }
1145 }
1146
Jungshik Janga5b74142014-06-23 18:03:10 +09001147 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001148 private boolean isLocalDeviceAddress(int address) {
1149 assertRunOnServiceThread();
1150 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
1151 if (device.isAddressOf(address)) {
1152 return true;
1153 }
1154 }
1155 return false;
1156 }
1157
Jungshik Jange9cf1582014-06-23 17:28:58 +09001158 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001159 HdmiDeviceInfo getAvrDeviceInfo() {
Jungshik Jange9cf1582014-06-23 17:28:58 +09001160 assertRunOnServiceThread();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001161 return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
Jungshik Jange9cf1582014-06-23 17:28:58 +09001162 }
1163
Jungshik Jang79c58a42014-06-16 16:45:36 +09001164 /**
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001165 * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001166 *
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001167 * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001168 *
Jungshik Jang339227d2014-08-25 15:37:20 +09001169 * @param logicalAddress logical address of the device to be retrieved
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001170 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001171 * Returns null if no logical address matched
1172 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001173 @ServiceThreadOnly
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001174 HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001175 assertRunOnServiceThread();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001176 return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
Jungshik Jang79c58a42014-06-16 16:45:36 +09001177 }
1178
Jungshik Jange9cf1582014-06-23 17:28:58 +09001179 boolean hasSystemAudioDevice() {
1180 return getSafeAvrDeviceInfo() != null;
1181 }
1182
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001183 HdmiDeviceInfo getSafeAvrDeviceInfo() {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001184 return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001185 }
1186
1187 /**
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001188 * Thread safe version of {@link #getCecDeviceInfo(int)}.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001189 *
1190 * @param logicalAddress logical address to be retrieved
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001191 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001192 * Returns null if no logical address matched
1193 */
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001194 HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) {
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001195 synchronized (mLock) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001196 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1197 if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) {
1198 return info;
1199 }
1200 }
1201 return null;
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001202 }
1203 }
1204
Jungshik Jangfa8e90d2014-06-23 14:40:32 +09001205 /**
Jungshik Jang8f2ed352014-07-07 15:02:47 +09001206 * Called when a device is newly added or a new device is detected or
1207 * existing device is updated.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001208 *
1209 * @param info device info of a new device.
1210 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001211 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001212 final void addCecDevice(HdmiDeviceInfo info) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001213 assertRunOnServiceThread();
1214 addDeviceInfo(info);
Jinsuk Kim13c030e2014-06-20 13:25:17 +09001215 if (info.getLogicalAddress() == mAddress) {
1216 // The addition of TV device itself should not be notified.
1217 return;
1218 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001219 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001220 }
1221
1222 /**
1223 * Called when a device is removed or removal of device is detected.
1224 *
1225 * @param address a logical address of a device to be removed
1226 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001227 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +09001228 final void removeCecDevice(int address) {
1229 assertRunOnServiceThread();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001230 HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
Jungshik Jang26dc71e2014-07-04 10:53:27 +09001231
Jungshik Jang79c58a42014-06-16 16:45:36 +09001232 mCecMessageCache.flushMessagesFrom(address);
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001233 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001234 }
1235
Jungshik Jang26dc71e2014-07-04 10:53:27 +09001236 @ServiceThreadOnly
1237 void handleRemoveActiveRoutingPath(int path) {
1238 assertRunOnServiceThread();
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001239 // Seq #23
1240 if (isTailOfActivePath(path, getActivePath())) {
1241 removeAction(RoutingControlAction.class);
1242 int newPath = mService.portIdToPath(getActivePortId());
1243 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(
1244 mAddress, getActivePath(), newPath));
Jinsuk Kim4b4b9402014-09-02 10:30:10 +09001245 mActiveSource.invalidate();
1246 addAndStartAction(new RoutingControlAction(this, getActivePath(), true, null));
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001247 }
1248 }
1249
Jinsuk Kim5344cd92014-07-03 18:02:01 +09001250 /**
1251 * Launch routing control process.
1252 *
1253 * @param routingForBootup true if routing control is initiated due to One Touch Play
1254 * or TV power on
1255 */
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001256 @ServiceThreadOnly
Jinsuk Kim5344cd92014-07-03 18:02:01 +09001257 void launchRoutingControl(boolean routingForBootup) {
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001258 assertRunOnServiceThread();
1259 // Seq #24
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001260 if (getActivePortId() != Constants.INVALID_PORT_ID) {
Jinsuk Kim5344cd92014-07-03 18:02:01 +09001261 if (!routingForBootup && !isProhibitMode()) {
1262 removeAction(RoutingControlAction.class);
1263 int newPath = mService.portIdToPath(getActivePortId());
1264 setActivePath(newPath);
1265 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(mAddress,
1266 getActivePath(), newPath));
1267 addAndStartAction(new RoutingControlAction(this, getActivePortId(),
1268 routingForBootup, null));
1269 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001270 } else {
1271 int activePath = mService.getPhysicalAddress();
1272 setActivePath(activePath);
Jinsuk Kim5344cd92014-07-03 18:02:01 +09001273 if (!routingForBootup) {
1274 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress,
1275 activePath));
1276 }
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001277 }
1278 }
1279
Jungshik Jang79c58a42014-06-16 16:45:36 +09001280 /**
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001281 * Returns the {@link HdmiDeviceInfo} instance whose physical address matches
Jungshik Jang79c58a42014-06-16 16:45:36 +09001282 * the given routing path. CEC devices use routing path for its physical address to
1283 * describe the hierarchy of the devices in the network.
1284 *
1285 * @param path routing path or physical address
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001286 * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null
Jungshik Jang79c58a42014-06-16 16:45:36 +09001287 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001288 @ServiceThreadOnly
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001289 final HdmiDeviceInfo getDeviceInfoByPath(int path) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001290 assertRunOnServiceThread();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001291 for (HdmiDeviceInfo info : getDeviceInfoList(false)) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001292 if (info.getPhysicalAddress() == path) {
1293 return info;
1294 }
1295 }
1296 return null;
1297 }
1298
1299 /**
1300 * Whether a device of the specified physical address and logical address exists
1301 * in a device info list. However, both are minimal condition and it could
1302 * be different device from the original one.
1303 *
Jungshik Jang79c58a42014-06-16 16:45:36 +09001304 * @param logicalAddress logical address of a device to be searched
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001305 * @param physicalAddress physical address of a device to be searched
Jungshik Jang79c58a42014-06-16 16:45:36 +09001306 * @return true if exist; otherwise false
1307 */
Jungshik Janga5b74142014-06-23 18:03:10 +09001308 @ServiceThreadOnly
Jinsuk Kim4b4b9402014-09-02 10:30:10 +09001309 private boolean isInDeviceList(int logicalAddress, int physicalAddress) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001310 assertRunOnServiceThread();
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001311 HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress);
Jungshik Jang79c58a42014-06-16 16:45:36 +09001312 if (device == null) {
1313 return false;
1314 }
1315 return device.getPhysicalAddress() == physicalAddress;
1316 }
1317
1318 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +09001319 @ServiceThreadOnly
Jinsuk Kim92b77cf2014-06-27 16:39:26 +09001320 void onHotplug(int portId, boolean connected) {
Jungshik Jang79c58a42014-06-16 16:45:36 +09001321 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +09001322
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +09001323 if (!connected) {
1324 removeCecSwitches(portId);
1325 }
Jungshik Jang79c58a42014-06-16 16:45:36 +09001326 // Tv device will have permanent HotplugDetectionAction.
1327 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
1328 if (!hotplugActions.isEmpty()) {
1329 // Note that hotplug action is single action running on a machine.
1330 // "pollAllDevicesNow" cleans up timer and start poll action immediately.
Jungshik Jang24c23c12014-07-07 18:04:39 +09001331 // It covers seq #40, #43.
Jungshik Jang79c58a42014-06-16 16:45:36 +09001332 hotplugActions.get(0).pollAllDevicesNow();
1333 }
Jungshik Jang60cffce2014-06-12 18:03:04 +09001334 }
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001335
Jinsuk Kim4fdd0f42014-08-06 07:23:32 +09001336 private void removeCecSwitches(int portId) {
1337 Iterator<Integer> it = mCecSwitches.iterator();
1338 while (!it.hasNext()) {
1339 int path = it.next();
1340 if (pathToPortId(path) == portId) {
1341 it.remove();
1342 }
1343 }
1344 }
1345
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001346 @ServiceThreadOnly
1347 void setAutoDeviceOff(boolean enabled) {
1348 assertRunOnServiceThread();
1349 mAutoDeviceOff = enabled;
Jinsuk Kim544b62b2014-07-14 14:01:23 +09001350 }
1351
1352 @ServiceThreadOnly
1353 void setAutoWakeup(boolean enabled) {
1354 assertRunOnServiceThread();
1355 mAutoWakeup = enabled;
Jinsuk Kim160a6e52014-07-02 06:16:36 +09001356 }
Yuncheol Heo38db6292014-07-01 14:15:14 +09001357
Yuncheol Heo25c20292014-07-31 17:59:39 +09001358 @ServiceThreadOnly
1359 boolean getAutoWakeup() {
1360 assertRunOnServiceThread();
1361 return mAutoWakeup;
1362 }
1363
Yuncheol Heo38db6292014-07-01 14:15:14 +09001364 @Override
1365 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001366 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
1367 super.disableDevice(initiatedByCec, callback);
Yuncheol Heo38db6292014-07-01 14:15:14 +09001368 assertRunOnServiceThread();
1369 // Remove any repeated working actions.
1370 // HotplugDetectionAction will be reinstated during the wake up process.
1371 // HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
1372 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery().
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001373 removeAction(DeviceDiscoveryAction.class);
Yuncheol Heo38db6292014-07-01 14:15:14 +09001374 removeAction(HotplugDetectionAction.class);
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001375 removeAction(PowerStatusMonitorAction.class);
Jungshik Jangfaa49bc2014-08-05 16:10:21 +09001376 // Remove recording actions.
Jungshik Jangb6591b82014-07-23 16:10:23 +09001377 removeAction(OneTouchRecordAction.class);
Jungshik Jangfaa49bc2014-08-05 16:10:21 +09001378 removeAction(TimerRecordingAction.class);
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001379
1380 disableSystemAudioIfExist();
1381 disableArcIfExist();
Jinsuk Kim49b47bb2014-07-22 10:40:35 +09001382 clearDeviceInfoList();
Yuncheol Heo38db6292014-07-01 14:15:14 +09001383 checkIfPendingActionsCleared();
1384 }
1385
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001386 @ServiceThreadOnly
1387 private void disableSystemAudioIfExist() {
1388 assertRunOnServiceThread();
1389 if (getAvrDeviceInfo() == null) {
1390 return;
1391 }
1392
1393 // Seq #31.
1394 removeAction(SystemAudioActionFromAvr.class);
1395 removeAction(SystemAudioActionFromTv.class);
1396 removeAction(SystemAudioAutoInitiationAction.class);
1397 removeAction(SystemAudioStatusAction.class);
1398 removeAction(VolumeControlAction.class);
1399
Jinsuk Kim7ecfbae2014-07-11 14:16:29 +09001400 // Turn off the mode but do not write it the settings, so that the next time TV powers on
1401 // the system audio mode setting can be restored automatically.
1402 setSystemAudioMode(false, false);
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001403 }
1404
1405 @ServiceThreadOnly
1406 private void disableArcIfExist() {
1407 assertRunOnServiceThread();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001408 HdmiDeviceInfo avr = getAvrDeviceInfo();
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001409 if (avr == null) {
1410 return;
1411 }
1412
1413 // Seq #44.
1414 removeAction(RequestArcInitiationAction.class);
1415 if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) {
1416 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
1417 }
1418 }
1419
Yuncheol Heo38db6292014-07-01 14:15:14 +09001420 @Override
1421 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +09001422 protected void onStandby(boolean initiatedByCec) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001423 assertRunOnServiceThread();
1424 // Seq #11
1425 if (!mService.isControlEnabled()) {
1426 return;
1427 }
Jinsuk Kim544b62b2014-07-14 14:01:23 +09001428 if (!initiatedByCec && mAutoDeviceOff) {
Yuncheol Heo38db6292014-07-01 14:15:14 +09001429 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
Jinsuk Kimc0c20d02014-07-04 14:34:31 +09001430 mAddress, Constants.ADDR_BROADCAST));
Yuncheol Heo38db6292014-07-01 14:15:14 +09001431 }
1432 }
1433
Jinsuk Kim4d43d932014-07-03 16:43:58 +09001434 boolean isProhibitMode() {
1435 return mService.isProhibitMode();
1436 }
Jinsuk Kimb38cd682014-07-07 08:05:03 +09001437
1438 boolean isPowerStandbyOrTransient() {
1439 return mService.isPowerStandbyOrTransient();
Jungshik Jang8866c812014-07-08 14:42:28 +09001440 }
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +09001441
Jungshik Jang339227d2014-08-25 15:37:20 +09001442 @ServiceThreadOnly
Jinsuk Kimc7eba0f2014-07-07 14:18:02 +09001443 void displayOsd(int messageId) {
Jungshik Jang339227d2014-08-25 15:37:20 +09001444 assertRunOnServiceThread();
1445 mService.displayOsd(messageId);
Jinsuk Kimb38cd682014-07-07 08:05:03 +09001446 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001447
1448 // Seq #54 and #55
1449 @ServiceThreadOnly
1450 void startOneTouchRecord(int recorderAddress, byte[] recordSource) {
1451 assertRunOnServiceThread();
1452 if (!mService.isControlEnabled()) {
1453 Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001454 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001455 return;
1456 }
1457
1458 if (!checkRecorder(recorderAddress)) {
1459 Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001460 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001461 return;
1462 }
1463
1464 if (!checkRecordSource(recordSource)) {
1465 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001466 announceOneTouchRecordResult(ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001467 return;
1468 }
1469
1470 addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource));
1471 Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:"
1472 + Arrays.toString(recordSource));
1473 }
1474
1475 @ServiceThreadOnly
1476 void stopOneTouchRecord(int recorderAddress) {
1477 assertRunOnServiceThread();
1478 if (!mService.isControlEnabled()) {
1479 Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001480 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001481 return;
1482 }
1483
1484 if (!checkRecorder(recorderAddress)) {
1485 Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001486 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001487 return;
1488 }
1489
1490 // Remove one touch record action so that other one touch record can be started.
1491 removeAction(OneTouchRecordAction.class);
1492 mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress));
1493 Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress);
1494 }
1495
1496 private boolean checkRecorder(int recorderAddress) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001497 HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001498 return (device != null)
1499 && (HdmiUtils.getTypeFromAddress(recorderAddress)
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001500 == HdmiDeviceInfo.DEVICE_RECORDER);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001501 }
1502
1503 private boolean checkRecordSource(byte[] recordSource) {
1504 return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource);
1505 }
1506
1507 @ServiceThreadOnly
1508 void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1509 assertRunOnServiceThread();
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001510 if (!mService.isControlEnabled()) {
1511 Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
Jungshik Jange5a93372014-07-25 13:41:14 +09001512 announceTimerRecordingResult(TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001513 return;
1514 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001515
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001516 if (!checkRecorder(recorderAddress)) {
1517 Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
1518 announceTimerRecordingResult(
Jungshik Jange5a93372014-07-25 13:41:14 +09001519 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001520 return;
1521 }
1522
1523 if (!checkTimerRecordingSource(sourceType, recordSource)) {
1524 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
1525 announceTimerRecordingResult(
Jungshik Jange5a93372014-07-25 13:41:14 +09001526 TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
Jungshik Jang12e5dce2014-07-24 15:27:44 +09001527 return;
1528 }
1529
1530 addAndStartAction(
1531 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
1532 Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
1533 + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
1534 }
1535
1536 private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
1537 return (recordSource != null)
1538 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
Jungshik Jangb6591b82014-07-23 16:10:23 +09001539 }
1540
1541 @ServiceThreadOnly
1542 void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
1543 assertRunOnServiceThread();
Jungshik Jange5a93372014-07-25 13:41:14 +09001544 if (!mService.isControlEnabled()) {
1545 Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
Jungshik Jangfaa49bc2014-08-05 16:10:21 +09001546 announceClearTimerRecordingResult(CLEAR_TIMER_STATUS_CEC_DISABLE);
Jungshik Jange5a93372014-07-25 13:41:14 +09001547 return;
1548 }
Jungshik Jangb6591b82014-07-23 16:10:23 +09001549
Jungshik Jange5a93372014-07-25 13:41:14 +09001550 if (!checkRecorder(recorderAddress)) {
1551 Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
Jungshik Jangfaa49bc2014-08-05 16:10:21 +09001552 announceClearTimerRecordingResult(CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION);
Jungshik Jange5a93372014-07-25 13:41:14 +09001553 return;
1554 }
1555
1556 if (!checkTimerRecordingSource(sourceType, recordSource)) {
1557 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
Jungshik Jangfaa49bc2014-08-05 16:10:21 +09001558 announceClearTimerRecordingResult(CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
Jungshik Jange5a93372014-07-25 13:41:14 +09001559 return;
1560 }
1561
1562 sendClearTimerMessage(recorderAddress, sourceType, recordSource);
1563 }
1564
1565 private void sendClearTimerMessage(int recorderAddress, int sourceType, byte[] recordSource) {
1566 HdmiCecMessage message = null;
1567 switch (sourceType) {
1568 case TIMER_RECORDING_TYPE_DIGITAL:
1569 message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress,
1570 recordSource);
1571 break;
1572 case TIMER_RECORDING_TYPE_ANALOGUE:
1573 message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress,
1574 recordSource);
1575 break;
1576 case TIMER_RECORDING_TYPE_EXTERNAL:
1577 message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress,
1578 recordSource);
1579 break;
1580 default:
1581 Slog.w(TAG, "Invalid source type:" + recorderAddress);
Jungshik Jangfaa49bc2014-08-05 16:10:21 +09001582 announceClearTimerRecordingResult(CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
Jungshik Jange5a93372014-07-25 13:41:14 +09001583 return;
1584
1585 }
1586 mService.sendCecCommand(message, new SendMessageCallback() {
1587 @Override
1588 public void onSendCompleted(int error) {
1589 if (error != Constants.SEND_RESULT_SUCCESS) {
Jungshik Jangfaa49bc2014-08-05 16:10:21 +09001590 announceClearTimerRecordingResult(
1591 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE);
Jungshik Jange5a93372014-07-25 13:41:14 +09001592 }
1593 }
1594 });
Jungshik Jangb6591b82014-07-23 16:10:23 +09001595 }
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001596
1597 void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
Jinsuk Kim8960d1b2014-08-13 10:48:30 +09001598 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001599 if (info == null) {
1600 Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
1601 return;
1602 }
1603
1604 if (info.getDevicePowerStatus() == newPowerStatus) {
1605 return;
1606 }
1607
1608 HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
1609 // addDeviceInfo replaces old device info with new one if exists.
1610 addDeviceInfo(newInfo);
1611
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001612 invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
Jungshik Jang410ca9c2014-08-07 18:04:14 +09001613 }
Terry Heo959d2db2014-08-28 16:45:41 +09001614
1615 @Override
1616 protected void dump(final IndentingPrintWriter pw) {
1617 super.dump(pw);
1618 pw.println("mArcEstablished: " + mArcEstablished);
1619 pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
1620 pw.println("mSystemAudioActivated: " + mSystemAudioActivated);
1621 pw.println("mSystemAudioMute: " + mSystemAudioMute);
1622 pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
1623 pw.println("mAutoWakeup: " + mAutoWakeup);
1624 pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
Jinsuk Kim4b4b9402014-09-02 10:30:10 +09001625 pw.println("CEC devices:");
1626 pw.increaseIndent();
1627 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
1628 pw.println(info);
1629 }
1630 pw.decreaseIndent();
Terry Heo959d2db2014-08-28 16:45:41 +09001631 }
Jinsuk Kim2918e9e2014-05-20 16:45:45 +09001632}