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
17package com.android.server.hdmi;
18
19import android.hardware.hdmi.HdmiDeviceInfo;
20import android.util.Slog;
21import android.util.SparseArray;
22
23import java.util.ArrayList;
24import java.util.Collections;
25import java.util.List;
26
27/**
28 * Various utilities to handle HDMI CEC messages.
29 */
30final 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     * Convert integer array to list of {@link Integer}.
156     *
157     * <p>The result is immutable.
158     *
159     * @param is integer array
160     * @return {@link List} instance containing the elements in the given array
161     */
162    static List<Integer> asImmutableList(final int[] is) {
163        ArrayList<Integer> list = new ArrayList<>(is.length);
164        for (int type : is) {
165            list.add(type);
166        }
167        return Collections.unmodifiableList(list);
168    }
169
170    /**
171     * Assemble two bytes into single integer value.
172     *
173     * @param data to be assembled
174     * @return assembled value
175     */
176    static int twoBytesToInt(byte[] data) {
177        return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
178    }
179
180    /**
181     * Assemble two bytes into single integer value.
182     *
183     * @param data to be assembled
184     * @param offset offset to the data to convert in the array
185     * @return assembled value
186     */
187    static int twoBytesToInt(byte[] data, int offset) {
188        return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
189    }
190
191    /**
192     * Assemble three bytes into single integer value.
193     *
194     * @param data to be assembled
195     * @return assembled value
196     */
197    static int threeBytesToInt(byte[] data) {
198        return ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
199    }
200
201    static <T> List<T> sparseArrayToList(SparseArray<T> array) {
202        ArrayList<T> list = new ArrayList<>();
203        for (int i = 0; i < array.size(); ++i) {
204            list.add(array.valueAt(i));
205        }
206        return list;
207    }
208
209    static <T> List<T> mergeToUnmodifiableList(List<T> a, List<T> b) {
210        if (a.isEmpty() && b.isEmpty()) {
211            return Collections.emptyList();
212        }
213        if (a.isEmpty()) {
214            return Collections.unmodifiableList(b);
215        }
216        if (b.isEmpty()) {
217            return Collections.unmodifiableList(a);
218        }
219        List<T> newList = new ArrayList<>();
220        newList.addAll(a);
221        newList.addAll(b);
222        return Collections.unmodifiableList(newList);
223    }
224
225    /**
226     * See if the new path is affecting the active path.
227     *
228     * @param activePath current active path
229     * @param newPath new path
230     * @return true if the new path changes the current active path
231     */
232    static boolean isAffectingActiveRoutingPath(int activePath, int newPath) {
233        // The new path affects the current active path if the parent of the new path
234        // is an ancestor of the active path.
235        // (1.1.0.0, 2.0.0.0) -> true, new path alters the parent
236        // (1.1.0.0, 1.2.0.0) -> true, new path is a sibling
237        // (1.1.0.0, 1.2.1.0) -> false, new path is a descendant of a sibling
238        // (1.0.0.0, 3.2.0.0) -> false, in a completely different path
239
240        // Get the parent of the new path by clearing the least significant
241        // non-zero nibble.
242        for (int i = 0; i <= 12; i += 4) {
243            int nibble = (newPath >> i) & 0xF;
244            if (nibble != 0) {
245                int mask = 0xFFF0 << i;
246                newPath &= mask;
247                break;
248            }
249        }
250        if (newPath == 0x0000) {
251            return true;  // Top path always affects the active path
252        }
253        return isInActiveRoutingPath(activePath, newPath);
254    }
255
256    /**
257     * See if the new path is in the active path.
258     *
259     * @param activePath current active path
260     * @param newPath new path
261     * @return true if the new path in the active routing path
262     */
263    static boolean isInActiveRoutingPath(int activePath, int newPath) {
264        // Check each nibble of the currently active path and the new path till the position
265        // where the active nibble is not zero. For (activePath, newPath),
266        // (1.1.0.0, 1.0.0.0) -> true, new path is a parent
267        // (1.2.1.0, 1.2.1.2) -> true, new path is a descendant
268        // (1.1.0.0, 1.2.0.0) -> false, new path is a sibling
269        // (1.0.0.0, 2.0.0.0) -> false, in a completely different path
270        for (int i = 12; i >= 0; i -= 4) {
271            int nibbleActive = (activePath >> i) & 0xF;
272            if (nibbleActive == 0) {
273                break;
274            }
275            int nibbleNew = (newPath >> i) & 0xF;
276            if (nibbleNew == 0) {
277                break;
278            }
279            if (nibbleActive != nibbleNew) {
280                return false;
281            }
282        }
283        return true;
284    }
285
286    /**
287     * Clone {@link HdmiDeviceInfo} with new power status.
288     */
289    static HdmiDeviceInfo cloneHdmiDeviceInfo(HdmiDeviceInfo info, int newPowerStatus) {
290        return new HdmiDeviceInfo(info.getLogicalAddress(),
291                info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(),
292                info.getVendorId(), info.getDisplayName(), newPowerStatus);
293    }
294
295    /**
296     * Convert 3 byte-long language code in string to integer representation.
297     * English(eng), for example, is converted to 0x656e67.
298     *
299     * @param language language code in string
300     * @return language code in integer representation
301     */
302    static int languageToInt(String language) {
303        String normalized = language.toLowerCase();
304        return ((normalized.charAt(0) & 0xFF) << 16)
305                | ((normalized.charAt(1) & 0xFF) << 8)
306                | (normalized.charAt(2) & 0xFF);
307    }
308}
309