Merge "Replace osd message for record status with IHdmiRecordCallback." into lmp-dev
diff --git a/Android.mk b/Android.mk
index 278e67f..0d71150 100644
--- a/Android.mk
+++ b/Android.mk
@@ -155,7 +155,7 @@
 	core/java/android/hardware/hdmi/IHdmiDeviceEventListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiInputChangeListener.aidl \
-	core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl \
+	core/java/android/hardware/hdmi/IHdmiRecordListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiSystemAudioModeChangeListener.aidl \
 	core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl \
 	core/java/android/hardware/input/IInputManager.aidl \
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 521a439..55db1df 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -69,69 +69,71 @@
     public static final int RESULT_INCORRECT_MODE = 6;
     public static final int RESULT_COMMUNICATION_FAILED = 7;
 
-    // -- Message ids for display osd.
-
-    /** Place holder for recording status message. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_STATUS_MESSAGE_START = 0x100;
+    // --- One Touch Recording success result
     /** Recording currently selected source. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x101;
+    public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x01;
     /** Recording Digital Service. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_DIGITAL_SERVICE = 0x102;
+    public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 0x02;
     /** Recording Analogue Service. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_ANALOGUE_SERVICE = 0x103;
+    public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 0x03;
     /** Recording External input. Indicates the status of a recording. */
-    public static final int MESSAGE_RECORDING_EXTERNAL_INPUT = 0x104;
+    public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 0x04;
+
+    // --- One Touch Record failure result
     /** No recording – unable to record Digital Service. No suitable tuner. */
-    public static final int MESSAGE_NO_RECORDNIG_UNABLE_DIGITAL_SERVICE = 0x105;
+    public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 0x05;
     /** No recording – unable to record Analogue Service. No suitable tuner. */
-    public static final int MESSAGE_NO_RECORDNIG_UNABLE_ANALOGUE_SERVICE = 0x106;
+    public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 0x06;
     /**
      * No recording – unable to select required service. as suitable tuner, but the requested
      * parameters are invalid or out of range for that tuner.
      */
-    public static final int MESSAGE_NO_RECORDNIG_UNABLE_SELECTED_SERVICE = 0x107;
+    public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 0x07;
     /** No recording – invalid External plug number */
-    public static final int MESSAGE_NO_RECORDNIG_INVALID_EXTERNAL_PLUG_NUMBER = 0x109;
+    public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 0x09;
     /** No recording – invalid External Physical Address */
-    public static final int MESSAGE_NO_RECORDNIG_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x10A;
+    public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x0A;
     /** No recording – CA system not supported */
-    public static final int MESSAGE_NO_RECORDNIG_UNSUPPORTED_CA = 0x10B;
+    public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 0x0B;
     /** No Recording – No or Insufficient CA Entitlements” */
-    public static final int MESSAGE_NO_RECORDNIG_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x10C;
+    public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x0C;
     /** No recording – Not allowed to copy source. Source is “copy never”. */
-    public static final int MESSAGE_NO_RECORDNIG_DISALLOW_TO_COPY = 0x10D;
+    public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 0x0D;
     /** No recording – No further copies allowed */
-    public static final int MESSAGE_NO_RECORDNIG_DISALLOW_TO_FUTHER_COPIES = 0x10E;
+    public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 0x0E;
     /** No recording – No media */
-    public static final int MESSAGE_NO_RECORDNIG_NO_MEDIA = 0x110;
+    public static final int ONE_TOUCH_RECORD_NO_MEDIA = 0x10;
     /** No recording – playing */
-    public static final int MESSAGE_NO_RECORDNIG_PLAYING = 0x111;
+    public static final int ONE_TOUCH_RECORD_PLAYING = 0x11;
     /** No recording – already recording */
-    public static final int MESSAGE_NO_RECORDNIG_ALREADY_RECORDING = 0x112;
+    public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 0x12;
     /** No recording – media protected */
-    public static final int MESSAGE_NO_RECORDNIG_MEDIA_PROTECTED = 0x113;
+    public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 0x13;
     /** No recording – no source signal */
-    public static final int MESSAGE_NO_RECORDNIG_NO_SOURCE_SIGNAL = 0x114;
+    public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 0x14;
     /** No recording – media problem */
-    public static final int MESSAGE_NO_RECORDNIG_MEDIA_PROBLEM = 0x115;
+    public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 0x15;
     /** No recording – not enough space available */
-    public static final int MESSAGE_NO_RECORDNIG_NOT_ENOUGH_SPACE = 0x116;
+    public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 0x16;
     /** No recording – Parental Lock On */
-    public static final int MESSAGE_NO_RECORDNIG_PARENT_LOCK_ON = 0x117;
+    public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 0x17;
     /** Recording terminated normally */
-    public static final int MESSAGE_RECORDING_TERMINATED_NORMALLY = 0x11A;
+    public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 0x1A;
     /** Recording has already terminated */
-    public static final int MESSAGE_RECORDING_ALREADY_TERMINATED = 0x11B;
+    public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 0x1B;
     /** No recording – other reason */
-    public static final int MESSAGE_NO_RECORDNIG_OTHER_REASON = 0x11F;
+    public static final int ONE_TOUCH_RECORD_OTHER_REASON = 0x1F;
     // From here extra message for recording that is not mentioned in CEC spec
     /** No recording. Previous recording request in progress. */
-    public static final int MESSAGE_NO_RECORDING_PREVIOUS_RECORDING_IN_PROGRESS = 0x130;
+    public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 0x30;
     /** No recording. Please check recorder and connection. */
-    public static final int MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION = 0x131;
+    public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 0x31;
     /** Cannot record currently displayed source. */
-    public static final int MESSAGE_NO_RECORDING_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x132;
+    public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x32;
+    /** CEC is disabled. */
+    public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 0x33;
 
+    // --- Types for timer recording
     /** Timer recording type for digital service source. */
     public static final int TIMER_RECORDING_TYPE_DIGITAL = 1;
     /** Timer recording type for analogue service source. */
@@ -139,6 +141,14 @@
     /** Timer recording type for external source. */
     public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3;
 
+    // --- Extra result value for timer recording.
+    /** No timer recording - check recorder and connection. */
+    public static final int TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 0x01;
+    /** No timer recording - cannot record selected source. */
+    public static final int TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 0x02;
+    /** CEC is disabled. */
+    public static final int TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED = 0x33;
+
     // True if we have a logical device of type playback hosted in the system.
     private final boolean mHasPlaybackDevice;
     // True if we have a logical device of type TV hosted in the system.
diff --git a/core/java/android/hardware/hdmi/HdmiRecordListener.java b/core/java/android/hardware/hdmi/HdmiRecordListener.java
new file mode 100644
index 0000000..0b1166b
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiRecordListener.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+import android.annotation.SystemApi;
+import android.hardware.hdmi.HdmiRecordSources.RecordSource;
+
+/**
+ * Listener for hdmi record feature including one touch record and timer recording.
+ * @hide
+ */
+@SystemApi
+public abstract class HdmiRecordListener {
+    protected HdmiRecordListener() {}
+
+    /**
+     * Called when TV received one touch record request from record device. The client of this
+     * should use {@link HdmiRecordSources} to return it.
+     *
+     * @param recorderAddress
+     * @return record source to be used for recording. Null if no device is available.
+     */
+    public abstract RecordSource getOneTouchRecordSource(int recorderAddress);
+
+    /**
+     * Called when one touch record is started or failed during initialization.
+     *
+     * @param result result code. For more details, please look at all constants starting with
+     *            "ONE_TOUCH_RECORD_". Only
+     *            {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE},
+     *            {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE},
+     *            {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE}, and
+     *            {@link HdmiControlManager#ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT} mean normal
+     *            start of recording; otherwise, describes failure.
+     */
+    public void onOneTouchRecordResult(int result) {
+    }
+
+    /**
+     * Called when timer recording is started or failed during initialization.
+     *
+     * @param result The most significant three bytes may contain result of <Timer Status>
+     *        while the least significant byte may have error message like
+     *        {@link HdmiControlManager#TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION}
+     *        or
+     *        {@link HdmiControlManager #TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE}
+     *        . If the least significant byte has non zero value the most significant three bytes
+     *        may have 0 value.
+     */
+    // TODO: implement result parser.
+    public void onTimerRecordingResult(int result) {
+    }
+}
diff --git a/core/java/android/hardware/hdmi/HdmiRecordSources.java b/core/java/android/hardware/hdmi/HdmiRecordSources.java
index 296cae6..917d1d9 100644
--- a/core/java/android/hardware/hdmi/HdmiRecordSources.java
+++ b/core/java/android/hardware/hdmi/HdmiRecordSources.java
@@ -492,7 +492,7 @@
         /** Indicates that a service is identified by a logical or virtual channel number. */
         private static final int DIGITAL_SERVICE_IDENTIFIED_BY_CHANNEL = 1;
 
-        private static final int EXTRA_DATA_SIZE = 7;
+        static final int EXTRA_DATA_SIZE = 7;
 
         /**
          * Type of identification. It should be one of DIGITAL_SERVICE_IDENTIFIED_BY_DIGITAL_ID and
@@ -609,7 +609,7 @@
      */
     @SystemApi
     public static final class AnalogueServiceSource extends RecordSource {
-        private static final int EXTRA_DATA_SIZE = 4;
+        static final int EXTRA_DATA_SIZE = 4;
 
         /** Indicates the Analogue broadcast type. */
         private final int mBroadcastType;
@@ -668,7 +668,7 @@
      */
     @SystemApi
     public static final class ExternalPlugData extends RecordSource {
-        private static final int EXTRA_DATA_SIZE = 1;
+        static final int EXTRA_DATA_SIZE = 1;
 
         /** External Plug number on the Recording Device. */
         private final int mPlugNumber;
@@ -713,7 +713,7 @@
      */
     @SystemApi
     public static final class ExternalPhysicalAddress extends RecordSource {
-        private static final int EXTRA_DATA_SIZE = 2;
+        static final int EXTRA_DATA_SIZE = 2;
 
         private final int mPhysicalAddress;
 
@@ -751,6 +751,7 @@
      * Check the byte array of record source.
      * @hide
      */
+    @SystemApi
     public static boolean checkRecordSource(byte[] recordSource) {
         int recordSourceType = recordSource[0];
         int extraDataSize = recordSource.length - 1;
diff --git a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
index 3e5e49b..01b4dd3 100644
--- a/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
+++ b/core/java/android/hardware/hdmi/HdmiTimerRecordSources.java
@@ -16,6 +16,10 @@
 
 package android.hardware.hdmi;
 
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
+
 import android.annotation.SystemApi;
 import android.hardware.hdmi.HdmiRecordSources.AnalogueServiceSource;
 import android.hardware.hdmi.HdmiRecordSources.DigitalServiceSource;
@@ -420,4 +424,35 @@
             return getDataSize(false);
         }
     }
+
+    /**
+     * Check the byte array of timer record source.
+     * @param sourcetype
+     * @param recordSource
+     * @hide
+     */
+    @SystemApi
+    public static boolean checkTimerRecordSource(int sourcetype, byte[] recordSource) {
+        int recordSourceSize = recordSource.length - TimerInfo.BASIC_INFO_SIZE;
+        switch (sourcetype) {
+            case TIMER_RECORDING_TYPE_DIGITAL:
+                return DigitalServiceSource.EXTRA_DATA_SIZE == recordSourceSize;
+            case TIMER_RECORDING_TYPE_ANALOGUE:
+                return AnalogueServiceSource.EXTRA_DATA_SIZE == recordSourceSize;
+            case TIMER_RECORDING_TYPE_EXTERNAL:
+                int specifier = recordSource[TimerInfo.BASIC_INFO_SIZE];
+                if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PLUG) {
+                    // One byte for specifier.
+                    return ExternalPlugData.EXTRA_DATA_SIZE + 1 == recordSourceSize;
+                } else if (specifier == EXTERNAL_SOURCE_SPECIFIER_EXTERNAL_PHYSICAL_ADDRESS) {
+                    // One byte for specifier.
+                    return ExternalPhysicalAddress.EXTRA_DATA_SIZE + 1 == recordSourceSize;
+                } else {
+                    // Invalid specifier.
+                    return false;
+                }
+            default:
+                return false;
+        }
+    }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java
index 6080914..3436287 100644
--- a/core/java/android/hardware/hdmi/HdmiTvClient.java
+++ b/core/java/android/hardware/hdmi/HdmiTvClient.java
@@ -15,10 +15,9 @@
  */
 package android.hardware.hdmi;
 
-import static android.hardware.hdmi.HdmiRecordSources.RecordSource;
-import static android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource;
-
 import android.annotation.SystemApi;
+import android.hardware.hdmi.HdmiRecordSources.RecordSource;
+import android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -155,27 +154,15 @@
     }
 
     /**
-     * Callback interface to used to get notified when a record request from recorder device.
+     * Set record listener
+     *
+     * @param listener
      */
-    public interface RecordRequestListener {
-        /**
-         * Called when tv receives request request from recorder device. When it's called,
-         * it should return record source in byte array so that hdmi control service
-         * can start recording with the given source info.
-         *
-         * @return {@link HdmiRecordSources} to be used to set recording info
-         */
-        RecordSource onRecordRequestReceived(int recorderAddress);
-    }
-
-    /**
-     * Set {@link RecordRequestListener} to hdmi control service.
-     */
-    public void setOneTouchRecordRequestListener(RecordRequestListener listener) {
+    public void setRecordListener(HdmiRecordListener listener) {
         try {
-            mService.setOneTouchRecordRequestListener(getCallbackWrapper(listener));
+            mService.setHdmiRecordListener(getListenerWrapper(listener));
         } catch (RemoteException e) {
-            Log.e(TAG, "failed to set record request listener: ", e);
+            Log.e(TAG, "failed to set record listener.", e);
         }
     }
 
@@ -282,13 +269,12 @@
         };
     }
 
-    private static IHdmiRecordRequestListener getCallbackWrapper(
-            final RecordRequestListener listener) {
-        return new IHdmiRecordRequestListener.Stub() {
+    private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) {
+        return new IHdmiRecordListener.Stub() {
             @Override
-            public byte[] onRecordRequestReceived(int recorderAddress) throws RemoteException {
+            public byte[] getOneTouchRecordSource(int recorderAddress) {
                 HdmiRecordSources.RecordSource source =
-                        listener.onRecordRequestReceived(recorderAddress);
+                        callback.getOneTouchRecordSource(recorderAddress);
                 if (source == null) {
                     return EmptyArray.BYTE;
                 }
@@ -296,6 +282,16 @@
                 source.toByteArray(true, data, 0);
                 return data;
             }
+
+            @Override
+            public void onOneTouchRecordResult(int result) {
+                callback.onOneTouchRecordResult(result);
+            }
+
+            @Override
+            public void onTimerRecordingResult(int result) {
+                callback.onTimerRecordingResult(result);
+            }
         };
     }
 }
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 95e0ee0..808e0c9 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -22,7 +22,7 @@
 import android.hardware.hdmi.IHdmiDeviceEventListener;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.hardware.hdmi.IHdmiInputChangeListener;
-import android.hardware.hdmi.IHdmiRecordRequestListener;
+import android.hardware.hdmi.IHdmiRecordListener;
 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
 import android.hardware.hdmi.IHdmiVendorCommandListener;
 
@@ -62,7 +62,7 @@
     void sendVendorCommand(int deviceType, int targetAddress, in byte[] params,
             boolean hasVendorId);
     void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType);
-    void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener);
+    void setHdmiRecordListener(IHdmiRecordListener callback);
     void startOneTouchRecord(int recorderAddress, in byte[] recordSource);
     void stopOneTouchRecord(int recorderAddress);
     void startTimerRecording(int recorderAddress, int sourceType, in byte[] recordSource);
diff --git a/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl b/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl
new file mode 100644
index 0000000..fba4b05
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiRecordListener.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+/**
+ * @hide
+ */
+ interface IHdmiRecordListener {
+     /**
+      * Called when TV received one touch record request from record device.
+      *
+      * @param recorderAddress
+      * @return record source in byte array.
+      */
+     byte[] getOneTouchRecordSource(int recorderAddress);
+
+     /**
+      * Called when one touch record is started or failed during initialization.
+      *
+      * @param result result code for one touch record
+      */
+     void onOneTouchRecordResult(int result);
+     /**
+      * Called when timer recording is started or failed during initialization.
+      * @param result result code for timer recording
+      */
+     void onTimerRecordingResult(int result);
+ }
\ No newline at end of file
diff --git a/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl b/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl
deleted file mode 100644
index f8f9e5f..0000000
--- a/core/java/android/hardware/hdmi/IHdmiRecordRequestListener.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.hdmi;
-
-/**
- * Callback interface definition for HDMI client to fill record source info
- * when it gets record start request.
- *
- * @hide
- */
-interface IHdmiRecordRequestListener {
-    byte[] onRecordRequestReceived(int recorderAddress);
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index bbecafa..5da8aef 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -16,10 +16,18 @@
 
 package com.android.server.hdmi;
 
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN;
+import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED;
+import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
+import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE;
+
 import android.content.Intent;
 import android.hardware.hdmi.HdmiCecDeviceInfo;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiRecordSources;
+import android.hardware.hdmi.HdmiTimerRecordSources;
 import android.hardware.hdmi.IHdmiControlCallback;
 import android.media.AudioManager;
 import android.media.AudioSystem;
@@ -47,7 +55,7 @@
 final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     private static final String TAG = "HdmiCecLocalDeviceTv";
 
-    // Whether ARC is available or not. "true" means that ARC is estabilished between TV and
+    // Whether ARC is available or not. "true" means that ARC is established between TV and
     // AVR as audio receiver.
     @ServiceThreadOnly
     private boolean mArcEstablished = false;
@@ -819,7 +827,8 @@
             // Assumes only one OneTouchRecordAction.
             OneTouchRecordAction action = actions.get(0);
             if (action.getRecorderAddress() != message.getSource()) {
-                displayOsd(HdmiControlManager.MESSAGE_NO_RECORDING_PREVIOUS_RECORDING_IN_PROGRESS);
+                announceOneTouchRecordResult(
+                        HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS);
             }
             return super.handleRecordTvScreen(message);
         }
@@ -830,6 +839,14 @@
         return true;
     }
 
+    void announceOneTouchRecordResult(int result) {
+        mService.invokeOneTouchRecordResult(result);
+    }
+
+    void announceTimerRecordingResult(int result) {
+        mService.invokeTimerRecordingResult(result);
+    }
+
     private boolean isMessageForSystemAudio(HdmiCecMessage message) {
         if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM
                 || message.getDestination() != Constants.ADDR_TV
@@ -1241,16 +1258,19 @@
         assertRunOnServiceThread();
         if (!mService.isControlEnabled()) {
             Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED);
             return;
         }
 
         if (!checkRecorder(recorderAddress)) {
             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
             return;
         }
 
         if (!checkRecordSource(recordSource)) {
             Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN);
             return;
         }
 
@@ -1264,11 +1284,13 @@
         assertRunOnServiceThread();
         if (!mService.isControlEnabled()) {
             Slog.w(TAG, "Can not stop one touch record. CEC control is disabled.");
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED);
             return;
         }
 
         if (!checkRecorder(recorderAddress)) {
             Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
+            announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
             return;
         }
 
@@ -1292,8 +1314,35 @@
     @ServiceThreadOnly
     void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {
         assertRunOnServiceThread();
+        if (!mService.isControlEnabled()) {
+            Slog.w(TAG, "Can not start one touch record. CEC control is disabled.");
+            announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED);
+            return;
+        }
 
-        // TODO: implement this.
+        if (!checkRecorder(recorderAddress)) {
+            Slog.w(TAG, "Invalid recorder address:" + recorderAddress);
+            announceTimerRecordingResult(
+                    TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
+            return;
+        }
+
+        if (!checkTimerRecordingSource(sourceType, recordSource)) {
+            Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource));
+            announceTimerRecordingResult(
+                    TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE);
+            return;
+        }
+
+        addAndStartAction(
+                new TimerRecordingAction(this, recorderAddress, sourceType, recordSource));
+        Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:"
+                + sourceType + ", RecordSource:" + Arrays.toString(recordSource));
+    }
+
+    private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) {
+        return (recordSource != null)
+                && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource);
     }
 
     @ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 79f1964..0855bfa 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -478,6 +478,78 @@
         return buildCommand(src, dest, Constants.MESSAGE_RECORD_OFF);
     }
 
+    /**
+     * Build <Set Digital Timer> command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and digital service information to be recorded
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSetDigitalTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params);
+    }
+
+    /**
+     * Build <Set Analogue Timer> command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and analog service information to be recorded
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSetAnalogueTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params);
+    }
+
+    /**
+     * Build <Set External Timer> command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and external source information to be recorded
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildSetExternalTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params);
+    }
+
+    /**
+     * Build <Clear Digital Timer> command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and digital service information to be cleared
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildClearDigitalTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params);
+    }
+
+    /**
+     * Build <Clear Analog Timer> command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and analog service information to be cleared
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildClearAnalogueTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params);
+    }
+
+    /**
+     * Build <Clear Digital Timer> command.
+     *
+     * @param src source address of command
+     * @param dest destination address of command
+     * @param params byte array of timing information and external source information to be cleared
+     * @return newly created {@link HdmiCecMessage}
+     */
+    static HdmiCecMessage buildClearExternalTimer(int src, int dest, byte[] params) {
+        return buildCommand(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params);
+    }
+
     /***** Please ADD new buildXXX() methods above. ******/
 
     /**
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 7672232..60421e3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -32,7 +32,7 @@
 import android.hardware.hdmi.IHdmiDeviceEventListener;
 import android.hardware.hdmi.IHdmiHotplugEventListener;
 import android.hardware.hdmi.IHdmiInputChangeListener;
-import android.hardware.hdmi.IHdmiRecordRequestListener;
+import android.hardware.hdmi.IHdmiRecordListener;
 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
 import android.hardware.hdmi.IHdmiVendorCommandListener;
 import android.media.AudioManager;
@@ -160,10 +160,10 @@
     private InputChangeListenerRecord mInputChangeListenerRecord;
 
     @GuardedBy("mLock")
-    private IHdmiRecordRequestListener mRecordRequestListener;
+    private IHdmiRecordListener mRecordListener;
 
     @GuardedBy("mLock")
-    private HdmiRecordRequestListenerRecord mRecordRequestListenerRecord;
+    private HdmiRecordListenerRecord mRecordListenerRecord;
 
     // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
     // handling will be disabled and no request will be handled.
@@ -692,11 +692,11 @@
         }
     }
 
-    private class HdmiRecordRequestListenerRecord implements IBinder.DeathRecipient {
+    private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
             synchronized (mLock) {
-                mRecordRequestListener = null;
+                mRecordListener = null;
             }
         }
     }
@@ -1027,11 +1027,11 @@
                     }
                 }
             });
-         }
+        }
 
         @Override
-        public void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) {
-            HdmiControlService.this.setOneTouchRecordRequestListener(listener);
+        public void setHdmiRecordListener(IHdmiRecordListener listener) {
+            HdmiControlService.this.setHdmiRecordListener(listener);
         }
 
         @Override
@@ -1234,32 +1234,55 @@
         }
     }
 
-    private void setOneTouchRecordRequestListener(IHdmiRecordRequestListener listener) {
+    private void setHdmiRecordListener(IHdmiRecordListener listener) {
         synchronized (mLock) {
-            mRecordRequestListenerRecord = new HdmiRecordRequestListenerRecord();
+            mRecordListenerRecord = new HdmiRecordListenerRecord();
             try {
-                listener.asBinder().linkToDeath(mRecordRequestListenerRecord, 0);
+                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Listener already died", e);
-                return;
+                Slog.w(TAG, "Listener already died.", e);
             }
-            mRecordRequestListener = listener;
+            mRecordListener = listener;
         }
     }
 
     byte[] invokeRecordRequestListener(int recorderAddress) {
         synchronized (mLock) {
-            try {
-                if (mRecordRequestListener != null) {
-                    return mRecordRequestListener.onRecordRequestReceived(recorderAddress);
+            if (mRecordListener != null) {
+                try {
+                    return mRecordListener.getOneTouchRecordSource(recorderAddress);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to start record.", e);
                 }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to start record.", e);
             }
             return EmptyArray.BYTE;
         }
     }
 
+    void invokeOneTouchRecordResult(int result) {
+        synchronized (mLock) {
+            if (mRecordListener != null) {
+                try {
+                    mRecordListener.onOneTouchRecordResult(result);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
+                }
+            }
+        }
+    }
+
+    void invokeTimerRecordingResult(int result) {
+        synchronized (mLock) {
+            if (mRecordListener != null) {
+                try {
+                    mRecordListener.onTimerRecordingResult(result);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
+                }
+            }
+        }
+    }
+
     private void invokeCallback(IHdmiControlCallback callback, int result) {
         try {
             callback.onComplete(result);
diff --git a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java
index 9ecbc5e..befc640 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java
@@ -16,26 +16,24 @@
 
 package com.android.server.hdmi;
 
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_ANALOGUE_SERVICE;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_DIGITAL_SERVICE;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_EXTERNAL_INPUT;
-import static android.hardware.hdmi.HdmiControlManager.MESSAGE_RECORDING_STATUS_MESSAGE_START;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE;
+import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT;
 
 import android.util.Slog;
 
 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
 
 /**
- * Feature action that performs one touch record. This class only provides a skeleton of one touch
- * play and has no detail implementation.
+ * Feature action that performs one touch record.
  */
 public class OneTouchRecordAction extends FeatureAction {
     private static final String TAG = "OneTouchRecordAction";
 
-    // Timer out for waiting <Record Status>
-    private static final int RECORD_STATUS_TIMEOUT = 120000;
+    // Timer out for waiting <Record Status> 120s
+    private static final int RECORD_STATUS_TIMEOUT_MS = 120000;
 
     // State that waits for <Record Status> once sending <Record On>
     private static final int STATE_WAITING_FOR_RECORD_STATUS = 1;
@@ -65,13 +63,14 @@
                     public void onSendCompleted(int error) {
                         // if failed to send <Record On>, display error message and finish action.
                         if (error != Constants.SEND_RESULT_SUCCESS) {
-                            tv().displayOsd(MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION);
+                            tv().announceOneTouchRecordResult(
+                                    ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
                             finish();
                             return;
                         }
 
                         mState = STATE_WAITING_FOR_RECORD_STATUS;
-                        addTimer(mState, RECORD_STATUS_TIMEOUT);
+                        addTimer(mState, RECORD_STATUS_TIMEOUT_MS);
                     }
                 });
     }
@@ -97,18 +96,16 @@
         }
 
         int recordStatus = cmd.getParams()[0];
+        tv().announceOneTouchRecordResult(recordStatus);
         Slog.i(TAG, "Got record status:" + recordStatus + " from " + cmd.getSource());
 
-        int recordStatusMessageCode = recordStatus + MESSAGE_RECORDING_STATUS_MESSAGE_START;
-        tv().displayOsd(recordStatusMessageCode);
-
         // If recording started successfully, change state and keep this action until <Record Off>
         // received. Otherwise, finish action.
-        switch (recordStatusMessageCode) {
-            case MESSAGE_RECORDING_CURRENTLY_SELECTED_SOURCE:
-            case MESSAGE_RECORDING_DIGITAL_SERVICE:
-            case MESSAGE_RECORDING_ANALOGUE_SERVICE:
-            case MESSAGE_RECORDING_EXTERNAL_INPUT:
+        switch (recordStatus) {
+            case ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE:
+            case ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE:
+            case ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE:
+            case ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT:
                 mState = STATE_RECORDING_IN_PROGRESS;
                 mActionTimer.clearTimerMessage();
                 break;
@@ -126,7 +123,7 @@
             return;
         }
 
-        tv().displayOsd(MESSAGE_NO_RECORDING_CHECK_RECORDER_CONNECTION);
+        tv().announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION);
         finish();
     }
 
diff --git a/services/core/java/com/android/server/hdmi/TimerRecordingAction.java b/services/core/java/com/android/server/hdmi/TimerRecordingAction.java
new file mode 100644
index 0000000..1dc26f1
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/TimerRecordingAction.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE;
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL;
+import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL;
+import static android.hardware.hdmi.HdmiControlManager.TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION;
+
+import android.util.Slog;
+
+import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
+
+import java.util.Arrays;
+
+/**
+ * Feature action that performs timer recording.
+ */
+public class TimerRecordingAction extends FeatureAction {
+    private static final String TAG = "TimerRecordingAction";
+
+    // Timer out for waiting <Timer Status> 120s.
+    private static final int TIMER_STATUS_TIMEOUT_MS = 120000;
+
+    // State that waits for <Timer Status> once sending <Set XXX Timer>
+    private static final int STATE_WAITING_FOR_TIMER_STATUS = 1;
+
+    private final int mRecorderAddress;
+    private final int mSourceType;
+    private final byte[] mRecordSource;
+
+    TimerRecordingAction(HdmiCecLocalDevice source, int recorderAddress, int sourceType,
+            byte[] recordSource) {
+        super(source);
+        mRecorderAddress = recorderAddress;
+        mSourceType = sourceType;
+        mRecordSource = recordSource;
+    }
+
+    @Override
+    boolean start() {
+        sendTimerMessage();
+        return true;
+    }
+
+    private void sendTimerMessage() {
+        HdmiCecMessage message = null;
+        switch (mSourceType) {
+            case TIMER_RECORDING_TYPE_DIGITAL:
+                message = HdmiCecMessageBuilder.buildSetDigitalTimer(getSourceAddress(),
+                        mRecorderAddress, mRecordSource);
+                break;
+            case TIMER_RECORDING_TYPE_ANALOGUE:
+                message = HdmiCecMessageBuilder.buildSetAnalogueTimer(getSourceAddress(),
+                        mRecorderAddress, mRecordSource);
+                break;
+            case TIMER_RECORDING_TYPE_EXTERNAL:
+                message = HdmiCecMessageBuilder.buildSetExternalTimer(getSourceAddress(),
+                        mRecorderAddress, mRecordSource);
+                break;
+            default:
+                tv().announceTimerRecordingResult(
+                        TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
+                finish();
+                return;
+        }
+        sendCommand(message, new SendMessageCallback() {
+            @Override
+            public void onSendCompleted(int error) {
+                if (error != Constants.SEND_RESULT_SUCCESS) {
+                    mState = STATE_WAITING_FOR_TIMER_STATUS;
+                    addTimer(mState, TIMER_STATUS_TIMEOUT_MS);
+                    finish();
+                    return;
+                }
+            }
+        });
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WAITING_FOR_TIMER_STATUS) {
+            return false;
+        }
+
+        if (cmd.getSource() != mRecorderAddress) {
+            return false;
+        }
+
+        switch (cmd.getOpcode()) {
+            case Constants.MESSAGE_TIMER_STATUS:
+                return handleTimerStatus(cmd);
+            case Constants.MESSAGE_FEATURE_ABORT:
+                return handleFeatureAbort(cmd);
+        }
+        return false;
+    }
+
+    private boolean handleTimerStatus(HdmiCecMessage cmd) {
+        byte[] timerStatusData = cmd.getParams();
+        // [Timer Status Data] should be one or three bytes.
+        if (timerStatusData.length == 1 || timerStatusData.length == 3) {
+            tv().announceTimerRecordingResult(bytesToInt(timerStatusData));
+            Slog.i(TAG, "Received [Timer Status Data]:" + Arrays.toString(timerStatusData));
+        } else {
+            Slog.w(TAG, "Invalid [Timer Status Data]:" + Arrays.toString(timerStatusData));
+        }
+
+        // Unlike one touch record, finish timer record when <Timer Status> is received.
+        finish();
+        return true;
+    }
+
+    private boolean handleFeatureAbort(HdmiCecMessage cmd) {
+        byte[] params = cmd.getParams();
+        int messageType = params[0];
+        switch (messageType) {
+            case Constants.MESSAGE_SET_DIGITAL_TIMER: // fall through
+            case Constants.MESSAGE_SET_ANALOG_TIMER: // fall through
+            case Constants.MESSAGE_SET_EXTERNAL_TIMER: // fall through
+                break;
+            default:
+                return false;
+        }
+        int reason = params[1];
+        Slog.i(TAG, "[Feature Abort] for " + messageType + " reason:" + reason);
+        tv().announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
+        finish();
+        return true;
+    }
+
+    // Convert byte array to int.
+    private static int bytesToInt(byte[] data) {
+        if (data.length > 4) {
+            throw new IllegalArgumentException("Invalid data size:" + Arrays.toString(data));
+        }
+        int result = 0;
+        for (int i = 0; i < data.length; ++i) {
+            int shift = (3 - i) * 8;
+            result |= ((data[i] & 0xFF) << shift);
+        }
+        return result;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            Slog.w(TAG, "Timeout in invalid state:[Expected:" + mState + ", Actual:" + state + "]");
+            return;
+        }
+
+        tv().announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION);
+        finish();
+    }
+}