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 java.io.UnsupportedEncodingException;
20import java.util.Arrays;
21
22/**
23 * A helper class to build {@link HdmiCecMessage} from various cec commands.
24 */
25public class HdmiCecMessageBuilder {
26    private static final int OSD_NAME_MAX_LENGTH = 13;
27
28    private HdmiCecMessageBuilder() {}
29
30    /**
31     * Build {@link HdmiCecMessage} from raw data.
32     *
33     * @param src source address of command
34     * @param dest destination address of command
35     * @param body body of message. It includes opcode.
36     * @return newly created {@link HdmiCecMessage}
37     */
38    static HdmiCecMessage of(int src, int dest, byte[] body) {
39        byte opcode = body[0];
40        byte params[] = Arrays.copyOfRange(body, 1, body.length);
41        return new HdmiCecMessage(src, dest, opcode, params);
42    }
43
44    /**
45     * Build <Feature Abort> command. <Feature Abort> consists of
46     * 1 byte original opcode and 1 byte reason fields with basic fields.
47     *
48     * @param src source address of command
49     * @param dest destination address of command
50     * @param originalOpcode original opcode causing feature abort
51     * @param reason reason of feature abort
52     * @return newly created {@link HdmiCecMessage}
53     */
54    static HdmiCecMessage buildFeatureAbortCommand(int src, int dest, int originalOpcode,
55            int reason) {
56        byte[] params = new byte[] {
57                (byte) (originalOpcode & 0xFF),
58                (byte) (reason & 0xFF),
59        };
60        return buildCommand(src, dest, Constants.MESSAGE_FEATURE_ABORT, params);
61    }
62
63    /**
64     * Build <Give Physical Address> command.
65     *
66     * @param src source address of command
67     * @param dest destination address of command
68     * @return newly created {@link HdmiCecMessage}
69     */
70    static HdmiCecMessage buildGivePhysicalAddress(int src, int dest) {
71        return buildCommand(src, dest, Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS);
72    }
73
74    /**
75     * Build <Give Osd Name> command.
76     *
77     * @param src source address of command
78     * @param dest destination address of command
79     * @return newly created {@link HdmiCecMessage}
80     */
81    static HdmiCecMessage buildGiveOsdNameCommand(int src, int dest) {
82        return buildCommand(src, dest, Constants.MESSAGE_GIVE_OSD_NAME);
83    }
84
85    /**
86     * Build <Give Vendor Id Command> command.
87     *
88     * @param src source address of command
89     * @param dest destination address of command
90     * @return newly created {@link HdmiCecMessage}
91     */
92    static HdmiCecMessage buildGiveDeviceVendorIdCommand(int src, int dest) {
93        return buildCommand(src, dest, Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID);
94    }
95
96    /**
97     * Build <Set Menu Language > command.
98     *
99     * <p>This is a broadcast message sent to all devices on the bus.
100     *
101     * @param src source address of command
102     * @param language 3-letter ISO639-2 based language code
103     * @return newly created {@link HdmiCecMessage} if language is valid.
104     *         Otherwise, return null
105     */
106    static HdmiCecMessage buildSetMenuLanguageCommand(int src, String language) {
107        if (language.length() != 3) {
108            return null;
109        }
110        // Hdmi CEC uses lower-cased ISO 639-2 (3 letters code).
111        String normalized = language.toLowerCase();
112        byte[] params = new byte[] {
113                (byte) (normalized.charAt(0) & 0xFF),
114                (byte) (normalized.charAt(1) & 0xFF),
115                (byte) (normalized.charAt(2) & 0xFF),
116        };
117        // <Set Menu Language> is broadcast message.
118        return buildCommand(src, Constants.ADDR_BROADCAST,
119                Constants.MESSAGE_SET_MENU_LANGUAGE, params);
120    }
121
122    /**
123     * Build &lt;Set Osd Name &gt; command.
124     *
125     * @param src source address of command
126     * @param name display (OSD) name of device
127     * @return newly created {@link HdmiCecMessage} if valid name. Otherwise,
128     *         return null
129     */
130    static HdmiCecMessage buildSetOsdNameCommand(int src, int dest, String name) {
131        int length = Math.min(name.length(), OSD_NAME_MAX_LENGTH);
132        byte[] params;
133        try {
134            params = name.substring(0, length).getBytes("US-ASCII");
135        } catch (UnsupportedEncodingException e) {
136            return null;
137        }
138        return buildCommand(src, dest, Constants.MESSAGE_SET_OSD_NAME, params);
139    }
140
141    /**
142     * Build &lt;Report Physical Address&gt; command. It has two bytes physical
143     * address and one byte device type as parameter.
144     *
145     * <p>This is a broadcast message sent to all devices on the bus.
146     *
147     * @param src source address of command
148     * @param address physical address of device
149     * @param deviceType type of device
150     * @return newly created {@link HdmiCecMessage}
151     */
152    static HdmiCecMessage buildReportPhysicalAddressCommand(int src, int address, int deviceType) {
153        byte[] params = new byte[] {
154                // Two bytes for physical address
155                (byte) ((address >> 8) & 0xFF),
156                (byte) (address & 0xFF),
157                // One byte device type
158                (byte) (deviceType & 0xFF)
159        };
160        // <Report Physical Address> is broadcast message.
161        return buildCommand(src, Constants.ADDR_BROADCAST,
162                Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS, params);
163    }
164
165    /**
166     * Build &lt;Device Vendor Id&gt; command. It has three bytes vendor id as
167     * parameter.
168     *
169     * <p>This is a broadcast message sent to all devices on the bus.
170     *
171     * @param src source address of command
172     * @param vendorId device's vendor id
173     * @return newly created {@link HdmiCecMessage}
174     */
175    static HdmiCecMessage buildDeviceVendorIdCommand(int src, int vendorId) {
176        byte[] params = new byte[] {
177                (byte) ((vendorId >> 16) & 0xFF),
178                (byte) ((vendorId >> 8) & 0xFF),
179                (byte) (vendorId & 0xFF)
180        };
181        // <Device Vendor Id> is broadcast message.
182        return buildCommand(src, Constants.ADDR_BROADCAST,
183                Constants.MESSAGE_DEVICE_VENDOR_ID, params);
184    }
185
186    /**
187     * Build &lt;Device Vendor Id&gt; command. It has one byte cec version as parameter.
188     *
189     * @param src source address of command
190     * @param dest destination address of command
191     * @param version version of cec. Use 0x04 for "Version 1.3a" and 0x05 for
192     *                "Version 1.4 or 1.4a or 1.4b
193     * @return newly created {@link HdmiCecMessage}
194     */
195    static HdmiCecMessage buildCecVersion(int src, int dest, int version) {
196        byte[] params = new byte[] {
197                (byte) (version & 0xFF)
198        };
199        return buildCommand(src, dest, Constants.MESSAGE_CEC_VERSION, params);
200    }
201
202    /**
203     * Build &lt;Request Arc Initiation&gt;
204     *
205     * @param src source address of command
206     * @param dest destination address of command
207     * @return newly created {@link HdmiCecMessage}
208     */
209    static HdmiCecMessage buildRequestArcInitiation(int src, int dest) {
210        return buildCommand(src, dest, Constants.MESSAGE_REQUEST_ARC_INITIATION);
211    }
212
213    /**
214     * Build &lt;Request Arc Termination&gt;
215     *
216     * @param src source address of command
217     * @param dest destination address of command
218     * @return newly created {@link HdmiCecMessage}
219     */
220    static HdmiCecMessage buildRequestArcTermination(int src, int dest) {
221        return buildCommand(src, dest, Constants.MESSAGE_REQUEST_ARC_TERMINATION);
222    }
223
224    /**
225     * Build &lt;Report Arc Initiated&gt;
226     *
227     * @param src source address of command
228     * @param dest destination address of command
229     * @return newly created {@link HdmiCecMessage}
230     */
231    static HdmiCecMessage buildReportArcInitiated(int src, int dest) {
232        return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_INITIATED);
233    }
234
235    /**
236     * Build &lt;Report Arc Terminated&gt;
237     *
238     * @param src source address of command
239     * @param dest destination address of command
240     * @return newly created {@link HdmiCecMessage}
241     */
242    static HdmiCecMessage buildReportArcTerminated(int src, int dest) {
243        return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_TERMINATED);
244    }
245
246    /**
247     * Build &lt;Text View On&gt; command.
248     *
249     * @param src source address of command
250     * @param dest destination address of command
251     * @return newly created {@link HdmiCecMessage}
252     */
253    static HdmiCecMessage buildTextViewOn(int src, int dest) {
254        return buildCommand(src, dest, Constants.MESSAGE_TEXT_VIEW_ON);
255    }
256
257    /**
258     * Build &lt;Active Source&gt; command.
259     *
260     * @param src source address of command
261     * @param physicalAddress physical address of the device to become active
262     * @return newly created {@link HdmiCecMessage}
263     */
264    static HdmiCecMessage buildActiveSource(int src, int physicalAddress) {
265        return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ACTIVE_SOURCE,
266                physicalAddressToParam(physicalAddress));
267    }
268
269    /**
270     * Build &lt;Inactive Source&gt; command.
271     *
272     * @param src source address of command
273     * @param physicalAddress physical address of the device to become inactive
274     * @return newly created {@link HdmiCecMessage}
275     */
276    static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) {
277        return buildCommand(src, Constants.ADDR_TV,
278                Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressToParam(physicalAddress));
279    }
280
281    /**
282     * Build &lt;Set Stream Path&gt; command.
283     *
284     * <p>This is a broadcast message sent to all devices on the bus.
285     *
286     * @param src source address of command
287     * @param streamPath physical address of the device to start streaming
288     * @return newly created {@link HdmiCecMessage}
289     */
290    static HdmiCecMessage buildSetStreamPath(int src, int streamPath) {
291        return buildCommand(src, Constants.ADDR_BROADCAST,
292                Constants.MESSAGE_SET_STREAM_PATH, physicalAddressToParam(streamPath));
293    }
294
295    /**
296     * Build &lt;Routing Change&gt; command.
297     *
298     * <p>This is a broadcast message sent to all devices on the bus.
299     *
300     * @param src source address of command
301     * @param oldPath physical address of the currently active routing path
302     * @param newPath physical address of the new active routing path
303     * @return newly created {@link HdmiCecMessage}
304     */
305    static HdmiCecMessage buildRoutingChange(int src, int oldPath, int newPath) {
306        byte[] param = new byte[] {
307            (byte) ((oldPath >> 8) & 0xFF), (byte) (oldPath & 0xFF),
308            (byte) ((newPath >> 8) & 0xFF), (byte) (newPath & 0xFF)
309        };
310        return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_CHANGE,
311                param);
312    }
313
314    /**
315     * Build &lt;Give Device Power Status&gt; command.
316     *
317     * @param src source address of command
318     * @param dest destination address of command
319     * @return newly created {@link HdmiCecMessage}
320     */
321    static HdmiCecMessage buildGiveDevicePowerStatus(int src, int dest) {
322        return buildCommand(src, dest, Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS);
323    }
324
325    /**
326     * Build &lt;Report Power Status&gt; command.
327     *
328     * @param src source address of command
329     * @param dest destination address of command
330     * @param powerStatus power status of the device
331     * @return newly created {@link HdmiCecMessage}
332     */
333    static HdmiCecMessage buildReportPowerStatus(int src, int dest, int powerStatus) {
334        byte[] param = new byte[] {
335                (byte) (powerStatus & 0xFF)
336        };
337        return buildCommand(src, dest, Constants.MESSAGE_REPORT_POWER_STATUS, param);
338    }
339
340    /**
341     * Build &lt;Report Menu Status&gt; command.
342     *
343     * @param src source address of command
344     * @param dest destination address of command
345     * @param menuStatus menu status of the device
346     * @return newly created {@link HdmiCecMessage}
347     */
348    static HdmiCecMessage buildReportMenuStatus(int src, int dest, int menuStatus) {
349        byte[] param = new byte[] {
350                (byte) (menuStatus & 0xFF)
351        };
352        return buildCommand(src, dest, Constants.MESSAGE_MENU_STATUS, param);
353    }
354
355    /**
356     * Build &lt;System Audio Mode Request&gt; command.
357     *
358     * @param src source address of command
359     * @param avr destination address of command, it should be AVR
360     * @param avrPhysicalAddress physical address of AVR
361     * @param enableSystemAudio whether to enable System Audio Mode or not
362     * @return newly created {@link HdmiCecMessage}
363     */
364    static HdmiCecMessage buildSystemAudioModeRequest(int src, int avr, int avrPhysicalAddress,
365            boolean enableSystemAudio) {
366        if (enableSystemAudio) {
367            return buildCommand(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
368                    physicalAddressToParam(avrPhysicalAddress));
369        } else {
370            return buildCommand(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST);
371        }
372    }
373
374    /**
375     * Build &lt;Give Audio Status&gt; command.
376     *
377     * @param src source address of command
378     * @param dest destination address of command
379     * @return newly created {@link HdmiCecMessage}
380     */
381    static HdmiCecMessage buildGiveAudioStatus(int src, int dest) {
382        return buildCommand(src, dest, Constants.MESSAGE_GIVE_AUDIO_STATUS);
383    }
384
385    /**
386     * Build &lt;User Control Pressed&gt; command.
387     *
388     * @param src source address of command
389     * @param dest destination address of command
390     * @param uiCommand keycode that user pressed
391     * @return newly created {@link HdmiCecMessage}
392     */
393    static HdmiCecMessage buildUserControlPressed(int src, int dest, int uiCommand) {
394        return buildUserControlPressed(src, dest, new byte[] { (byte) (uiCommand & 0xFF) });
395    }
396
397    /**
398     * Build &lt;User Control Pressed&gt; command.
399     *
400     * @param src source address of command
401     * @param dest destination address of command
402     * @param commandParam uiCommand and the additional parameter
403     * @return newly created {@link HdmiCecMessage}
404     */
405    static HdmiCecMessage buildUserControlPressed(int src, int dest, byte[] commandParam) {
406        return buildCommand(src, dest, Constants.MESSAGE_USER_CONTROL_PRESSED, commandParam);
407    }
408
409    /**
410     * Build &lt;User Control Released&gt; command.
411     *
412     * @param src source address of command
413     * @param dest destination address of command
414     * @return newly created {@link HdmiCecMessage}
415     */
416    static HdmiCecMessage buildUserControlReleased(int src, int dest) {
417        return buildCommand(src, dest, Constants.MESSAGE_USER_CONTROL_RELEASED);
418    }
419
420    /**
421     * Build &lt;Give System Audio Mode Status&gt; command.
422     *
423     * @param src source address of command
424     * @param dest destination address of command
425     * @return newly created {@link HdmiCecMessage}
426     */
427    static HdmiCecMessage buildGiveSystemAudioModeStatus(int src, int dest) {
428        return buildCommand(src, dest, Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS);
429    }
430
431    /**
432     * Build &lt;Standby&gt; command.
433     *
434     * @param src source address of command
435     * @param dest destination address of command
436     * @return newly created {@link HdmiCecMessage}
437     */
438    public static HdmiCecMessage buildStandby(int src, int dest) {
439        return buildCommand(src, dest, Constants.MESSAGE_STANDBY);
440    }
441
442    /**
443     * Build &lt;Vendor Command&gt; command.
444     *
445     * @param src source address of command
446     * @param dest destination address of command
447     * @param params vendor-specific parameters
448     * @return newly created {@link HdmiCecMessage}
449     */
450    static HdmiCecMessage buildVendorCommand(int src, int dest, byte[] params) {
451        return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND, params);
452    }
453
454    /**
455     * Build &lt;Vendor Command With ID&gt; command.
456     *
457     * @param src source address of command
458     * @param dest destination address of command
459     * @param vendorId vendor ID
460     * @param operands vendor-specific parameters
461     * @return newly created {@link HdmiCecMessage}
462     */
463    static HdmiCecMessage buildVendorCommandWithId(int src, int dest, int vendorId,
464            byte[] operands) {
465        byte[] params = new byte[operands.length + 3];  // parameter plus len(vendorId)
466        params[0] = (byte) ((vendorId >> 16) & 0xFF);
467        params[1] = (byte) ((vendorId >> 8) & 0xFF);
468        params[2] = (byte) (vendorId & 0xFF);
469        System.arraycopy(operands, 0, params, 3, operands.length);
470        return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, params);
471    }
472
473    /**
474     * Build &lt;Record On&gt; command.
475     *
476     * @param src source address of command
477     * @param dest destination address of command
478     * @param params parameter of command
479     * @return newly created {@link HdmiCecMessage}
480     */
481    static HdmiCecMessage buildRecordOn(int src, int dest, byte[] params) {
482        return buildCommand(src, dest, Constants.MESSAGE_RECORD_ON, params);
483    }
484
485    /**
486     * Build &lt;Record Off&gt; command.
487     *
488     * @param src source address of command
489     * @param dest destination address of command
490     * @return newly created {@link HdmiCecMessage}
491     */
492    static HdmiCecMessage buildRecordOff(int src, int dest) {
493        return buildCommand(src, dest, Constants.MESSAGE_RECORD_OFF);
494    }
495
496    /**
497     * Build &lt;Set Digital Timer&gt; command.
498     *
499     * @param src source address of command
500     * @param dest destination address of command
501     * @param params byte array of timing information and digital service information to be recorded
502     * @return newly created {@link HdmiCecMessage}
503     */
504    static HdmiCecMessage buildSetDigitalTimer(int src, int dest, byte[] params) {
505        return buildCommand(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params);
506    }
507
508    /**
509     * Build &lt;Set Analogue Timer&gt; command.
510     *
511     * @param src source address of command
512     * @param dest destination address of command
513     * @param params byte array of timing information and analog service information to be recorded
514     * @return newly created {@link HdmiCecMessage}
515     */
516    static HdmiCecMessage buildSetAnalogueTimer(int src, int dest, byte[] params) {
517        return buildCommand(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params);
518    }
519
520    /**
521     * Build &lt;Set External Timer&gt; command.
522     *
523     * @param src source address of command
524     * @param dest destination address of command
525     * @param params byte array of timing information and external source information to be recorded
526     * @return newly created {@link HdmiCecMessage}
527     */
528    static HdmiCecMessage buildSetExternalTimer(int src, int dest, byte[] params) {
529        return buildCommand(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params);
530    }
531
532    /**
533     * Build &lt;Clear Digital Timer&gt; command.
534     *
535     * @param src source address of command
536     * @param dest destination address of command
537     * @param params byte array of timing information and digital service information to be cleared
538     * @return newly created {@link HdmiCecMessage}
539     */
540    static HdmiCecMessage buildClearDigitalTimer(int src, int dest, byte[] params) {
541        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params);
542    }
543
544    /**
545     * Build &lt;Clear Analog Timer&gt; command.
546     *
547     * @param src source address of command
548     * @param dest destination address of command
549     * @param params byte array of timing information and analog service information to be cleared
550     * @return newly created {@link HdmiCecMessage}
551     */
552    static HdmiCecMessage buildClearAnalogueTimer(int src, int dest, byte[] params) {
553        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params);
554    }
555
556    /**
557     * Build &lt;Clear Digital Timer&gt; command.
558     *
559     * @param src source address of command
560     * @param dest destination address of command
561     * @param params byte array of timing information and external source information to be cleared
562     * @return newly created {@link HdmiCecMessage}
563     */
564    static HdmiCecMessage buildClearExternalTimer(int src, int dest, byte[] params) {
565        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params);
566    }
567
568    /***** Please ADD new buildXXX() methods above. ******/
569
570    /**
571     * Build a {@link HdmiCecMessage} without extra parameter.
572     *
573     * @param src source address of command
574     * @param dest destination address of command
575     * @param opcode opcode for a message
576     * @return newly created {@link HdmiCecMessage}
577     */
578    private static HdmiCecMessage buildCommand(int src, int dest, int opcode) {
579        return new HdmiCecMessage(src, dest, opcode, HdmiCecMessage.EMPTY_PARAM);
580    }
581
582    /**
583     * Build a {@link HdmiCecMessage} with given values.
584     *
585     * @param src source address of command
586     * @param dest destination address of command
587     * @param opcode opcode for a message
588     * @param params extra parameters for command
589     * @return newly created {@link HdmiCecMessage}
590     */
591    private static HdmiCecMessage buildCommand(int src, int dest, int opcode, byte[] params) {
592        return new HdmiCecMessage(src, dest, opcode, params);
593    }
594
595    private static byte[] physicalAddressToParam(int physicalAddress) {
596        return new byte[] {
597                (byte) ((physicalAddress >> 8) & 0xFF),
598                (byte) (physicalAddress & 0xFF)
599        };
600    }
601}
602