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     /* package */ static class TimeUnit {
191         /* package */ final int mHour;
192         /* package */ final int mMinute;
193 
TimeUnit(int hour, int minute)194         /* package */ TimeUnit(int hour, int minute) {
195             mHour = hour;
196             mMinute = minute;
197         }
198 
toByteArray(byte[] data, int index)199         /* package */ int toByteArray(byte[] data, int index) {
200             data[index] = toBcdByte(mHour);
201             data[index + 1] = toBcdByte(mMinute);
202             return 2;
203         }
204 
toBcdByte(int value)205         /* package */ static byte toBcdByte(int value) {
206             int digitOfTen = (value / 10) % 10;
207             int digitOfOne = value % 10;
208             return (byte) ((digitOfTen << 4) | digitOfOne);
209         }
210     }
211 
212     /**
213      * Place holder for time value.
214      * @hide
215      */
216     @SystemApi
217     public static final class Time extends TimeUnit {
Time(int hour, int minute)218         private Time(int hour, int minute) {
219             super(hour, minute);
220         }
221     }
222 
223     /**
224      * Place holder for duration value.
225      * @hide
226      */
227     @SystemApi
228     public static final class Duration extends TimeUnit {
Duration(int hour, int minute)229         private Duration(int hour, int minute) {
230             super(hour, minute);
231         }
232     }
233 
234     /**
235      * Fields for recording sequence.
236      * The following can be merged by OR(|) operation.
237      */
238     public static final int RECORDING_SEQUENCE_REPEAT_ONCE_ONLY = 0;
239     public static final int RECORDING_SEQUENCE_REPEAT_SUNDAY = 1 << 0;
240     public static final int RECORDING_SEQUENCE_REPEAT_MONDAY = 1 << 1;
241     public static final int RECORDING_SEQUENCE_REPEAT_TUESDAY = 1 << 2;
242     public static final int RECORDING_SEQUENCE_REPEAT_WEDNESDAY = 1 << 3;
243     public static final int RECORDING_SEQUENCE_REPEAT_THURSDAY = 1 << 4;
244     public static final int RECORDING_SEQUENCE_REPEAT_FRIDAY = 1 << 5;
245     public static final int RECORDING_SEQUENCE_REPEAT_SATUREDAY = 1 << 6;
246 
247     private static final int RECORDING_SEQUENCE_REPEAT_MASK =
248             (RECORDING_SEQUENCE_REPEAT_SUNDAY | RECORDING_SEQUENCE_REPEAT_MONDAY |
249             RECORDING_SEQUENCE_REPEAT_TUESDAY | RECORDING_SEQUENCE_REPEAT_WEDNESDAY |
250             RECORDING_SEQUENCE_REPEAT_THURSDAY | RECORDING_SEQUENCE_REPEAT_FRIDAY |
251             RECORDING_SEQUENCE_REPEAT_SATUREDAY);
252 
253     /**
254      * Creates {@link TimerInfo} with the given information.
255      *
256      * @param dayOfMonth day of month
257      * @param monthOfYear month of year
258      * @param startTime start time in {@link Time}
259      * @param duration duration in {@link Duration}
260      * @param recordingSequence recording sequence. Use RECORDING_SEQUENCE_REPEAT_ONCE_ONLY for no
261      *            repeat. Otherwise use combination of {@link #RECORDING_SEQUENCE_REPEAT_SUNDAY},
262      *            {@link #RECORDING_SEQUENCE_REPEAT_MONDAY},
263      *            {@link #RECORDING_SEQUENCE_REPEAT_TUESDAY},
264      *            {@link #RECORDING_SEQUENCE_REPEAT_WEDNESDAY},
265      *            {@link #RECORDING_SEQUENCE_REPEAT_THURSDAY},
266      *            {@link #RECORDING_SEQUENCE_REPEAT_FRIDAY},
267      *            {@link #RECORDING_SEQUENCE_REPEAT_SATUREDAY}.
268      * @return {@link TimerInfo}.
269      * @throws IllegalArgumentException if input value is invalid
270      */
timerInfoOf(int dayOfMonth, int monthOfYear, Time startTime, Duration duration, int recordingSequence)271     public static TimerInfo timerInfoOf(int dayOfMonth, int monthOfYear, Time startTime,
272             Duration duration, int recordingSequence) {
273         if (dayOfMonth < 0 || dayOfMonth > 31) {
274             throw new IllegalArgumentException(
275                     "Day of month should be in range of [0, 31]:" + dayOfMonth);
276         }
277         if (monthOfYear < 1 || monthOfYear > 12) {
278             throw new IllegalArgumentException(
279                     "Month of year should be in range of [1, 12]:" + monthOfYear);
280         }
281         checkTimeValue(startTime.mHour, startTime.mMinute);
282         checkDurationValue(duration.mHour, duration.mMinute);
283         // Recording sequence should use least 7 bits or no bits.
284         if ((recordingSequence != 0)
285                 && ((recordingSequence & ~RECORDING_SEQUENCE_REPEAT_MASK) != 0)) {
286             throw new IllegalArgumentException(
287                     "Invalid reecording sequence value:" + recordingSequence);
288         }
289 
290         return new TimerInfo(dayOfMonth, monthOfYear, startTime, duration, recordingSequence);
291     }
292 
293     /**
294      * Container basic timer information. It consists of the following fields.
295      * <ul>
296      * <li>[Day of Month]
297      * <li>[Month of Year]
298      * <li>[Start Time]
299      * <li>[Duration]
300      * <li>[Recording Sequence]
301      * </ul>
302      * @hide
303      */
304     @SystemApi
305     public static final class TimerInfo {
306         private static final int DAY_OF_MONTH_SIZE = 1;
307         private static final int MONTH_OF_YEAR_SIZE = 1;
308         private static final int START_TIME_SIZE = 2; // 1byte for hour and 1byte for minute.
309         private static final int DURATION_SIZE = 2; // 1byte for hour and 1byte for minute.
310         private static final int RECORDING_SEQUENCE_SIZE = 1;
311         private static final int BASIC_INFO_SIZE = DAY_OF_MONTH_SIZE + MONTH_OF_YEAR_SIZE
312                 + START_TIME_SIZE + DURATION_SIZE + RECORDING_SEQUENCE_SIZE;
313 
314         /** Day of month. */
315         private final int mDayOfMonth;
316         /** Month of year. */
317         private final int mMonthOfYear;
318         /**
319          * Time of day.
320          * [Hour][Minute]. 0 &lt;= Hour &lt;= 24, 0 &lt;= Minute &lt;= 60 in BCD format.
321          */
322         private final Time mStartTime;
323         /**
324          * Duration. [Hour][Minute].
325          * 0 &lt;= Hour &lt;= 99, 0 &lt;= Minute &lt;= 60 in BCD format.
326          * */
327         private final Duration mDuration;
328         /**
329          * Indicates if recording is repeated and, if so, on which days. For repeated recordings,
330          * the recording sequence value is the bitwise OR of the days when recordings are required.
331          * [Recording Sequence] shall be set to 0x00 when the recording is not repeated. Bit 7 is
332          * reserved and shall be set to zero.
333          */
334         private final int mRecordingSequence;
335 
TimerInfo(int dayOfMonth, int monthOfYear, Time startTime, Duration duration, int recordingSequence)336         private TimerInfo(int dayOfMonth, int monthOfYear, Time startTime,
337                 Duration duration, int recordingSequence) {
338             mDayOfMonth = dayOfMonth;
339             mMonthOfYear = monthOfYear;
340             mStartTime = startTime;
341             mDuration = duration;
342             mRecordingSequence = recordingSequence;
343         }
344 
toByteArray(byte[] data, int index)345         int toByteArray(byte[] data, int index) {
346             // [Day of Month]
347             data[index] = (byte) mDayOfMonth;
348             index += DAY_OF_MONTH_SIZE;
349             // [Month of Year]
350             data[index] = (byte) mMonthOfYear;
351             index += MONTH_OF_YEAR_SIZE;
352             // [Start Time]
353             index += mStartTime.toByteArray(data, index);
354             index += mDuration.toByteArray(data, index);
355             // [Duration]
356             // [Recording Sequence]
357             data[index] = (byte) mRecordingSequence;
358             return getDataSize();
359         }
360 
getDataSize()361         int getDataSize() {
362             return BASIC_INFO_SIZE;
363         }
364     }
365 
366     /**
367      * Record source container for timer record. This is used to set parameter for &lt;Set Digital
368      * Timer&gt;, &lt;Set Analogue Timer&gt;, and &lt;Set External Timer&gt; message.
369      * <p>
370      * In order to create this from each source type, use one of helper method.
371      * <ul>
372      * <li>{@link #ofDigitalSource} for digital source
373      * <li>{@link #ofAnalogueSource} for analogue source
374      * <li>{@link #ofExternalPlug} for external plug type
375      * <li>{@link #ofExternalPhysicalAddress} for external physical address type.
376      * </ul>
377      * @hide
378      */
379     @SystemApi
380     public static final class TimerRecordSource {
381         private final RecordSource mRecordSource;
382         private final TimerInfo mTimerInfo;
383 
TimerRecordSource(TimerInfo timerInfo, RecordSource recordSource)384         private TimerRecordSource(TimerInfo timerInfo, RecordSource recordSource) {
385             mTimerInfo = timerInfo;
386             mRecordSource = recordSource;
387         }
388 
getDataSize()389         int getDataSize() {
390             return mTimerInfo.getDataSize() + mRecordSource.getDataSize(false);
391         }
392 
toByteArray(byte[] data, int index)393         int toByteArray(byte[] data, int index) {
394             // Basic infos including [Day of Month] [Month of Year] [Start Time] [Duration]
395             // [Recording Sequence]
396             index += mTimerInfo.toByteArray(data, index);
397             // [Record Source]
398             mRecordSource.toByteArray(false, data, index);
399             return getDataSize();
400         }
401     }
402 
403     /**
404      * External source specifier types.
405      */
406     private static final int EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PLUG = 4;
407     private static final int EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PHYSICAL_ADDRESS = 5;
408 
409     /**
410      * Decorator for external source. Beside digital or analogue source, external source starts with
411      * [External Source Specifier] because it covers both external plug type and external specifier.
412      */
413     private static class ExternalSourceDecorator extends RecordSource {
414         private final RecordSource mRecordSource;
415         private final int mExternalSourceSpecifier;
416 
ExternalSourceDecorator(RecordSource recordSource, int externalSourceSpecifier)417         private ExternalSourceDecorator(RecordSource recordSource, int externalSourceSpecifier) {
418             // External source has one byte field for [External Source Specifier].
419             super(recordSource.mSourceType, recordSource.getDataSize(false) + 1);
420             mRecordSource = recordSource;
421             mExternalSourceSpecifier = externalSourceSpecifier;
422         }
423 
424         @Override
extraParamToByteArray(byte[] data, int index)425         int extraParamToByteArray(byte[] data, int index) {
426             data[index] = (byte) mExternalSourceSpecifier;
427             mRecordSource.toByteArray(false, data, index + 1);
428             return getDataSize(false);
429         }
430     }
431 
432     /**
433      * Checks the byte array of timer record source.
434      * @param sourcetype
435      * @param recordSource
436      * @hide
437      */
438     @SystemApi
checkTimerRecordSource(int sourcetype, byte[] recordSource)439     public static boolean checkTimerRecordSource(int sourcetype, byte[] recordSource) {
440         int recordSourceSize = recordSource.length - TimerInfo.BASIC_INFO_SIZE;
441         switch (sourcetype) {
442             case TIMER_RECORDING_TYPE_DIGITAL:
443                 return DigitalServiceSource.EXTRA_DATA_SIZE == recordSourceSize;
444             case TIMER_RECORDING_TYPE_ANALOGUE:
445                 return AnalogueServiceSource.EXTRA_DATA_SIZE == recordSourceSize;
446             case TIMER_RECORDING_TYPE_EXTERNAL:
447                 int specifier = recordSource[TimerInfo.BASIC_INFO_SIZE];
448                 if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PLUG) {
449                     // One byte for specifier.
450                     return ExternalPlugData.EXTRA_DATA_SIZE + 1 == recordSourceSize;
451                 } else if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PHYSICAL_ADDRESS) {
452                     // One byte for specifier.
453                     return ExternalPhysicalAddress.EXTRA_DATA_SIZE + 1 == recordSourceSize;
454                 } else {
455                     // Invalid specifier.
456                     return false;
457                 }
458             default:
459                 return false;
460         }
461     }
462 }
463