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 android.hardware.hdmi;
18 
19 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
20 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
21 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
22 
23 import android.annotation.SystemApi;
24 import android.hardware.hdmi.HdmiRecordSources.AnalogueServiceSource;
25 import android.hardware.hdmi.HdmiRecordSources.DigitalServiceSource;
26 import android.hardware.hdmi.HdmiRecordSources.ExternalPhysicalAddress;
27 import android.hardware.hdmi.HdmiRecordSources.ExternalPlugData;
28 import android.hardware.hdmi.HdmiRecordSources.RecordSource;
29 import android.util.Log;
30 
31 /**
32  * Container for timer record source used for timer recording. Timer source consists of two parts,
33  * timer info and record source.
34  * <p>
35  * Timer info contains all timing information used for recording. It consists of the following
36  * values.
37  * <ul>
38  * <li>[Day of Month]
39  * <li>[Month of Year]
40  * <li>[Start Time]
41  * <li>[Duration]
42  * <li>[Recording Sequence]
43  * </ul>
44  * <p>
45  * Record source containers all program information used for recording.
46  * For more details, look at {@link HdmiRecordSources}.
47  * <p>
48  * Usage
49  * <pre>
50  * TimeOrDuration startTime = HdmiTimerRecordSources.ofTime(18, 00);  // 6PM.
51  * TimeOrDuration duration = HdmiTimerRecordSource.ofDuration(1, 00);  // 1 hour duration.
52  * // For 1 hour from 6PM, August 10th every SaturDay and Sunday.
53  * TimerInfo timerInfo = HdmiTimerRecordSource.timerInfoOf(10, 8, starTime, duration,
54  *            HdmiTimerRecordSource.RECORDING_SEQUENCE_REPEAT_SATURDAY |
55  *            HdmiTimerRecordSource.RECORDING_SEQUENCE_REPEAT_SUNDAY);
56  * // create digital source.
57  * DigitalServiceSource recordSource = HdmiRecordSource.ofDvb(...);
58  * TimerRecordSource source = ofDigitalSource(timerInfo, recordSource);
59  * </pre>
60  *
61  * @hide
62  */
63 @SystemApi
64 public class HdmiTimerRecordSources {
65     private static final String TAG = "HdmiTimerRecordingSources";
66 
HdmiTimerRecordSources()67     private HdmiTimerRecordSources() {}
68 
69     /**
70      * Creates {@link TimerRecordSource} for digital source which is used for &lt;Set Digital
71      * Timer&gt;.
72      *
73      * @param timerInfo timer info used for timer recording
74      * @param source digital source used for timer recording
75      * @return {@link TimerRecordSource}
76      * @throws IllegalArgumentException if {@code timerInfo} or {@code source} is null
77      */
ofDigitalSource(TimerInfo timerInfo, DigitalServiceSource source)78     public static TimerRecordSource ofDigitalSource(TimerInfo timerInfo,
79             DigitalServiceSource source) {
80         checkTimerRecordSourceInputs(timerInfo, source);
81         return new TimerRecordSource(timerInfo, source);
82     }
83 
84     /**
85      * Creates {@link TimerRecordSource} for analogue source which is used for &lt;Set Analogue
86      * Timer&gt;.
87      *
88      * @param timerInfo timer info used for timer recording
89      * @param source digital source used for timer recording
90      * @return {@link TimerRecordSource}
91      * @throws IllegalArgumentException if {@code timerInfo} or {@code source} is null
92      */
ofAnalogueSource(TimerInfo timerInfo, AnalogueServiceSource source)93     public static TimerRecordSource ofAnalogueSource(TimerInfo timerInfo,
94             AnalogueServiceSource source) {
95         checkTimerRecordSourceInputs(timerInfo, source);
96         return new TimerRecordSource(timerInfo, source);
97     }
98 
99     /**
100      * Creates {@link TimerRecordSource} for external plug which is used for &lt;Set External
101      * Timer&gt;.
102      *
103      * @param timerInfo timer info used for timer recording
104      * @param source digital source used for timer recording
105      * @return {@link TimerRecordSource}
106      * @throws IllegalArgumentException if {@code timerInfo} or {@code source} is null
107      */
ofExternalPlug(TimerInfo timerInfo, ExternalPlugData source)108     public static TimerRecordSource ofExternalPlug(TimerInfo timerInfo, ExternalPlugData source) {
109         checkTimerRecordSourceInputs(timerInfo, source);
110         return new TimerRecordSource(timerInfo,
111                 new ExternalSourceDecorator(source, EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PLUG));
112     }
113 
114     /**
115      * Creates {@link TimerRecordSource} for external physical address which is used for &lt;Set
116      * External Timer&gt;.
117      *
118      * @param timerInfo timer info used for timer recording
119      * @param source digital source used for timer recording
120      * @return {@link TimerRecordSource}
121      * @throws IllegalArgumentException if {@code timerInfo} or {@code source} is null
122      */
ofExternalPhysicalAddress(TimerInfo timerInfo, ExternalPhysicalAddress source)123     public static TimerRecordSource ofExternalPhysicalAddress(TimerInfo timerInfo,
124             ExternalPhysicalAddress source) {
125         checkTimerRecordSourceInputs(timerInfo, source);
126         return new TimerRecordSource(timerInfo,
127                 new ExternalSourceDecorator(source,
128                         EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PHYSICAL_ADDRESS));
129     }
130 
checkTimerRecordSourceInputs(TimerInfo timerInfo, RecordSource source)131     private static void checkTimerRecordSourceInputs(TimerInfo timerInfo, RecordSource source) {
132         if (timerInfo == null) {
133             Log.w(TAG, "TimerInfo should not be null.");
134             throw new IllegalArgumentException("TimerInfo should not be null.");
135         }
136         if (source == null) {
137             Log.w(TAG, "source should not be null.");
138             throw new IllegalArgumentException("source should not be null.");
139         }
140     }
141 
142     /**
143      * Creates {@link Duration} for time value.
144      *
145      * @param hour hour in range of [0, 23]
146      * @param minute minute in range of [0, 60]
147      * @return {@link Duration}
148      * @throws IllegalArgumentException if hour or minute is out of range
149      */
timeOf(int hour, int minute)150     public static Time timeOf(int hour, int minute) {
151         checkTimeValue(hour, minute);
152         return new Time(hour, minute);
153     }
154 
checkTimeValue(int hour, int minute)155     private static void checkTimeValue(int hour, int minute) {
156         if (hour < 0 || hour > 23) {
157             throw new IllegalArgumentException("Hour should be in rage of [0, 23]:" + hour);
158         }
159         if (minute < 0 || minute > 59) {
160             throw new IllegalArgumentException("Minute should be in rage of [0, 59]:" + minute);
161         }
162     }
163 
164     /**
165      * Creates {@link Duration} for duration value.
166      *
167      * @param hour hour in range of [0, 99]
168      * @param minute minute in range of [0, 59]
169      * @return {@link Duration}
170      * @throws IllegalArgumentException if hour or minute is out of range
171      */
durationOf(int hour, int minute)172     public static Duration durationOf(int hour, int minute) {
173         checkDurationValue(hour, minute);
174         return new Duration(hour, minute);
175     }
176 
checkDurationValue(int hour, int minute)177     private static void checkDurationValue(int hour, int minute) {
178         if (hour < 0 || hour > 99) {
179             throw new IllegalArgumentException("Hour should be in rage of [0, 99]:" + hour);
180         }
181         if (minute < 0 || minute > 59) {
182             throw new IllegalArgumentException("minute should be in rage of [0, 59]:" + minute);
183         }
184     }
185 
186     /**
187      * Base class for time-related information.
188      * @hide
189      */
190     @SystemApi
191     /* package */ static class TimeUnit {
192         /* package */ final int mHour;
193         /* package */ final int mMinute;
194 
TimeUnit(int hour, int minute)195         /* package */ TimeUnit(int hour, int minute) {
196             mHour = hour;
197             mMinute = minute;
198         }
199 
toByteArray(byte[] data, int index)200         /* package */ int toByteArray(byte[] data, int index) {
201             data[index] = toBcdByte(mHour);
202             data[index + 1] = toBcdByte(mMinute);
203             return 2;
204         }
205 
toBcdByte(int value)206         /* package */ static byte toBcdByte(int value) {
207             int digitOfTen = (value / 10) % 10;
208             int digitOfOne = value % 10;
209             return (byte) ((digitOfTen << 4) | digitOfOne);
210         }
211     }
212 
213     /**
214      * Place holder for time value.
215      * @hide
216      */
217     @SystemApi
218     public static final class Time extends TimeUnit {
Time(int hour, int minute)219         private Time(int hour, int minute) {
220             super(hour, minute);
221         }
222     }
223 
224     /**
225      * Place holder for duration value.
226      * @hide
227      */
228     @SystemApi
229     public static final class Duration extends TimeUnit {
Duration(int hour, int minute)230         private Duration(int hour, int minute) {
231             super(hour, minute);
232         }
233     }
234 
235     /**
236      * Fields for recording sequence.
237      * The following can be merged by OR(|) operation.
238      */
239     public static final int RECORDING_SEQUENCE_REPEAT_ONCE_ONLY = 0;
240     public static final int RECORDING_SEQUENCE_REPEAT_SUNDAY = 1 << 0;
241     public static final int RECORDING_SEQUENCE_REPEAT_MONDAY = 1 << 1;
242     public static final int RECORDING_SEQUENCE_REPEAT_TUESDAY = 1 << 2;
243     public static final int RECORDING_SEQUENCE_REPEAT_WEDNESDAY = 1 << 3;
244     public static final int RECORDING_SEQUENCE_REPEAT_THURSDAY = 1 << 4;
245     public static final int RECORDING_SEQUENCE_REPEAT_FRIDAY = 1 << 5;
246     public static final int RECORDING_SEQUENCE_REPEAT_SATUREDAY = 1 << 6;
247 
248     private static final int RECORDING_SEQUENCE_REPEAT_MASK =
249             (RECORDING_SEQUENCE_REPEAT_SUNDAY | RECORDING_SEQUENCE_REPEAT_MONDAY |
250             RECORDING_SEQUENCE_REPEAT_TUESDAY | RECORDING_SEQUENCE_REPEAT_WEDNESDAY |
251             RECORDING_SEQUENCE_REPEAT_THURSDAY | RECORDING_SEQUENCE_REPEAT_FRIDAY |
252             RECORDING_SEQUENCE_REPEAT_SATUREDAY);
253 
254     /**
255      * Creates {@link TimerInfo} with the given information.
256      *
257      * @param dayOfMonth day of month
258      * @param monthOfYear month of year
259      * @param startTime start time in {@link Time}
260      * @param duration duration in {@link Duration}
261      * @param recordingSequence recording sequence. Use RECORDING_SEQUENCE_REPEAT_ONCE_ONLY for no
262      *            repeat. Otherwise use combination of {@link #RECORDING_SEQUENCE_REPEAT_SUNDAY},
263      *            {@link #RECORDING_SEQUENCE_REPEAT_MONDAY},
264      *            {@link #RECORDING_SEQUENCE_REPEAT_TUESDAY},
265      *            {@link #RECORDING_SEQUENCE_REPEAT_WEDNESDAY},
266      *            {@link #RECORDING_SEQUENCE_REPEAT_THURSDAY},
267      *            {@link #RECORDING_SEQUENCE_REPEAT_FRIDAY},
268      *            {@link #RECORDING_SEQUENCE_REPEAT_SATUREDAY}.
269      * @return {@link TimerInfo}.
270      * @throws IllegalArgumentException if input value is invalid
271      */
timerInfoOf(int dayOfMonth, int monthOfYear, Time startTime, Duration duration, int recordingSequence)272     public static TimerInfo timerInfoOf(int dayOfMonth, int monthOfYear, Time startTime,
273             Duration duration, int recordingSequence) {
274         if (dayOfMonth < 0 || dayOfMonth > 31) {
275             throw new IllegalArgumentException(
276                     "Day of month should be in range of [0, 31]:" + dayOfMonth);
277         }
278         if (monthOfYear < 1 || monthOfYear > 12) {
279             throw new IllegalArgumentException(
280                     "Month of year should be in range of [1, 12]:" + monthOfYear);
281         }
282         checkTimeValue(startTime.mHour, startTime.mMinute);
283         checkDurationValue(duration.mHour, duration.mMinute);
284         // Recording sequence should use least 7 bits or no bits.
285         if ((recordingSequence != 0)
286                 && ((recordingSequence & ~RECORDING_SEQUENCE_REPEAT_MASK) != 0)) {
287             throw new IllegalArgumentException(
288                     "Invalid reecording sequence value:" + recordingSequence);
289         }
290 
291         return new TimerInfo(dayOfMonth, monthOfYear, startTime, duration, recordingSequence);
292     }
293 
294     /**
295      * Container basic timer information. It consists of the following fields.
296      * <ul>
297      * <li>[Day of Month]
298      * <li>[Month of Year]
299      * <li>[Start Time]
300      * <li>[Duration]
301      * <li>[Recording Sequence]
302      * </ul>
303      * @hide
304      */
305     @SystemApi
306     public static final class TimerInfo {
307         private static final int DAY_OF_MONTH_SIZE = 1;
308         private static final int MONTH_OF_YEAR_SIZE = 1;
309         private static final int START_TIME_SIZE = 2; // 1byte for hour and 1byte for minute.
310         private static final int DURATION_SIZE = 2; // 1byte for hour and 1byte for minute.
311         private static final int RECORDING_SEQUENCE_SIZE = 1;
312         private static final int BASIC_INFO_SIZE = DAY_OF_MONTH_SIZE + MONTH_OF_YEAR_SIZE
313                 + START_TIME_SIZE + DURATION_SIZE + RECORDING_SEQUENCE_SIZE;
314 
315         /** Day of month. */
316         private final int mDayOfMonth;
317         /** Month of year. */
318         private final int mMonthOfYear;
319         /**
320          * Time of day.
321          * [Hour][Minute]. 0 &lt;= Hour &lt;= 24, 0 &lt;= Minute &lt;= 60 in BCD format.
322          */
323         private final Time mStartTime;
324         /**
325          * Duration. [Hour][Minute].
326          * 0 &lt;= Hour &lt;= 99, 0 &lt;= Minute &lt;= 60 in BCD format.
327          * */
328         private final Duration mDuration;
329         /**
330          * Indicates if recording is repeated and, if so, on which days. For repeated recordings,
331          * the recording sequence value is the bitwise OR of the days when recordings are required.
332          * [Recording Sequence] shall be set to 0x00 when the recording is not repeated. Bit 7 is
333          * reserved and shall be set to zero.
334          */
335         private final int mRecordingSequence;
336 
TimerInfo(int dayOfMonth, int monthOfYear, Time startTime, Duration duration, int recordingSequence)337         private TimerInfo(int dayOfMonth, int monthOfYear, Time startTime,
338                 Duration duration, int recordingSequence) {
339             mDayOfMonth = dayOfMonth;
340             mMonthOfYear = monthOfYear;
341             mStartTime = startTime;
342             mDuration = duration;
343             mRecordingSequence = recordingSequence;
344         }
345 
toByteArray(byte[] data, int index)346         int toByteArray(byte[] data, int index) {
347             // [Day of Month]
348             data[index] = (byte) mDayOfMonth;
349             index += DAY_OF_MONTH_SIZE;
350             // [Month of Year]
351             data[index] = (byte) mMonthOfYear;
352             index += MONTH_OF_YEAR_SIZE;
353             // [Start Time]
354             index += mStartTime.toByteArray(data, index);
355             index += mDuration.toByteArray(data, index);
356             // [Duration]
357             // [Recording Sequence]
358             data[index] = (byte) mRecordingSequence;
359             return getDataSize();
360         }
361 
getDataSize()362         int getDataSize() {
363             return BASIC_INFO_SIZE;
364         }
365     }
366 
367     /**
368      * Record source container for timer record. This is used to set parameter for &lt;Set Digital
369      * Timer&gt;, &lt;Set Analogue Timer&gt;, and &lt;Set External Timer&gt; message.
370      * <p>
371      * In order to create this from each source type, use one of helper method.
372      * <ul>
373      * <li>{@link #ofDigitalSource} for digital source
374      * <li>{@link #ofAnalogueSource} for analogue source
375      * <li>{@link #ofExternalPlug} for external plug type
376      * <li>{@link #ofExternalPhysicalAddress} for external physical address type.
377      * </ul>
378      * @hide
379      */
380     @SystemApi
381     public static final class TimerRecordSource {
382         private final RecordSource mRecordSource;
383         private final TimerInfo mTimerInfo;
384 
TimerRecordSource(TimerInfo timerInfo, RecordSource recordSource)385         private TimerRecordSource(TimerInfo timerInfo, RecordSource recordSource) {
386             mTimerInfo = timerInfo;
387             mRecordSource = recordSource;
388         }
389 
getDataSize()390         int getDataSize() {
391             return mTimerInfo.getDataSize() + mRecordSource.getDataSize(false);
392         }
393 
toByteArray(byte[] data, int index)394         int toByteArray(byte[] data, int index) {
395             // Basic infos including [Day of Month] [Month of Year] [Start Time] [Duration]
396             // [Recording Sequence]
397             index += mTimerInfo.toByteArray(data, index);
398             // [Record Source]
399             mRecordSource.toByteArray(false, data, index);
400             return getDataSize();
401         }
402     }
403 
404     /**
405      * External source specifier types.
406      */
407     private static final int EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PLUG = 4;
408     private static final int EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PHYSICAL_ADDRESS = 5;
409 
410     /**
411      * Decorator for external source. Beside digital or analogue source, external source starts with
412      * [External Source Specifier] because it covers both external plug type and external specifier.
413      */
414     private static class ExternalSourceDecorator extends RecordSource {
415         private final RecordSource mRecordSource;
416         private final int mExternalSourceSpecifier;
417 
ExternalSourceDecorator(RecordSource recordSource, int externalSourceSpecifier)418         private ExternalSourceDecorator(RecordSource recordSource, int externalSourceSpecifier) {
419             // External source has one byte field for [External Source Specifier].
420             super(recordSource.mSourceType, recordSource.getDataSize(false) + 1);
421             mRecordSource = recordSource;
422             mExternalSourceSpecifier = externalSourceSpecifier;
423         }
424 
425         @Override
extraParamToByteArray(byte[] data, int index)426         int extraParamToByteArray(byte[] data, int index) {
427             data[index] = (byte) mExternalSourceSpecifier;
428             mRecordSource.toByteArray(false, data, index + 1);
429             return getDataSize(false);
430         }
431     }
432 
433     /**
434      * Checks the byte array of timer record source.
435      * @param sourcetype
436      * @param recordSource
437      * @hide
438      */
439     @SystemApi
checkTimerRecordSource(int sourcetype, byte[] recordSource)440     public static boolean checkTimerRecordSource(int sourcetype, byte[] recordSource) {
441         int recordSourceSize = recordSource.length - TimerInfo.BASIC_INFO_SIZE;
442         switch (sourcetype) {
443             case TIMER_RECORDING_TYPE_DIGITAL:
444                 return DigitalServiceSource.EXTRA_DATA_SIZE == recordSourceSize;
445             case TIMER_RECORDING_TYPE_ANALOGUE:
446                 return AnalogueServiceSource.EXTRA_DATA_SIZE == recordSourceSize;
447             case TIMER_RECORDING_TYPE_EXTERNAL:
448                 int specifier = recordSource[TimerInfo.BASIC_INFO_SIZE];
449                 if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PLUG) {
450                     // One byte for specifier.
451                     return ExternalPlugData.EXTRA_DATA_SIZE + 1 == recordSourceSize;
452                 } else if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PHYSICAL_ADDRESS) {
453                     // One byte for specifier.
454                     return ExternalPhysicalAddress.EXTRA_DATA_SIZE + 1 == recordSourceSize;
455                 } else {
456                     // Invalid specifier.
457                     return false;
458                 }
459             default:
460                 return false;
461         }
462     }
463 }
464