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.hardware.hdmi.HdmiDeviceInfo; 20 import android.util.Slog; 21 import android.util.SparseArray; 22 23 import java.util.ArrayList; 24 import java.util.Collections; 25 import java.util.List; 26 27 /** 28 * Various utilities to handle HDMI CEC messages. 29 */ 30 final class HdmiUtils { 31 32 private static final int[] ADDRESS_TO_TYPE = { 33 HdmiDeviceInfo.DEVICE_TV, // ADDR_TV 34 HdmiDeviceInfo.DEVICE_RECORDER, // ADDR_RECORDER_1 35 HdmiDeviceInfo.DEVICE_RECORDER, // ADDR_RECORDER_2 36 HdmiDeviceInfo.DEVICE_TUNER, // ADDR_TUNER_1 37 HdmiDeviceInfo.DEVICE_PLAYBACK, // ADDR_PLAYBACK_1 38 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, // ADDR_AUDIO_SYSTEM 39 HdmiDeviceInfo.DEVICE_TUNER, // ADDR_TUNER_2 40 HdmiDeviceInfo.DEVICE_TUNER, // ADDR_TUNER_3 41 HdmiDeviceInfo.DEVICE_PLAYBACK, // ADDR_PLAYBACK_2 42 HdmiDeviceInfo.DEVICE_RECORDER, // ADDR_RECORDER_3 43 HdmiDeviceInfo.DEVICE_TUNER, // ADDR_TUNER_4 44 HdmiDeviceInfo.DEVICE_PLAYBACK, // ADDR_PLAYBACK_3 45 HdmiDeviceInfo.DEVICE_RESERVED, 46 HdmiDeviceInfo.DEVICE_RESERVED, 47 HdmiDeviceInfo.DEVICE_TV, // ADDR_SPECIFIC_USE 48 }; 49 50 private static final String[] DEFAULT_NAMES = { 51 "TV", 52 "Recorder_1", 53 "Recorder_2", 54 "Tuner_1", 55 "Playback_1", 56 "AudioSystem", 57 "Tuner_2", 58 "Tuner_3", 59 "Playback_2", 60 "Recorder_3", 61 "Tuner_4", 62 "Playback_3", 63 "Reserved_1", 64 "Reserved_2", 65 "Secondary_TV", 66 }; 67 68 private HdmiUtils() { /* cannot be instantiated */ } 69 70 /** 71 * Check if the given logical address is valid. A logical address is valid 72 * if it is one allocated for an actual device which allows communication 73 * with other logical devices. 74 * 75 * @param address logical address 76 * @return true if the given address is valid 77 */ 78 static boolean isValidAddress(int address) { 79 return (Constants.ADDR_TV <= address && address <= Constants.ADDR_SPECIFIC_USE); 80 } 81 82 /** 83 * Return the device type for the given logical address. 84 * 85 * @param address logical address 86 * @return device type for the given logical address; DEVICE_INACTIVE 87 * if the address is not valid. 88 */ 89 static int getTypeFromAddress(int address) { 90 if (isValidAddress(address)) { 91 return ADDRESS_TO_TYPE[address]; 92 } 93 return HdmiDeviceInfo.DEVICE_INACTIVE; 94 } 95 96 /** 97 * Return the default device name for a logical address. This is the name 98 * by which the logical device is known to others until a name is 99 * set explicitly using HdmiCecService.setOsdName. 100 * 101 * @param address logical address 102 * @return default device name; empty string if the address is not valid 103 */ 104 static String getDefaultDeviceName(int address) { 105 if (isValidAddress(address)) { 106 return DEFAULT_NAMES[address]; 107 } 108 return ""; 109 } 110 111 /** 112 * Verify if the given address is for the given device type. If not it will throw 113 * {@link IllegalArgumentException}. 114 * 115 * @param logicalAddress the logical address to verify 116 * @param deviceType the device type to check 117 * @throw IllegalArgumentException 118 */ 119 static void verifyAddressType(int logicalAddress, int deviceType) { 120 int actualDeviceType = getTypeFromAddress(logicalAddress); 121 if (actualDeviceType != deviceType) { 122 throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType 123 + ", Actual:" + actualDeviceType); 124 } 125 } 126 127 /** 128 * Check if the given CEC message come from the given address. 129 * 130 * @param cmd the CEC message to check 131 * @param expectedAddress the expected source address of the given message 132 * @param tag the tag of caller module (for log message) 133 * @return true if the CEC message comes from the given address 134 */ 135 static boolean checkCommandSource(HdmiCecMessage cmd, int expectedAddress, String tag) { 136 int src = cmd.getSource(); 137 if (src != expectedAddress) { 138 Slog.w(tag, "Invalid source [Expected:" + expectedAddress + ", Actual:" + src + "]"); 139 return false; 140 } 141 return true; 142 } 143 144 /** 145 * Parse the parameter block of CEC message as [System Audio Status]. 146 * 147 * @param cmd the CEC message to parse 148 * @return true if the given parameter has [ON] value 149 */ 150 static boolean parseCommandParamSystemAudioStatus(HdmiCecMessage cmd) { 151 return cmd.getParams()[0] == Constants.SYSTEM_AUDIO_STATUS_ON; 152 } 153 154 /** 155 * Parse the <Report Audio Status> message and check if it is mute 156 * 157 * @param cmd the CEC message to parse 158 * @return true if the given parameter has [MUTE] 159 */ 160 static boolean isAudioStatusMute(HdmiCecMessage cmd) { 161 byte params[] = cmd.getParams(); 162 return (params[0] & 0x80) == 0x80; 163 } 164 165 /** 166 * Parse the <Report Audio Status> message and extract the volume 167 * 168 * @param cmd the CEC message to parse 169 * @return device's volume. Constants.UNKNOWN_VOLUME in case it is out of range 170 */ 171 static int getAudioStatusVolume(HdmiCecMessage cmd) { 172 byte params[] = cmd.getParams(); 173 int volume = params[0] & 0x7F; 174 if (volume < 0x00 || 0x64 < volume) { 175 volume = Constants.UNKNOWN_VOLUME; 176 } 177 return volume; 178 } 179 180 /** 181 * Convert integer array to list of {@link Integer}. 182 * 183 * <p>The result is immutable. 184 * 185 * @param is integer array 186 * @return {@link List} instance containing the elements in the given array 187 */ 188 static List<Integer> asImmutableList(final int[] is) { 189 ArrayList<Integer> list = new ArrayList<>(is.length); 190 for (int type : is) { 191 list.add(type); 192 } 193 return Collections.unmodifiableList(list); 194 } 195 196 /** 197 * Assemble two bytes into single integer value. 198 * 199 * @param data to be assembled 200 * @return assembled value 201 */ 202 static int twoBytesToInt(byte[] data) { 203 return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); 204 } 205 206 /** 207 * Assemble two bytes into single integer value. 208 * 209 * @param data to be assembled 210 * @param offset offset to the data to convert in the array 211 * @return assembled value 212 */ 213 static int twoBytesToInt(byte[] data, int offset) { 214 return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); 215 } 216 217 /** 218 * Assemble three bytes into single integer value. 219 * 220 * @param data to be assembled 221 * @return assembled value 222 */ 223 static int threeBytesToInt(byte[] data) { 224 return ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); 225 } 226 227 static <T> List<T> sparseArrayToList(SparseArray<T> array) { 228 ArrayList<T> list = new ArrayList<>(); 229 for (int i = 0; i < array.size(); ++i) { 230 list.add(array.valueAt(i)); 231 } 232 return list; 233 } 234 235 static <T> List<T> mergeToUnmodifiableList(List<T> a, List<T> b) { 236 if (a.isEmpty() && b.isEmpty()) { 237 return Collections.emptyList(); 238 } 239 if (a.isEmpty()) { 240 return Collections.unmodifiableList(b); 241 } 242 if (b.isEmpty()) { 243 return Collections.unmodifiableList(a); 244 } 245 List<T> newList = new ArrayList<>(); 246 newList.addAll(a); 247 newList.addAll(b); 248 return Collections.unmodifiableList(newList); 249 } 250 251 /** 252 * See if the new path is affecting the active path. 253 * 254 * @param activePath current active path 255 * @param newPath new path 256 * @return true if the new path changes the current active path 257 */ 258 static boolean isAffectingActiveRoutingPath(int activePath, int newPath) { 259 // The new path affects the current active path if the parent of the new path 260 // is an ancestor of the active path. 261 // (1.1.0.0, 2.0.0.0) -> true, new path alters the parent 262 // (1.1.0.0, 1.2.0.0) -> true, new path is a sibling 263 // (1.1.0.0, 1.2.1.0) -> false, new path is a descendant of a sibling 264 // (1.0.0.0, 3.2.0.0) -> false, in a completely different path 265 266 // Get the parent of the new path by clearing the least significant 267 // non-zero nibble. 268 for (int i = 0; i <= 12; i += 4) { 269 int nibble = (newPath >> i) & 0xF; 270 if (nibble != 0) { 271 int mask = 0xFFF0 << i; 272 newPath &= mask; 273 break; 274 } 275 } 276 if (newPath == 0x0000) { 277 return true; // Top path always affects the active path 278 } 279 return isInActiveRoutingPath(activePath, newPath); 280 } 281 282 /** 283 * See if the new path is in the active path. 284 * 285 * @param activePath current active path 286 * @param newPath new path 287 * @return true if the new path in the active routing path 288 */ 289 static boolean isInActiveRoutingPath(int activePath, int newPath) { 290 // Check each nibble of the currently active path and the new path till the position 291 // where the active nibble is not zero. For (activePath, newPath), 292 // (1.1.0.0, 1.0.0.0) -> true, new path is a parent 293 // (1.2.1.0, 1.2.1.2) -> true, new path is a descendant 294 // (1.1.0.0, 1.2.0.0) -> false, new path is a sibling 295 // (1.0.0.0, 2.0.0.0) -> false, in a completely different path 296 for (int i = 12; i >= 0; i -= 4) { 297 int nibbleActive = (activePath >> i) & 0xF; 298 if (nibbleActive == 0) { 299 break; 300 } 301 int nibbleNew = (newPath >> i) & 0xF; 302 if (nibbleNew == 0) { 303 break; 304 } 305 if (nibbleActive != nibbleNew) { 306 return false; 307 } 308 } 309 return true; 310 } 311 312 /** 313 * Clone {@link HdmiDeviceInfo} with new power status. 314 */ 315 static HdmiDeviceInfo cloneHdmiDeviceInfo(HdmiDeviceInfo info, int newPowerStatus) { 316 return new HdmiDeviceInfo(info.getLogicalAddress(), 317 info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(), 318 info.getVendorId(), info.getDisplayName(), newPowerStatus); 319 } 320 } 321