Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.hdmi; |
| 18 | |
| 19 | import android.annotation.Nullable; |
| 20 | import android.content.Context; |
Jungshik Jang | a1fa91f | 2014-05-08 20:56:41 +0900 | [diff] [blame] | 21 | import android.hardware.hdmi.HdmiCec; |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 22 | import android.hardware.hdmi.HdmiCecDeviceInfo; |
| 23 | import android.hardware.hdmi.HdmiCecMessage; |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 24 | import android.hardware.hdmi.HdmiHotplugEvent; |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 25 | import android.hardware.hdmi.HdmiPortInfo; |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 26 | import android.hardware.hdmi.IHdmiControlCallback; |
| 27 | import android.hardware.hdmi.IHdmiControlService; |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 28 | import android.hardware.hdmi.IHdmiDeviceEventListener; |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 29 | import android.hardware.hdmi.IHdmiHotplugEventListener; |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 30 | import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; |
Jungshik Jang | a858d22 | 2014-06-23 17:17:47 +0900 | [diff] [blame] | 31 | import android.media.AudioManager; |
Jungshik Jang | 42c9800 | 2014-06-12 13:17:44 +0900 | [diff] [blame] | 32 | import android.os.Build; |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 33 | import android.os.Handler; |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 34 | import android.os.HandlerThread; |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 35 | import android.os.IBinder; |
Jungshik Jang | e9c77c8 | 2014-04-24 20:30:09 +0900 | [diff] [blame] | 36 | import android.os.Looper; |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 37 | import android.os.RemoteException; |
Jungshik Jang | 41d9746 | 2014-06-30 22:26:29 +0900 | [diff] [blame] | 38 | import android.util.Log; |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 39 | import android.util.Slog; |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 40 | import android.util.SparseArray; |
Jungshik Jang | 8b308d9 | 2014-05-29 21:52:28 +0900 | [diff] [blame] | 41 | import android.util.SparseIntArray; |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 42 | |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 43 | import com.android.internal.annotations.GuardedBy; |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 44 | import com.android.server.SystemService; |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 45 | import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 46 | import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 47 | |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 48 | import java.util.ArrayList; |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 49 | import java.util.Collections; |
Jungshik Jang | 02bb426 | 2014-05-23 16:48:31 +0900 | [diff] [blame] | 50 | import java.util.List; |
Jungshik Jang | a1fa91f | 2014-05-08 20:56:41 +0900 | [diff] [blame] | 51 | |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 52 | /** |
| 53 | * Provides a service for sending and processing HDMI control messages, |
| 54 | * HDMI-CEC and MHL control command, and providing the information on both standard. |
| 55 | */ |
| 56 | public final class HdmiControlService extends SystemService { |
| 57 | private static final String TAG = "HdmiControlService"; |
| 58 | |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 59 | // TODO: Rename the permission to HDMI_CONTROL. |
| 60 | private static final String PERMISSION = "android.permission.HDMI_CEC"; |
| 61 | |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 62 | /** |
| 63 | * Interface to report send result. |
| 64 | */ |
| 65 | interface SendMessageCallback { |
| 66 | /** |
| 67 | * Called when {@link HdmiControlService#sendCecCommand} is completed. |
| 68 | * |
Yuncheol Heo | ece603b | 2014-05-23 20:10:19 +0900 | [diff] [blame] | 69 | * @param error result of send request. |
| 70 | * @see {@link #SEND_RESULT_SUCCESS} |
| 71 | * @see {@link #SEND_RESULT_NAK} |
| 72 | * @see {@link #SEND_RESULT_FAILURE} |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 73 | */ |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 74 | void onSendCompleted(int error); |
| 75 | } |
| 76 | |
Jungshik Jang | 02bb426 | 2014-05-23 16:48:31 +0900 | [diff] [blame] | 77 | /** |
| 78 | * Interface to get a list of available logical devices. |
| 79 | */ |
| 80 | interface DevicePollingCallback { |
| 81 | /** |
| 82 | * Called when device polling is finished. |
| 83 | * |
| 84 | * @param ackedAddress a list of logical addresses of available devices |
| 85 | */ |
| 86 | void onPollingFinished(List<Integer> ackedAddress); |
| 87 | } |
| 88 | |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 89 | // A thread to handle synchronous IO of CEC and MHL control service. |
| 90 | // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) |
| 91 | // and sparse call it shares a thread to handle IO operations. |
| 92 | private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); |
| 93 | |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 94 | // Used to synchronize the access to the service. |
| 95 | private final Object mLock = new Object(); |
| 96 | |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 97 | // Type of logical devices hosted in the system. Stored in the unmodifiable list. |
| 98 | private final List<Integer> mLocalDevices; |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 99 | |
| 100 | // List of listeners registered by callers that want to get notified of |
| 101 | // hotplug events. |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 102 | @GuardedBy("mLock") |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 103 | private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); |
| 104 | |
| 105 | // List of records for hotplug event listener to handle the the caller killed in action. |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 106 | @GuardedBy("mLock") |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 107 | private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = |
| 108 | new ArrayList<>(); |
| 109 | |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 110 | // List of listeners registered by callers that want to get notified of |
| 111 | // device status events. |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 112 | @GuardedBy("mLock") |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 113 | private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>(); |
| 114 | |
| 115 | // List of records for device event listener to handle the the caller killed in action. |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 116 | @GuardedBy("mLock") |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 117 | private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = |
| 118 | new ArrayList<>(); |
| 119 | |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 120 | // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol |
| 121 | // handling will be disabled and no request will be handled. |
| 122 | @GuardedBy("mLock") |
| 123 | private boolean mHdmiControlEnabled; |
| 124 | |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 125 | // List of listeners registered by callers that want to get notified of |
| 126 | // system audio mode changes. |
| 127 | private final ArrayList<IHdmiSystemAudioModeChangeListener> |
| 128 | mSystemAudioModeChangeListeners = new ArrayList<>(); |
| 129 | // List of records for system audio mode change to handle the the caller killed in action. |
| 130 | private final ArrayList<SystemAudioModeChangeListenerRecord> |
| 131 | mSystemAudioModeChangeListenerRecords = new ArrayList<>(); |
| 132 | |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 133 | // Handler used to run a task in service thread. |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 134 | private final Handler mHandler = new Handler(); |
| 135 | |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 136 | @Nullable |
| 137 | private HdmiCecController mCecController; |
| 138 | |
| 139 | @Nullable |
| 140 | private HdmiMhlController mMhlController; |
| 141 | |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 142 | // HDMI port information. Stored in the unmodifiable list to keep the static information |
| 143 | // from being modified. |
| 144 | private List<HdmiPortInfo> mPortInfo; |
| 145 | |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 146 | public HdmiControlService(Context context) { |
| 147 | super(context); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 148 | mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray( |
| 149 | com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 150 | } |
| 151 | |
| 152 | @Override |
| 153 | public void onStart() { |
Jungshik Jang | 2f51aec | 2014-05-20 14:37:38 +0900 | [diff] [blame] | 154 | mIoThread.start(); |
Jungshik Jang | e9c77c8 | 2014-04-24 20:30:09 +0900 | [diff] [blame] | 155 | mCecController = HdmiCecController.create(this); |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 156 | |
Jinsuk Kim | a8a5e50 | 2014-05-15 16:51:49 +0900 | [diff] [blame] | 157 | if (mCecController != null) { |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 158 | initializeLocalDevices(mLocalDevices); |
Jinsuk Kim | a8a5e50 | 2014-05-15 16:51:49 +0900 | [diff] [blame] | 159 | } else { |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 160 | Slog.i(TAG, "Device does not support HDMI-CEC."); |
| 161 | } |
| 162 | |
Jungshik Jang | e9c77c8 | 2014-04-24 20:30:09 +0900 | [diff] [blame] | 163 | mMhlController = HdmiMhlController.create(this); |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 164 | if (mMhlController == null) { |
| 165 | Slog.i(TAG, "Device does not support MHL-control."); |
| 166 | } |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 167 | mPortInfo = initPortInfo(); |
Jinsuk Kim | 8692fc6 | 2014-05-29 07:39:22 +0900 | [diff] [blame] | 168 | publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); |
Yuncheol Heo | 63a2e06 | 2014-05-27 23:06:01 +0900 | [diff] [blame] | 169 | |
| 170 | // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and |
| 171 | // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 172 | mHdmiControlEnabled = true; |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 173 | } |
Jungshik Jang | e9c77c8 | 2014-04-24 20:30:09 +0900 | [diff] [blame] | 174 | |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 175 | @ServiceThreadOnly |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 176 | private void initializeLocalDevices(final List<Integer> deviceTypes) { |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 177 | assertRunOnServiceThread(); |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 178 | // A container for [Logical Address, Local device info]. |
| 179 | final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); |
| 180 | final SparseIntArray finished = new SparseIntArray(); |
Jinsuk Kim | 13c030e | 2014-06-20 13:25:17 +0900 | [diff] [blame] | 181 | mCecController.clearLogicalAddress(); |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 182 | for (int type : deviceTypes) { |
| 183 | final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); |
| 184 | localDevice.init(); |
| 185 | mCecController.allocateLogicalAddress(type, |
| 186 | localDevice.getPreferredAddress(), new AllocateAddressCallback() { |
| 187 | @Override |
| 188 | public void onAllocated(int deviceType, int logicalAddress) { |
| 189 | if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { |
| 190 | Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); |
| 191 | } else { |
| 192 | HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); |
| 193 | localDevice.setDeviceInfo(deviceInfo); |
| 194 | mCecController.addLocalDevice(deviceType, localDevice); |
| 195 | mCecController.addLogicalAddress(logicalAddress); |
| 196 | devices.append(logicalAddress, localDevice); |
| 197 | } |
| 198 | finished.append(deviceType, logicalAddress); |
| 199 | |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 200 | // Address allocation completed for all devices. Notify each device. |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 201 | if (deviceTypes.size() == finished.size()) { |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 202 | notifyAddressAllocated(devices); |
| 203 | } |
| 204 | } |
| 205 | }); |
| 206 | } |
| 207 | } |
| 208 | |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 209 | @ServiceThreadOnly |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 210 | private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 211 | assertRunOnServiceThread(); |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 212 | for (int i = 0; i < devices.size(); ++i) { |
| 213 | int address = devices.keyAt(i); |
| 214 | HdmiCecLocalDevice device = devices.valueAt(i); |
Jinsuk Kim | 13c030e | 2014-06-20 13:25:17 +0900 | [diff] [blame] | 215 | device.handleAddressAllocated(address); |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 216 | } |
| 217 | } |
| 218 | |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 219 | // Initialize HDMI port information. Combine the information from CEC and MHL HAL and |
| 220 | // keep them in one place. |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 221 | @ServiceThreadOnly |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 222 | private List<HdmiPortInfo> initPortInfo() { |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 223 | assertRunOnServiceThread(); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 224 | HdmiPortInfo[] cecPortInfo = null; |
| 225 | |
| 226 | // CEC HAL provides majority of the info while MHL does only MHL support flag for |
| 227 | // each port. Return empty array if CEC HAL didn't provide the info. |
| 228 | if (mCecController != null) { |
| 229 | cecPortInfo = mCecController.getPortInfos(); |
| 230 | } |
| 231 | if (cecPortInfo == null) { |
| 232 | return Collections.emptyList(); |
| 233 | } |
| 234 | |
| 235 | HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0]; |
| 236 | if (mMhlController != null) { |
| 237 | // TODO: Implement plumbing logic to get MHL port information. |
| 238 | // mhlPortInfo = mMhlController.getPortInfos(); |
| 239 | } |
| 240 | |
| 241 | // Use the id (port number) to find the matched info between CEC and MHL to combine them |
| 242 | // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found. |
| 243 | ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); |
| 244 | for (int i = 0; i < cecPortInfo.length; ++i) { |
| 245 | HdmiPortInfo cec = cecPortInfo[i]; |
| 246 | int id = cec.getId(); |
| 247 | boolean mhlInfoFound = false; |
| 248 | for (HdmiPortInfo mhl : mhlPortInfo) { |
| 249 | if (id == mhl.getId()) { |
| 250 | result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(), |
| 251 | cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported())); |
| 252 | mhlInfoFound = true; |
| 253 | break; |
| 254 | } |
| 255 | } |
| 256 | if (!mhlInfoFound) { |
| 257 | result.add(cec); |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | return Collections.unmodifiableList(result); |
| 262 | } |
| 263 | |
| 264 | /** |
| 265 | * Returns HDMI port information for the given port id. |
| 266 | * |
| 267 | * @param portId HDMI port id |
| 268 | * @return {@link HdmiPortInfo} for the given port |
| 269 | */ |
| 270 | HdmiPortInfo getPortInfo(int portId) { |
| 271 | // mPortInfo is an unmodifiable list and the only reference to its inner list. |
| 272 | // No lock is necessary. |
| 273 | for (HdmiPortInfo info : mPortInfo) { |
| 274 | if (portId == info.getId()) { |
| 275 | return info; |
| 276 | } |
| 277 | } |
| 278 | return null; |
| 279 | } |
| 280 | |
Jungshik Jang | e9c77c8 | 2014-04-24 20:30:09 +0900 | [diff] [blame] | 281 | /** |
Jinsuk Kim | 401e3de | 2014-06-14 07:47:39 +0900 | [diff] [blame] | 282 | * Returns the routing path (physical address) of the HDMI port for the given |
| 283 | * port id. |
| 284 | */ |
| 285 | int portIdToPath(int portId) { |
| 286 | HdmiPortInfo portInfo = getPortInfo(portId); |
| 287 | if (portInfo == null) { |
| 288 | Slog.e(TAG, "Cannot find the port info: " + portId); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 289 | return HdmiConstants.INVALID_PHYSICAL_ADDRESS; |
Jinsuk Kim | 401e3de | 2014-06-14 07:47:39 +0900 | [diff] [blame] | 290 | } |
| 291 | return portInfo.getAddress(); |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * Returns the id of HDMI port located at the top of the hierarchy of |
| 296 | * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, |
| 297 | * the port id to be returned is the ID associated with the port address |
| 298 | * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. |
| 299 | */ |
| 300 | int pathToPortId(int path) { |
| 301 | int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK; |
| 302 | for (HdmiPortInfo info : mPortInfo) { |
| 303 | if (portAddress == info.getAddress()) { |
| 304 | return info.getId(); |
| 305 | } |
| 306 | } |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 307 | return HdmiConstants.INVALID_PORT_ID; |
Jinsuk Kim | 401e3de | 2014-06-14 07:47:39 +0900 | [diff] [blame] | 308 | } |
| 309 | |
| 310 | /** |
Jungshik Jang | e9c77c8 | 2014-04-24 20:30:09 +0900 | [diff] [blame] | 311 | * Returns {@link Looper} for IO operation. |
| 312 | * |
| 313 | * <p>Declared as package-private. |
| 314 | */ |
| 315 | Looper getIoLooper() { |
| 316 | return mIoThread.getLooper(); |
| 317 | } |
| 318 | |
| 319 | /** |
| 320 | * Returns {@link Looper} of main thread. Use this {@link Looper} instance |
| 321 | * for tasks that are running on main service thread. |
| 322 | * |
| 323 | * <p>Declared as package-private. |
| 324 | */ |
| 325 | Looper getServiceLooper() { |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 326 | return mHandler.getLooper(); |
Jungshik Jang | e9c77c8 | 2014-04-24 20:30:09 +0900 | [diff] [blame] | 327 | } |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 328 | |
| 329 | /** |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 330 | * Returns physical address of the device. |
| 331 | */ |
| 332 | int getPhysicalAddress() { |
| 333 | return mCecController.getPhysicalAddress(); |
| 334 | } |
| 335 | |
| 336 | /** |
| 337 | * Returns vendor id of CEC service. |
| 338 | */ |
| 339 | int getVendorId() { |
| 340 | return mCecController.getVendorId(); |
| 341 | } |
| 342 | |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 343 | @ServiceThreadOnly |
Jinsuk Kim | a6ce770 | 2014-05-11 06:54:49 +0900 | [diff] [blame] | 344 | HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 345 | assertRunOnServiceThread(); |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 346 | HdmiCecLocalDeviceTv tv = tv(); |
| 347 | if (tv == null) { |
| 348 | return null; |
| 349 | } |
| 350 | return tv.getDeviceInfo(logicalAddress); |
Jinsuk Kim | a6ce770 | 2014-05-11 06:54:49 +0900 | [diff] [blame] | 351 | } |
| 352 | |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 353 | /** |
Jungshik Jang | 092b445 | 2014-06-11 15:19:17 +0900 | [diff] [blame] | 354 | * Returns version of CEC. |
| 355 | */ |
| 356 | int getCecVersion() { |
| 357 | return mCecController.getVersion(); |
| 358 | } |
| 359 | |
| 360 | /** |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 361 | * Whether a device of the specified physical address is connected to ARC enabled port. |
| 362 | */ |
| 363 | boolean isConnectedToArcPort(int physicalAddress) { |
| 364 | for (HdmiPortInfo portInfo : mPortInfo) { |
| 365 | if (hasSameTopPort(portInfo.getAddress(), physicalAddress) |
| 366 | && portInfo.isArcSupported()) { |
| 367 | return true; |
| 368 | } |
| 369 | } |
| 370 | return false; |
| 371 | } |
| 372 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 373 | void runOnServiceThread(Runnable runnable) { |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 374 | mHandler.post(runnable); |
| 375 | } |
| 376 | |
Yuncheol Heo | 63a2e06 | 2014-05-27 23:06:01 +0900 | [diff] [blame] | 377 | void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { |
| 378 | mHandler.postAtFrontOfQueue(runnable); |
| 379 | } |
| 380 | |
| 381 | private void assertRunOnServiceThread() { |
| 382 | if (Looper.myLooper() != mHandler.getLooper()) { |
| 383 | throw new IllegalStateException("Should run on service thread."); |
| 384 | } |
| 385 | } |
| 386 | |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 387 | /** |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 388 | * Transmit a CEC command to CEC bus. |
| 389 | * |
| 390 | * @param command CEC command to send out |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 391 | * @param callback interface used to the result of send command |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 392 | */ |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 393 | @ServiceThreadOnly |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 394 | void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 395 | assertRunOnServiceThread(); |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 396 | mCecController.sendCommand(command, callback); |
| 397 | } |
| 398 | |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 399 | @ServiceThreadOnly |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 400 | void sendCecCommand(HdmiCecMessage command) { |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 401 | assertRunOnServiceThread(); |
Jungshik Jang | d643f76 | 2014-05-22 19:28:09 +0900 | [diff] [blame] | 402 | mCecController.sendCommand(command, null); |
Jinsuk Kim | c70d229 | 2014-04-30 15:43:16 +0900 | [diff] [blame] | 403 | } |
| 404 | |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 405 | @ServiceThreadOnly |
Jungshik Jang | a1fa91f | 2014-05-08 20:56:41 +0900 | [diff] [blame] | 406 | boolean handleCecCommand(HdmiCecMessage message) { |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 407 | assertRunOnServiceThread(); |
Jungshik Jang | 092b445 | 2014-06-11 15:19:17 +0900 | [diff] [blame] | 408 | return dispatchMessageToLocalDevice(message); |
| 409 | } |
| 410 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 411 | void setAudioReturnChannel(boolean enabled) { |
| 412 | mCecController.setAudioReturnChannel(enabled); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 413 | } |
| 414 | |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 415 | @ServiceThreadOnly |
Jungshik Jang | 092b445 | 2014-06-11 15:19:17 +0900 | [diff] [blame] | 416 | private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 417 | assertRunOnServiceThread(); |
Jungshik Jang | 092b445 | 2014-06-11 15:19:17 +0900 | [diff] [blame] | 418 | for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 419 | if (device.dispatchMessage(message) |
| 420 | && message.getDestination() != HdmiCec.ADDR_BROADCAST) { |
Jungshik Jang | 092b445 | 2014-06-11 15:19:17 +0900 | [diff] [blame] | 421 | return true; |
| 422 | } |
| 423 | } |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 424 | |
Jungshik Jang | 3a959fc | 2014-07-03 09:34:05 +0900 | [diff] [blame] | 425 | if (message.getDestination() != HdmiCec.ADDR_BROADCAST) { |
| 426 | Slog.w(TAG, "Unhandled cec command:" + message); |
| 427 | } |
Jungshik Jang | 092b445 | 2014-06-11 15:19:17 +0900 | [diff] [blame] | 428 | return false; |
Jungshik Jang | a1fa91f | 2014-05-08 20:56:41 +0900 | [diff] [blame] | 429 | } |
| 430 | |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 431 | /** |
| 432 | * Called when a new hotplug event is issued. |
| 433 | * |
Jungshik Jang | 8b308d9 | 2014-05-29 21:52:28 +0900 | [diff] [blame] | 434 | * @param portNo hdmi port number where hot plug event issued. |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 435 | * @param connected whether to be plugged in or not |
| 436 | */ |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 437 | @ServiceThreadOnly |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 438 | void onHotplug(int portNo, boolean connected) { |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 439 | assertRunOnServiceThread(); |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 440 | for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { |
| 441 | device.onHotplug(portNo, connected); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 442 | } |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 443 | announceHotplugEvent(portNo, connected); |
Jungshik Jang | 67ea521 | 2014-05-15 14:05:24 +0900 | [diff] [blame] | 444 | } |
| 445 | |
Jungshik Jang | 02bb426 | 2014-05-23 16:48:31 +0900 | [diff] [blame] | 446 | /** |
| 447 | * Poll all remote devices. It sends <Polling Message> to all remote |
| 448 | * devices. |
| 449 | * |
| 450 | * @param callback an interface used to get a list of all remote devices' address |
Jungshik Jang | 1de5142 | 2014-07-03 11:14:26 +0900 | [diff] [blame^] | 451 | * @param sourceAddress a logical address of source device where sends polling message |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 452 | * @param pickStrategy strategy how to pick polling candidates |
Jungshik Jang | 02bb426 | 2014-05-23 16:48:31 +0900 | [diff] [blame] | 453 | * @param retryCount the number of retry used to send polling message to remote devices |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 454 | * @throw IllegalArgumentException if {@code pickStrategy} is invalid value |
Jungshik Jang | 02bb426 | 2014-05-23 16:48:31 +0900 | [diff] [blame] | 455 | */ |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 456 | @ServiceThreadOnly |
Jungshik Jang | 1de5142 | 2014-07-03 11:14:26 +0900 | [diff] [blame^] | 457 | void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, |
| 458 | int retryCount) { |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 459 | assertRunOnServiceThread(); |
Jungshik Jang | 1de5142 | 2014-07-03 11:14:26 +0900 | [diff] [blame^] | 460 | mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), |
| 461 | retryCount); |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 462 | } |
| 463 | |
| 464 | private int checkPollStrategy(int pickStrategy) { |
Jungshik Jang | 3ecdd832 | 2014-06-17 14:04:38 +0900 | [diff] [blame] | 465 | int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK; |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 466 | if (strategy == 0) { |
| 467 | throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); |
| 468 | } |
Jungshik Jang | 3ecdd832 | 2014-06-17 14:04:38 +0900 | [diff] [blame] | 469 | int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK; |
Jungshik Jang | 0f8b4b7 | 2014-05-28 17:58:58 +0900 | [diff] [blame] | 470 | if (iterationStrategy == 0) { |
| 471 | throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); |
| 472 | } |
| 473 | return strategy | iterationStrategy; |
Jungshik Jang | 02bb426 | 2014-05-23 16:48:31 +0900 | [diff] [blame] | 474 | } |
| 475 | |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 476 | List<HdmiCecLocalDevice> getAllLocalDevices() { |
| 477 | assertRunOnServiceThread(); |
| 478 | return mCecController.getLocalDeviceList(); |
| 479 | } |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 480 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 481 | Object getServiceLock() { |
| 482 | return mLock; |
| 483 | } |
| 484 | |
| 485 | void setAudioStatus(boolean mute, int volume) { |
| 486 | // TODO: Hook up with AudioManager. |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 487 | } |
| 488 | |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 489 | void announceSystemAudioModeChange(boolean enabled) { |
| 490 | for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { |
| 491 | invokeSystemAudioModeChange(listener, enabled); |
| 492 | } |
| 493 | } |
| 494 | |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 495 | private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { |
Jungshik Jang | 42c9800 | 2014-06-12 13:17:44 +0900 | [diff] [blame] | 496 | // TODO: find better name instead of model name. |
| 497 | String displayName = Build.MODEL; |
Jungshik Jang | 3ee6572 | 2014-06-03 16:22:30 +0900 | [diff] [blame] | 498 | return new HdmiCecDeviceInfo(logicalAddress, |
| 499 | getPhysicalAddress(), deviceType, getVendorId(), displayName); |
| 500 | } |
| 501 | |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 502 | // Record class that monitors the event of the caller of being killed. Used to clean up |
| 503 | // the listener list and record list accordingly. |
| 504 | private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { |
| 505 | private final IHdmiHotplugEventListener mListener; |
| 506 | |
| 507 | public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { |
| 508 | mListener = listener; |
| 509 | } |
| 510 | |
| 511 | @Override |
| 512 | public void binderDied() { |
| 513 | synchronized (mLock) { |
| 514 | mHotplugEventListenerRecords.remove(this); |
| 515 | mHotplugEventListeners.remove(mListener); |
| 516 | } |
| 517 | } |
| 518 | } |
| 519 | |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 520 | private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { |
| 521 | private final IHdmiDeviceEventListener mListener; |
| 522 | |
| 523 | public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { |
| 524 | mListener = listener; |
| 525 | } |
| 526 | |
| 527 | @Override |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 528 | public void binderDied() { |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 529 | synchronized (mLock) { |
| 530 | mDeviceEventListenerRecords.remove(this); |
| 531 | mDeviceEventListeners.remove(mListener); |
| 532 | } |
| 533 | } |
| 534 | } |
| 535 | |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 536 | private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { |
| 537 | private IHdmiSystemAudioModeChangeListener mListener; |
| 538 | |
| 539 | public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { |
| 540 | mListener = listener; |
| 541 | } |
| 542 | |
| 543 | @Override |
| 544 | public void binderDied() { |
| 545 | synchronized (mLock) { |
| 546 | mSystemAudioModeChangeListenerRecords.remove(this); |
| 547 | mSystemAudioModeChangeListeners.remove(mListener); |
| 548 | } |
| 549 | } |
| 550 | } |
| 551 | |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 552 | private void enforceAccessPermission() { |
| 553 | getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); |
| 554 | } |
| 555 | |
| 556 | private final class BinderService extends IHdmiControlService.Stub { |
| 557 | @Override |
| 558 | public int[] getSupportedTypes() { |
| 559 | enforceAccessPermission(); |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 560 | // mLocalDevices is an unmodifiable list - no lock necesary. |
| 561 | int[] localDevices = new int[mLocalDevices.size()]; |
| 562 | for (int i = 0; i < localDevices.length; ++i) { |
| 563 | localDevices[i] = mLocalDevices.get(i); |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 564 | } |
Jinsuk Kim | 0340bbc | 2014-06-05 11:07:47 +0900 | [diff] [blame] | 565 | return localDevices; |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 566 | } |
| 567 | |
| 568 | @Override |
Jinsuk Kim | a6ce770 | 2014-05-11 06:54:49 +0900 | [diff] [blame] | 569 | public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { |
| 570 | enforceAccessPermission(); |
| 571 | runOnServiceThread(new Runnable() { |
| 572 | @Override |
| 573 | public void run() { |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 574 | HdmiCecLocalDeviceTv tv = tv(); |
Jinsuk Kim | a6ce770 | 2014-05-11 06:54:49 +0900 | [diff] [blame] | 575 | if (tv == null) { |
Jinsuk Kim | a062a93 | 2014-06-18 10:00:39 +0900 | [diff] [blame] | 576 | Slog.w(TAG, "Local tv device not available"); |
Jinsuk Kim | a6ce770 | 2014-05-11 06:54:49 +0900 | [diff] [blame] | 577 | invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); |
| 578 | return; |
| 579 | } |
| 580 | tv.deviceSelect(logicalAddress, callback); |
| 581 | } |
| 582 | }); |
| 583 | } |
| 584 | |
Jinsuk Kim | a6ce770 | 2014-05-11 06:54:49 +0900 | [diff] [blame] | 585 | @Override |
Jinsuk Kim | a062a93 | 2014-06-18 10:00:39 +0900 | [diff] [blame] | 586 | public void portSelect(final int portId, final IHdmiControlCallback callback) { |
| 587 | enforceAccessPermission(); |
| 588 | runOnServiceThread(new Runnable() { |
| 589 | @Override |
| 590 | public void run() { |
| 591 | HdmiCecLocalDeviceTv tv = tv(); |
| 592 | if (tv == null) { |
| 593 | Slog.w(TAG, "Local tv device not available"); |
| 594 | invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); |
| 595 | return; |
| 596 | } |
Jinsuk Kim | 8333571 | 2014-06-24 07:57:00 +0900 | [diff] [blame] | 597 | tv.doManualPortSwitching(portId, callback); |
Jinsuk Kim | a062a93 | 2014-06-18 10:00:39 +0900 | [diff] [blame] | 598 | } |
| 599 | }); |
| 600 | } |
| 601 | |
| 602 | @Override |
| 603 | public void sendKeyEvent(final int keyCode, final boolean isPressed) { |
| 604 | enforceAccessPermission(); |
| 605 | runOnServiceThread(new Runnable() { |
| 606 | @Override |
| 607 | public void run() { |
| 608 | // TODO: sendKeyEvent is for TV device only for now. Allow other |
| 609 | // local devices of different types to use this as well. |
| 610 | HdmiCecLocalDeviceTv tv = tv(); |
| 611 | if (tv == null) { |
| 612 | Slog.w(TAG, "Local tv device not available"); |
| 613 | return; |
| 614 | } |
| 615 | tv.sendKeyEvent(keyCode, isPressed); |
| 616 | } |
| 617 | }); |
| 618 | } |
| 619 | |
| 620 | @Override |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 621 | public void oneTouchPlay(final IHdmiControlCallback callback) { |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 622 | enforceAccessPermission(); |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 623 | runOnServiceThread(new Runnable() { |
| 624 | @Override |
| 625 | public void run() { |
| 626 | HdmiControlService.this.oneTouchPlay(callback); |
| 627 | } |
| 628 | }); |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 629 | } |
| 630 | |
| 631 | @Override |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 632 | public void queryDisplayStatus(final IHdmiControlCallback callback) { |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 633 | enforceAccessPermission(); |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 634 | runOnServiceThread(new Runnable() { |
| 635 | @Override |
| 636 | public void run() { |
| 637 | HdmiControlService.this.queryDisplayStatus(callback); |
| 638 | } |
| 639 | }); |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 640 | } |
| 641 | |
| 642 | @Override |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 643 | public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 644 | enforceAccessPermission(); |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 645 | runOnServiceThread(new Runnable() { |
| 646 | @Override |
| 647 | public void run() { |
| 648 | HdmiControlService.this.addHotplugEventListener(listener); |
| 649 | } |
| 650 | }); |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 651 | } |
| 652 | |
| 653 | @Override |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 654 | public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 655 | enforceAccessPermission(); |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 656 | runOnServiceThread(new Runnable() { |
| 657 | @Override |
| 658 | public void run() { |
| 659 | HdmiControlService.this.removeHotplugEventListener(listener); |
| 660 | } |
| 661 | }); |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 662 | } |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 663 | |
| 664 | @Override |
| 665 | public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { |
| 666 | enforceAccessPermission(); |
| 667 | runOnServiceThread(new Runnable() { |
| 668 | @Override |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 669 | public void run() { |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 670 | HdmiControlService.this.addDeviceEventListener(listener); |
| 671 | } |
| 672 | }); |
| 673 | } |
| 674 | |
| 675 | @Override |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 676 | public List<HdmiPortInfo> getPortInfo() { |
| 677 | enforceAccessPermission(); |
| 678 | return mPortInfo; |
| 679 | } |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 680 | |
| 681 | @Override |
| 682 | public boolean canChangeSystemAudioMode() { |
| 683 | enforceAccessPermission(); |
| 684 | HdmiCecLocalDeviceTv tv = tv(); |
| 685 | if (tv == null) { |
| 686 | return false; |
| 687 | } |
Jungshik Jang | e9cf158 | 2014-06-23 17:28:58 +0900 | [diff] [blame] | 688 | return tv.hasSystemAudioDevice(); |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 689 | } |
| 690 | |
| 691 | @Override |
| 692 | public boolean getSystemAudioMode() { |
| 693 | enforceAccessPermission(); |
| 694 | HdmiCecLocalDeviceTv tv = tv(); |
| 695 | if (tv == null) { |
| 696 | return false; |
| 697 | } |
| 698 | return tv.getSystemAudioMode(); |
| 699 | } |
| 700 | |
| 701 | @Override |
| 702 | public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { |
| 703 | enforceAccessPermission(); |
| 704 | runOnServiceThread(new Runnable() { |
| 705 | @Override |
| 706 | public void run() { |
| 707 | HdmiCecLocalDeviceTv tv = tv(); |
| 708 | if (tv == null) { |
| 709 | Slog.w(TAG, "Local tv device not available"); |
| 710 | invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); |
| 711 | return; |
| 712 | } |
| 713 | tv.changeSystemAudioMode(enabled, callback); |
| 714 | } |
| 715 | }); |
| 716 | } |
| 717 | |
| 718 | @Override |
| 719 | public void addSystemAudioModeChangeListener( |
| 720 | final IHdmiSystemAudioModeChangeListener listener) { |
| 721 | enforceAccessPermission(); |
| 722 | HdmiControlService.this.addSystemAudioModeChangeListner(listener); |
| 723 | } |
| 724 | |
| 725 | @Override |
| 726 | public void removeSystemAudioModeChangeListener( |
| 727 | final IHdmiSystemAudioModeChangeListener listener) { |
| 728 | enforceAccessPermission(); |
| 729 | HdmiControlService.this.removeSystemAudioModeChangeListener(listener); |
| 730 | } |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 731 | |
| 732 | @Override |
Jinsuk Kim | 160a6e5 | 2014-07-02 06:16:36 +0900 | [diff] [blame] | 733 | public void setControlEnabled(final boolean enabled) { |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 734 | enforceAccessPermission(); |
| 735 | synchronized (mLock) { |
| 736 | mHdmiControlEnabled = enabled; |
| 737 | } |
| 738 | // TODO: Stop the running actions when disabled, and start |
| 739 | // address allocation/device discovery when enabled. |
| 740 | if (!enabled) { |
| 741 | return; |
| 742 | } |
| 743 | runOnServiceThread(new Runnable() { |
| 744 | @Override |
| 745 | public void run() { |
| 746 | HdmiCecLocalDeviceTv tv = tv(); |
| 747 | if (tv == null) { |
| 748 | return; |
| 749 | } |
Jinsuk Kim | 160a6e5 | 2014-07-02 06:16:36 +0900 | [diff] [blame] | 750 | int value = enabled ? HdmiCec.ENABLED : HdmiCec.DISABLED; |
| 751 | mCecController.setOption(HdmiCec.OPTION_CEC_ENABLE, value); |
| 752 | if (mMhlController != null) { |
| 753 | mMhlController.setOption(HdmiCec.OPTION_MHL_ENABLE, value); |
| 754 | } |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 755 | tv.routingAtEnableTime(); |
| 756 | } |
| 757 | }); |
| 758 | } |
Jungshik Jang | a13da0d | 2014-06-30 16:26:06 +0900 | [diff] [blame] | 759 | |
| 760 | @Override |
Jungshik Jang | 41d9746 | 2014-06-30 22:26:29 +0900 | [diff] [blame] | 761 | public void setSystemAudioVolume(final int oldIndex, final int newIndex, |
| 762 | final int maxIndex) { |
| 763 | enforceAccessPermission(); |
| 764 | runOnServiceThread(new Runnable() { |
| 765 | @Override |
| 766 | public void run() { |
| 767 | HdmiCecLocalDeviceTv tv = tv(); |
| 768 | if (tv == null) { |
| 769 | Slog.w(TAG, "Local tv device not available"); |
| 770 | return; |
| 771 | } |
| 772 | tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); |
| 773 | } |
| 774 | }); |
| 775 | } |
| 776 | |
| 777 | @Override |
| 778 | public void setSystemAudioMute(final boolean mute) { |
| 779 | enforceAccessPermission(); |
| 780 | runOnServiceThread(new Runnable() { |
| 781 | @Override |
| 782 | public void run() { |
| 783 | HdmiCecLocalDeviceTv tv = tv(); |
| 784 | if (tv == null) { |
| 785 | Slog.w(TAG, "Local tv device not available"); |
| 786 | return; |
| 787 | } |
| 788 | tv.changeMute(mute); |
| 789 | } |
| 790 | }); |
| 791 | } |
| 792 | |
| 793 | @Override |
Jungshik Jang | a13da0d | 2014-06-30 16:26:06 +0900 | [diff] [blame] | 794 | public void setArcMode(final boolean enabled) { |
| 795 | enforceAccessPermission(); |
| 796 | runOnServiceThread(new Runnable() { |
| 797 | @Override |
| 798 | public void run() { |
| 799 | HdmiCecLocalDeviceTv tv = tv(); |
| 800 | if (tv == null) { |
Jungshik Jang | 41d9746 | 2014-06-30 22:26:29 +0900 | [diff] [blame] | 801 | Log.w(TAG, "Local tv device not available to change arc mode."); |
Jungshik Jang | a13da0d | 2014-06-30 16:26:06 +0900 | [diff] [blame] | 802 | return; |
| 803 | } |
| 804 | } |
| 805 | }); |
| 806 | } |
Jinsuk Kim | 160a6e5 | 2014-07-02 06:16:36 +0900 | [diff] [blame] | 807 | |
| 808 | @Override |
| 809 | public void setOption(final int key, final int value) { |
| 810 | if (!isTvDevice()) { |
| 811 | return; |
| 812 | } |
| 813 | switch (key) { |
| 814 | case HdmiCec.OPTION_CEC_AUTO_WAKEUP: |
| 815 | mCecController.setOption(key, value); |
| 816 | break; |
| 817 | case HdmiCec.OPTION_CEC_AUTO_DEVICE_OFF: |
| 818 | // No need to pass this option to HAL. |
| 819 | tv().setAutoDeviceOff(value == HdmiCec.ENABLED); |
| 820 | break; |
| 821 | case HdmiCec.OPTION_MHL_INPUT_SWITCHING: // Fall through |
| 822 | case HdmiCec.OPTION_MHL_POWER_CHARGE: |
| 823 | if (mMhlController != null) { |
| 824 | mMhlController.setOption(key, value); |
| 825 | } |
| 826 | break; |
| 827 | } |
| 828 | } |
| 829 | |
| 830 | private boolean isTvDevice() { |
| 831 | return tv() != null; |
| 832 | } |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 833 | } |
| 834 | |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 835 | @ServiceThreadOnly |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 836 | private void oneTouchPlay(final IHdmiControlCallback callback) { |
| 837 | assertRunOnServiceThread(); |
| 838 | HdmiCecLocalDevicePlayback source = playback(); |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 839 | if (source == null) { |
| 840 | Slog.w(TAG, "Local playback device not available"); |
| 841 | invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); |
| 842 | return; |
| 843 | } |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 844 | source.oneTouchPlay(callback); |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 845 | } |
| 846 | |
Jungshik Jang | a5b7414 | 2014-06-23 18:03:10 +0900 | [diff] [blame] | 847 | @ServiceThreadOnly |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 848 | private void queryDisplayStatus(final IHdmiControlCallback callback) { |
| 849 | assertRunOnServiceThread(); |
| 850 | HdmiCecLocalDevicePlayback source = playback(); |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 851 | if (source == null) { |
| 852 | Slog.w(TAG, "Local playback device not available"); |
| 853 | invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); |
| 854 | return; |
| 855 | } |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 856 | source.queryDisplayStatus(callback); |
Jinsuk Kim | 78d695d | 2014-05-13 16:36:15 +0900 | [diff] [blame] | 857 | } |
| 858 | |
| 859 | private void addHotplugEventListener(IHdmiHotplugEventListener listener) { |
| 860 | HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); |
| 861 | try { |
| 862 | listener.asBinder().linkToDeath(record, 0); |
| 863 | } catch (RemoteException e) { |
| 864 | Slog.w(TAG, "Listener already died"); |
| 865 | return; |
| 866 | } |
| 867 | synchronized (mLock) { |
| 868 | mHotplugEventListenerRecords.add(record); |
| 869 | mHotplugEventListeners.add(listener); |
| 870 | } |
| 871 | } |
| 872 | |
| 873 | private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { |
| 874 | synchronized (mLock) { |
| 875 | for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { |
| 876 | if (record.mListener.asBinder() == listener.asBinder()) { |
| 877 | listener.asBinder().unlinkToDeath(record, 0); |
| 878 | mHotplugEventListenerRecords.remove(record); |
| 879 | break; |
| 880 | } |
| 881 | } |
| 882 | mHotplugEventListeners.remove(listener); |
| 883 | } |
| 884 | } |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 885 | |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 886 | private void addDeviceEventListener(IHdmiDeviceEventListener listener) { |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 887 | DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); |
| 888 | try { |
| 889 | listener.asBinder().linkToDeath(record, 0); |
| 890 | } catch (RemoteException e) { |
| 891 | Slog.w(TAG, "Listener already died"); |
| 892 | return; |
| 893 | } |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 894 | synchronized (mLock) { |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 895 | mDeviceEventListeners.add(listener); |
| 896 | mDeviceEventListenerRecords.add(record); |
| 897 | } |
| 898 | } |
| 899 | |
| 900 | void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) { |
| 901 | synchronized (mLock) { |
| 902 | for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { |
| 903 | try { |
| 904 | listener.onStatusChanged(device, activated); |
| 905 | } catch (RemoteException e) { |
| 906 | Slog.e(TAG, "Failed to report device event:" + e); |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 907 | } |
| 908 | } |
Jinsuk Kim | 6d97f5b | 2014-06-16 11:41:42 +0900 | [diff] [blame] | 909 | } |
| 910 | } |
| 911 | |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 912 | private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { |
| 913 | SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( |
| 914 | listener); |
| 915 | try { |
| 916 | listener.asBinder().linkToDeath(record, 0); |
| 917 | } catch (RemoteException e) { |
| 918 | Slog.w(TAG, "Listener already died"); |
| 919 | return; |
| 920 | } |
| 921 | synchronized (mLock) { |
| 922 | mSystemAudioModeChangeListeners.add(listener); |
| 923 | mSystemAudioModeChangeListenerRecords.add(record); |
| 924 | } |
| 925 | } |
| 926 | |
| 927 | private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { |
| 928 | synchronized (mLock) { |
| 929 | for (SystemAudioModeChangeListenerRecord record : |
| 930 | mSystemAudioModeChangeListenerRecords) { |
| 931 | if (record.mListener.asBinder() == listener) { |
| 932 | listener.asBinder().unlinkToDeath(record, 0); |
| 933 | mSystemAudioModeChangeListenerRecords.remove(record); |
| 934 | break; |
| 935 | } |
| 936 | } |
| 937 | mSystemAudioModeChangeListeners.remove(listener); |
| 938 | } |
| 939 | } |
| 940 | |
Jinsuk Kim | 7fe2ae0 | 2014-05-26 17:33:05 +0900 | [diff] [blame] | 941 | private void invokeCallback(IHdmiControlCallback callback, int result) { |
| 942 | try { |
| 943 | callback.onComplete(result); |
| 944 | } catch (RemoteException e) { |
| 945 | Slog.e(TAG, "Invoking callback failed:" + e); |
| 946 | } |
| 947 | } |
Yuncheol Heo | 63a2e06 | 2014-05-27 23:06:01 +0900 | [diff] [blame] | 948 | |
Jungshik Jang | ea67c18 | 2014-06-19 22:19:20 +0900 | [diff] [blame] | 949 | private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, |
| 950 | boolean enabled) { |
| 951 | try { |
| 952 | listener.onStatusChanged(enabled); |
| 953 | } catch (RemoteException e) { |
| 954 | Slog.e(TAG, "Invoking callback failed:" + e); |
| 955 | } |
| 956 | } |
| 957 | |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 958 | private void announceHotplugEvent(int portId, boolean connected) { |
| 959 | HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 960 | synchronized (mLock) { |
| 961 | for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 962 | invokeHotplugEventListenerLocked(listener, event); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 963 | } |
| 964 | } |
| 965 | } |
| 966 | |
Jinsuk Kim | 4893c7e | 2014-06-19 14:13:22 +0900 | [diff] [blame] | 967 | private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 968 | HdmiHotplugEvent event) { |
| 969 | try { |
| 970 | listener.onReceived(event); |
| 971 | } catch (RemoteException e) { |
| 972 | Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); |
| 973 | } |
Jungshik Jang | e81e108 | 2014-06-05 15:37:59 +0900 | [diff] [blame] | 974 | } |
| 975 | |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 976 | private static boolean hasSameTopPort(int path1, int path2) { |
| 977 | return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK) |
| 978 | == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK); |
| 979 | } |
| 980 | |
Jungshik Jang | 79c58a4 | 2014-06-16 16:45:36 +0900 | [diff] [blame] | 981 | private HdmiCecLocalDeviceTv tv() { |
| 982 | return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); |
| 983 | } |
| 984 | |
| 985 | private HdmiCecLocalDevicePlayback playback() { |
| 986 | return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); |
Jungshik Jang | 60cffce | 2014-06-12 18:03:04 +0900 | [diff] [blame] | 987 | } |
Jungshik Jang | a858d22 | 2014-06-23 17:17:47 +0900 | [diff] [blame] | 988 | |
| 989 | AudioManager getAudioManager() { |
| 990 | return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); |
| 991 | } |
Jinsuk Kim | 92b77cf | 2014-06-27 16:39:26 +0900 | [diff] [blame] | 992 | |
| 993 | boolean isControlEnabled() { |
| 994 | synchronized (mLock) { |
| 995 | return mHdmiControlEnabled; |
| 996 | } |
| 997 | } |
Jungshik Jang | 0792d37 | 2014-04-23 17:57:26 +0900 | [diff] [blame] | 998 | } |